Merge remote-tracking branch 'origin/upstream/main'

Updated to commit: f8bc783c2fb469dd86639a41cc7c7df6a8e79677

Additionally, resolved merge conflict for openthread-config-android.h
and also updated ot_version.gni to indicate new version for
soft-transition.

Also changed compilation flags to compile for THREAD_VERSION_1_1 so
that it works with current RCP firmware.

Bug: 73064

Change-Id: I4ff3f0159f0ba46c3d7012e9041fdcd92d9236a7
diff --git a/.codecov.yml b/.codecov.yml
index 2b6b883..bf74750 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -1,10 +1,6 @@
 coverage:
   status:
-    project:
-      default:
-        target: auto
-        threshold: 1%
-        only_pulls: true
+    project: off
     patch: off
 
 ignore:
@@ -13,4 +9,4 @@
 
 comment:
   layout: "diff, flags, files"
-  after_n_builds: 4
+  after_n_builds: 5
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 041a4ef..47acce8 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -38,12 +38,14 @@
     - uses: rokroskar/workflow-run-cleanup-action@master
       env:
         GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
-      if: "github.ref != 'refs/heads/master'"
+      if: "github.ref != 'refs/heads/main'"
 
   pretty:
     runs-on: ubuntu-20.04
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -58,6 +60,8 @@
     runs-on: ubuntu-18.04
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo pip3 install --system -U cmake==3.10.3
@@ -84,6 +88,8 @@
       CXX: ${{ matrix.compiler_cpp }}
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -96,6 +102,8 @@
     runs-on: ubuntu-20.04
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -127,11 +135,13 @@
             gcc_extract_dir: gcc-arm-none-eabi-9-2019-q4-major
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         cd /tmp
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
-        sudo apt-get --no-install-recommends install -y lib32z1 ninja-build
+        sudo apt-get --no-install-recommends install -y lib32z1 ninja-build gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf
         wget ${{ matrix.gcc_download_url }} -O gcc-arm.tar.bz2
         tar xjf gcc-arm.tar.bz2
         # use the minimal required cmake version
@@ -155,6 +165,8 @@
       CXX: g++-${{ matrix.gcc_ver }}
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -175,6 +187,8 @@
       CXX: clang++-${{ matrix.clang_ver }}
     steps:
       - uses: actions/checkout@v2
+        with:
+          submodules: true
       - name: Bootstrap
         run: |
           sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -198,6 +212,8 @@
       LDFLAGS: -m32
     steps:
       - uses: actions/checkout@v2
+        with:
+          submodules: true
       - name: Bootstrap
         run: |
           sudo dpkg --add-architecture i386
@@ -213,6 +229,8 @@
     runs-on: ubuntu-20.04
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -233,6 +251,8 @@
       CXX: clang++
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         brew install automake ninja llvm
@@ -248,6 +268,8 @@
       CXX: g++
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         brew install automake ninja
@@ -260,6 +282,8 @@
     runs-on: ubuntu-20.04
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Build
       run: |
         docker run --rm -v $PWD:/build/openthread openthread/android-trusty /build/openthread/script/check-android-build
diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index d3f369f..a8c9a1f 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -38,7 +38,7 @@
     - uses: rokroskar/workflow-run-cleanup-action@master
       env:
         GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
-      if: "github.ref != 'refs/heads/master'"
+      if: "github.ref != 'refs/heads/main'"
 
   buildx:
     name: buildx-${{ matrix.docker_name }}
diff --git a/tests/scripts/expect/cli-anycast.exp b/.github/workflows/makefile-check.yml
similarity index 74%
copy from tests/scripts/expect/cli-anycast.exp
copy to .github/workflows/makefile-check.yml
index 3081a6b..37e8024 100644
--- a/tests/scripts/expect/cli-anycast.exp
+++ b/.github/workflows/makefile-check.yml
@@ -1,6 +1,5 @@
-#!/usr/bin/expect -f
 #
-#  Copyright (c) 2020, The OpenThread Authors.
+#  Copyright (c) 2021, The OpenThread Authors.
 #  All rights reserved.
 #
 #  Redistribution and use in source and binary forms, with or without
@@ -27,30 +26,25 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
-source "tests/scripts/expect/_common.exp"
+name: Makefile Check
 
+on: [push, pull_request]
 
-set spawn_id [spawn_node 1]
+jobs:
+  cancel-previous-runs:
+    runs-on: ubuntu-20.04
+    steps:
+    - uses: rokroskar/workflow-run-cleanup-action@master
+      env:
+        GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+      if: "github.ref != 'refs/heads/main'"
 
-send "panid 0xface\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-
-wait_for "state" "leader"
-expect "Done"
-
-send "ping fdde:ad00:beef:0:0:ff:fe00:fc00\n"
-expect "Done"
-expect "16 bytes from "
-expect {
-    "fdde:ad00:beef:0:0:ff:fe00:fc00" abort
-
-    -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})} {}
-
-    timeout abort
-}
-
-dispose
+  makefile-check:
+    runs-on: ubuntu-20.04
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        submodules: true
+    - name: Check
+      run: |
+        script/check-core-makefiles
diff --git a/.github/workflows/otbr.yml b/.github/workflows/otbr.yml
new file mode 100644
index 0000000..7dc6195
--- /dev/null
+++ b/.github/workflows/otbr.yml
@@ -0,0 +1,187 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+name: Border Router
+
+on: [push, pull_request]
+
+jobs:
+
+  cancel-previous-runs:
+    runs-on: ubuntu-20.04
+    steps:
+    - uses: rokroskar/workflow-run-cleanup-action@master
+      env:
+        GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+      if: "github.ref != 'refs/heads/main'"
+
+  backbone-router:
+    runs-on: ubuntu-20.04
+    env:
+      REFERENCE_DEVICE: 1
+      VIRTUAL_TIME: 0
+      PACKET_VERIFICATION: 1
+      THREAD_VERSION: 1.2
+      INTER_OP: 1
+      COVERAGE: 1
+      MULTIPLY: 1
+      PYTHONUNBUFFERED: 1
+      VERBOSE: 1
+      # The Border Routing and DUA feature can coexist, but current wireshark
+      # packet verification can't handle it because of the order of context ID
+      # of OMR prefix and Domain prefix is not deterministic.
+      BORDER_ROUTING: 0
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        submodules: true
+    - name: Build OTBR Docker
+      env:
+        GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+      run: |
+        ./script/test build_otbr_docker
+    - name: Bootstrap
+      run: |
+        sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
+        sudo apt-get --no-install-recommends install -y python3-setuptools python3-wheel ninja-build socat lcov
+        python3 -m pip install -r tests/scripts/thread-cert/requirements.txt
+    - name: Build
+      run: |
+        ./script/test build
+    - name: Get Thread-Wireshark
+      run: |
+        ./script/test get_thread_wireshark
+    - name: Run
+      run: |
+        export CI_ENV="$(bash <(curl -s https://codecov.io/env)) -e GITHUB_ACTIONS -e COVERAGE"
+        echo "CI_ENV=${CI_ENV}"
+        sudo -E ./script/test cert_suite ./tests/scripts/thread-cert/backbone/*.py || (sudo chmod a+r *.log *.json *.pcap && false)
+    - uses: actions/upload-artifact@v2
+      with:
+        name: cov-thread-1-2-backbone-docker
+        path: /tmp/coverage/
+    - uses: actions/upload-artifact@v2
+      if: ${{ failure() }}
+      with:
+        name: thread-1-2-backbone-results
+        path: |
+          *.pcap
+          *.json
+          *.log
+    - name: Generate Coverage
+      run: |
+        ./script/test generate_coverage gcc
+    - uses: actions/upload-artifact@v2
+      with:
+        name: cov-thread-1-2-backbone
+        path: tmp/coverage.info
+
+  thread-border-router:
+    runs-on: ubuntu-20.04
+    env:
+      REFERENCE_DEVICE: 1
+      VIRTUAL_TIME: 0
+      PACKET_VERIFICATION: 1
+      THREAD_VERSION: 1.2
+      INTER_OP: 1
+      COVERAGE: 1
+      MULTIPLY: 1
+      PYTHONUNBUFFERED: 1
+      VERBOSE: 1
+      BORDER_ROUTING: 1
+    steps:
+    - uses: actions/checkout@v2
+    - name: Build OTBR Docker
+      env:
+        GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+      run: |
+        ./script/test build_otbr_docker
+    - name: Bootstrap
+      run: |
+        sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
+        sudo apt-get --no-install-recommends install -y python3-setuptools python3-wheel ninja-build socat lcov
+        python3 -m pip install -r tests/scripts/thread-cert/requirements.txt
+    - name: Build
+      run: |
+        ./script/test build
+    - name: Get Thread-Wireshark
+      run: |
+        ./script/test get_thread_wireshark
+    - name: Run
+      run: |
+        export CI_ENV="$(bash <(curl -s https://codecov.io/env)) -e GITHUB_ACTIONS -e COVERAGE"
+        echo "CI_ENV=${CI_ENV}"
+        sudo -E ./script/test cert_suite ./tests/scripts/thread-cert/border_router/*.py || (sudo chmod a+r *.log *.json *.pcap && false)
+    - uses: actions/upload-artifact@v2
+      with:
+        name: cov-thread-border-router-docker
+        path: /tmp/coverage/
+    - uses: actions/upload-artifact@v2
+      if: ${{ failure() }}
+      with:
+        name: thread-border-router-results
+        path: |
+          *.pcap
+          *.json
+          *.log
+    - name: Generate Coverage
+      run: |
+        ./script/test generate_coverage gcc
+    - uses: actions/upload-artifact@v2
+      with:
+        name: cov-thread-border-router
+        path: tmp/coverage.info
+
+  upload-coverage:
+    needs:
+    - backbone-router
+    - thread-border-router
+    runs-on: ubuntu-20.04
+    steps:
+    - uses: actions/checkout@v2
+      with:
+        submodules: true
+    - name: Bootstrap
+      run: |
+        sudo apt-get --no-install-recommends install -y lcov
+    - uses: actions/download-artifact@v2
+      with:
+        path: coverage/
+    - name: Upload Coverage
+      run: |
+        script/test upload_codecov
+
+  delete-coverage-artifacts:
+    needs: upload-coverage
+    if: always()
+    runs-on: ubuntu-20.04
+    steps:
+    - uses: geekyeggo/delete-artifact@1-glob-support
+      with:
+        name: cov-*
+        useGlob: true
diff --git a/.github/workflows/otci.yml b/.github/workflows/otci.yml
new file mode 100644
index 0000000..97e78f7
--- /dev/null
+++ b/.github/workflows/otci.yml
@@ -0,0 +1,70 @@
+#
+#  Copyright (c) 2020, The OpenThread Authors.
+#  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.
+#
+
+name: OTCI
+
+on: [push, pull_request]
+
+jobs:
+
+  cancel-previous-runs:
+    runs-on: ubuntu-20.04
+    steps:
+    - uses: rokroskar/workflow-run-cleanup-action@master
+      env:
+        GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+      if: "github.ref != 'refs/heads/main'"
+
+  cli-sim:
+    runs-on: ubuntu-20.04
+    strategy:
+      matrix:
+        virtual_time: [0, 1]
+    env:
+      REFERENCE_DEVICE: 1
+      VIRTUAL_TIME: ${{ matrix.virtual_time }}
+      REAL_DEVICE: 0
+    steps:
+    - uses: actions/checkout@v2
+    - name: Bootstrap
+      run: |
+        sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
+        sudo apt-get --no-install-recommends install -y g++-multilib python3-setuptools python3-wheel
+        python3 -m pip install -r tests/scripts/thread-cert/requirements.txt
+    - name: Build
+      run: |
+        ./bootstrap
+        make -f examples/Makefile-simulation THREAD_VERSION=1.2 DUA=1 MLR=1 BACKBONE_ROUTER=1 CSL_RECEIVER=1
+    - name: Install OTCI Python Library
+      run: |
+        (cd tools/otci && python3 setup.py install --user)
+    - name: Run
+      run: |
+        export PYTHONPATH=./tests/scripts/thread-cert/
+        export OT_CLI=./output/simulation/bin/ot-cli-ftd
+        python3 tools/otci/tests/test_otci.py
diff --git a/.github/workflows/posix.yml b/.github/workflows/posix.yml
index 6c46bde..0c64cd9 100644
--- a/.github/workflows/posix.yml
+++ b/.github/workflows/posix.yml
@@ -38,8 +38,103 @@
     - uses: rokroskar/workflow-run-cleanup-action@master
       env:
         GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
-      if: "github.ref != 'refs/heads/master'"
+      if: "github.ref != 'refs/heads/main'"
 
+  expects-macos:
+    runs-on: macos-10.15
+    env:
+      CFLAGS: -DCLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER=1 -DOPENTHREAD_CONFIG_MLE_MAX_CHILDREN=15 -DOPENTHREAD_CONFIG_MAC_SCAN_DURATION=500
+      CXXFLAGS: -DCLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER=1 -DOPENTHREAD_CONFIG_MLE_MAX_CHILDREN=15 -DOPENTHREAD_CONFIG_MAC_SCAN_DURATION=500
+      CC: clang
+      CXX: clang++
+      THREAD_VERSION: 1.1
+    steps:
+    - uses: actions/checkout@v2
+    - name: Bootstrap
+      run: |
+        brew install ninja
+    - name: Run RCP Mode
+      run: |
+        OT_OPTIONS='-DOT_READLINE=OFF -DOT_APP_NCP=OFF' OT_NODE_TYPE=rcp-cli ./script/test build expect
+    - name: Run Native IP Mode
+      run: |
+        brew install dnsmasq
+        echo 'listen-address=::1' | sudo tee $(brew --prefix)/etc/dnsmasq.conf
+        sudo brew services start dnsmasq
+        host ipv6.google.com ::1
+        OT_OPTIONS=-DOT_READLINE=OFF OT_NATIVE_IP=1 OT_NODE_TYPE=rcp-cli ./script/test clean build expect
+
+  expects-linux:
+    runs-on: ubuntu-18.04
+    env:
+      CFLAGS: -DCLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER=1 -DOPENTHREAD_CONFIG_MLE_MAX_CHILDREN=15
+      CXXFLAGS: -DCLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER=1 -DOPENTHREAD_CONFIG_MLE_MAX_CHILDREN=15
+    steps:
+    - uses: actions/checkout@v2
+    - name: Bootstrap
+      run: |
+        sudo apt-get --no-install-recommends install -y expect ninja-build lcov socat
+    - name: Run RCP Mode
+      run: |
+        ulimit -c unlimited
+        ./script/test prepare_coredump_upload
+        OT_OPTIONS='-DOT_READLINE=OFF -DOT_FULL_LOGS=ON -DOT_LOG_OUTPUT=PLATFORM_DEFINED' VIRTUAL_TIME=0 OT_NODE_TYPE=rcp ./script/test build expect
+    - name: Check Crash
+      if: ${{ failure() }}
+      run: |
+          CRASHED=$(./script/test check_crash | tail -1)
+          [[ $CRASHED -eq "1" ]] && echo "Crashed!" || echo "Not crashed."
+          echo "CRASHED_RCP=$CRASHED" >> $GITHUB_ENV
+    - uses: actions/upload-artifact@v2
+      if: ${{ failure() && env.CRASHED_RCP == '1' }}
+      with:
+        name: core-expect-rcp
+        path: |
+          ./ot-core-dump/*
+    - name: Generate Coverage
+      run: |
+        ./script/test generate_coverage gcc
+    - uses: actions/upload-artifact@v2
+      with:
+        name: cov-expects-linux-1
+        path: tmp/coverage.info
+    - name: Run TUN Mode
+      run: |
+        sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
+        sudo apt-get install --no-install-recommends -y dnsmasq bind9-host ntp
+        sudo systemctl start dnsmasq ntp
+        host ipv6.google.com 127.0.0.1
+        echo 'listen-address=::1' | sudo tee /etc/dnsmasq.conf
+        echo 0 | sudo tee /proc/sys/net/ipv6/conf/all/disable_ipv6
+        sudo systemctl restart dnsmasq
+        host ipv6.google.com ::1
+        ulimit -c unlimited
+        ./script/test prepare_coredump_upload
+        OT_OPTIONS='-DOT_READLINE=OFF -DOT_FULL_LOGS=ON -DOT_LOG_OUTPUT=PLATFORM_DEFINED' OT_NATIVE_IP=1 VIRTUAL_TIME=0 OT_NODE_TYPE=rcp ./script/test clean build expect
+    - name: Check Crash
+      if: ${{ failure() }}
+      run: |
+          CRASHED=$(./script/test check_crash | tail -1)
+          [[ $CRASHED -eq "1" ]] && echo "Crashed!" || echo "Not crashed."
+          echo "CRASHED_TUN=$CRASHED" >> $GITHUB_ENV
+    - uses: actions/upload-artifact@v2
+      if: ${{ failure() && env.CRASHED_TUN == '1' }}
+      with:
+        name: core-expect-posix
+        path: |
+          ./ot-core-dump/*
+    - uses: actions/upload-artifact@v2
+      if: ${{ failure() }}
+      with:
+        name: syslog-expect-posix
+        path: /var/log/syslog
+    - name: Generate Coverage
+      run: |
+        ./script/test generate_coverage gcc
+    - uses: actions/upload-artifact@v2
+      with:
+        name: cov-expects-linux-2
+        path: tmp/coverage.info
 
   posix-cli:
     runs-on: ubuntu-20.04
@@ -48,10 +143,13 @@
       PYTHONUNBUFFERED: 1
       READLINE: readline
       REFERENCE_DEVICE: 1
+      THREAD_VERSION: 1.1
       VIRTUAL_TIME: 1
       VIRTUAL_TIME_UART: 1
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -86,10 +184,13 @@
       PYTHONUNBUFFERED: 1
       READLINE: readline
       REFERENCE_DEVICE: 1
+      THREAD_VERSION: 1.1
       VIRTUAL_TIME: 1
       VIRTUAL_TIME_UART: 1
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -123,6 +224,8 @@
       COVERAGE: 1
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -148,6 +251,8 @@
       COVERAGE: 1
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -182,6 +287,8 @@
       DAEMON: 1
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -211,6 +318,7 @@
 
   upload-coverage:
     needs:
+    - expects-linux
     - posix-cli
     - posix-ncp
     - posix-ncp-rcp-migrate
@@ -219,6 +327,8 @@
     runs-on: ubuntu-20.04
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo apt-get --no-install-recommends install -y lcov
diff --git a/.github/workflows/simulation.yml b/.github/workflows/simulation-1.1.yml
similarity index 86%
rename from .github/workflows/simulation.yml
rename to .github/workflows/simulation-1.1.yml
index a25ad80..08958c3 100644
--- a/.github/workflows/simulation.yml
+++ b/.github/workflows/simulation-1.1.yml
@@ -26,7 +26,7 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
-name: Simulation
+name: Simulation 1.1
 
 on: [push, pull_request]
 
@@ -38,7 +38,7 @@
     - uses: rokroskar/workflow-run-cleanup-action@master
       env:
         GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
-      if: "github.ref != 'refs/heads/master'"
+      if: "github.ref != 'refs/heads/main'"
 
   distcheck:
     runs-on: ubuntu-20.04
@@ -46,9 +46,12 @@
       CC: clang
       CXX: clang++
       REFERENCE_DEVICE: 1
+      THREAD_VERSION: 1.1
       VIRTUAL_TIME: 1
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -70,11 +73,14 @@
       LDFLAGS: -m32
       COVERAGE: 1
       REFERENCE_DEVICE: 1
+      THREAD_VERSION: 1.1
       VIRTUAL_TIME: 1
       VIRTUAL_TIME_UART: 1
       MAX_NETWORK_SIZE: 999
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - uses: actions/setup-go@v1
       with:
         go-version: '1.14'
@@ -116,11 +122,14 @@
   packet-verification:
     runs-on: ubuntu-20.04
     env:
-      REFERENCE_DEVICE: 1
-      VIRTUAL_TIME: 1
       PACKET_VERIFICATION: 1
+      REFERENCE_DEVICE: 1
+      THREAD_VERSION: 1.1
+      VIRTUAL_TIME: 1
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -161,9 +170,12 @@
       LDFLAGS: -m32
       COVERAGE: 1
       REFERENCE_DEVICE: 1
+      THREAD_VERSION: 1.1
       VIRTUAL_TIME: 1
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -200,11 +212,14 @@
       LDFLAGS: -m32
       COVERAGE: 1
       REFERENCE_DEVICE: 1
+      THREAD_VERSION: 1.1
       USE_MTD: 1
       VIRTUAL_TIME: 1
       MESSAGE_USE_HEAP: ${{ matrix.message_use_heap }}
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -227,7 +242,7 @@
         ./script/test generate_coverage gcc
     - uses: actions/upload-artifact@v2
       with:
-        name: cov-cli-mtd
+        name: cov-cli-mtd-${{ matrix.message_use_heap }}
         path: tmp/coverage.info
 
   cli-time-sync:
@@ -238,10 +253,13 @@
       LDFLAGS: -m32
       COVERAGE: 1
       REFERENCE_DEVICE: 1
+      THREAD_VERSION: 1.1
       TIME_SYNC: 1
       VIRTUAL_TIME: 1
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -272,6 +290,7 @@
     env:
       CFLAGS: -DCLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER=1 -DOPENTHREAD_CONFIG_MLE_MAX_CHILDREN=15
       CXXFLAGS: -DCLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER=1 -DOPENTHREAD_CONFIG_MLE_MAX_CHILDREN=15
+      THREAD_VERSION: 1.1
     steps:
     - uses: actions/checkout@v2
     - name: Bootstrap
@@ -282,87 +301,41 @@
         ulimit -c unlimited
         ./script/test prepare_coredump_upload
         OT_OPTIONS='-DOT_TIME_SYNC=ON -DOT_FULL_LOGS=ON -DOT_LOG_OUTPUT=PLATFORM_DEFINED' VIRTUAL_TIME=0 ./script/test build expect
-    - name: Copy Shared Libraries
+    - name: Check Crash
       if: ${{ failure() }}
       run: |
-          ./script/test copy_so_lib
+          CRASHED=$(./script/test check_crash | tail -1)
+          [[ $CRASHED -eq "1" ]] && echo "Crashed!" || echo "Not crashed."
+          echo "CRASHED_CLI=$CRASHED" >> $GITHUB_ENV
     - uses: actions/upload-artifact@v2
-      if: ${{ failure() }}
+      if: ${{ failure() && env.CRASHED_CLI == '1' }}
       with:
         name: core-expect-cli
         path: |
-          ./build/openthread-simulation-1.1/examples/apps/cli/ot-cli-ftd
           ./ot-core-dump/*
-          ./so-lib/*
     - name: Generate Coverage
       run: |
         ./script/test generate_coverage gcc
     - uses: actions/upload-artifact@v2
       with:
-        name: cov-expects-1
-        path: tmp/coverage.info
-    - name: Run RCP Mode
-      run: |
-        ulimit -c unlimited
-        ./script/test prepare_coredump_upload
-        OT_OPTIONS='-DOT_READLINE=OFF -DOT_FULL_LOGS=ON -DOT_LOG_OUTPUT=PLATFORM_DEFINED' VIRTUAL_TIME=0 OT_NODE_TYPE=rcp ./script/test clean build expect
-    - name: Generate Coverage
-      run: |
-        ./script/test generate_coverage gcc
-    - uses: actions/upload-artifact@v2
-      with:
-        name: cov-expects-2
-        path: tmp/coverage.info
-    - name: Run TUN Mode
-      run: |
-        sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
-        sudo apt-get install --no-install-recommends -y dnsmasq bind9-host ntp
-        sudo systemctl start dnsmasq ntp
-        host ipv6.google.com 127.0.0.1
-        echo 'listen-address=::1' | sudo tee /etc/dnsmasq.conf
-        echo 0 | sudo tee /proc/sys/net/ipv6/conf/all/disable_ipv6
-        sudo systemctl restart dnsmasq
-        host ipv6.google.com ::1
-        ulimit -c unlimited
-        ./script/test prepare_coredump_upload
-        OT_OPTIONS='-DOT_READLINE=OFF -DOT_FULL_LOGS=ON -DOT_LOG_OUTPUT=PLATFORM_DEFINED' OT_NATIVE_IP=1 VIRTUAL_TIME=0 OT_NODE_TYPE=rcp ./script/test clean build expect
-    - name: Copy Shared Libraries
-      if: ${{ failure() }}
-      run: |
-          ./script/test copy_so_lib
-    - uses: actions/upload-artifact@v2
-      if: ${{ failure() }}
-      with:
-        name: core-expect-posix
-        path: |
-          ./build/openthread-simulation-1.1/examples/apps/ncp/ot-rcp
-          ./build/openthread-posix-1.1/src/posix/ot-cli
-          ./ot-core-dump/*
-          ./so-lib/*
-    - uses: actions/upload-artifact@v2
-      if: ${{ failure() }}
-      with:
-        name: syslog-expect-posix
-        path: /var/log/syslog
-    - name: Generate Coverage
-      run: |
-        ./script/test generate_coverage gcc
-    - uses: actions/upload-artifact@v2
-      with:
-        name: cov-expects-3
+        name: cov-expects
         path: tmp/coverage.info
 
   external-commissioner:
     runs-on: ubuntu-20.04
+    env:
+      THREAD_VERSION: 1.1
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       env:
         GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
       run: |
         sudo rm /etc/apt/sources.list.d/*
         sudo apt-get install -y avahi-daemon avahi-utils lcov
-        script/git-tool clone https://github.com/openthread/ot-commissioner.git /tmp/ot-commissioner --depth 1 --branch master
+        script/git-tool clone https://github.com/openthread/ot-commissioner.git /tmp/ot-commissioner --depth 1 --branch main
     - name: Build
       run: |
         cd /tmp/ot-commissioner
@@ -397,9 +370,12 @@
       COVERAGE: 1
       MULTIPLE_INSTANCE: 1
       REFERENCE_DEVICE: 1
+      THREAD_VERSION: 1.1
       VIRTUAL_TIME: 1
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo apt-get --no-install-recommends install -y python3-setuptools python3-wheel lcov
@@ -434,9 +410,12 @@
       NODE_TYPE: ncp-sim
       PYTHONUNBUFFERED: 1
       REFERENCE_DEVICE: 1
+      THREAD_VERSION: 1.1
       VIRTUAL_TIME: 1
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -470,11 +449,14 @@
       NODE_TYPE: ncp-sim
       PYTHONUNBUFFERED: 1
       REFERENCE_DEVICE: 1
+      THREAD_VERSION: 1.1
       VIRTUAL_TIME: 1
       CC: clang
       CXX: clang++
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -516,6 +498,8 @@
     runs-on: ubuntu-20.04
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo apt-get --no-install-recommends install -y lcov
diff --git a/.github/workflows/simulation-1.2.yml b/.github/workflows/simulation-1.2.yml
index 4c9890f..3fdacde 100644
--- a/.github/workflows/simulation-1.2.yml
+++ b/.github/workflows/simulation-1.2.yml
@@ -38,7 +38,7 @@
     - uses: rokroskar/workflow-run-cleanup-action@master
       env:
         GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
-      if: "github.ref != 'refs/heads/master'"
+      if: "github.ref != 'refs/heads/main'"
 
   thread-1-2:
     name: thread-1-2-${{ matrix.compiler.c }}-${{ matrix.arch }}
@@ -59,6 +59,8 @@
         arch: ["m32", "m64"]
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -66,7 +68,6 @@
         python3 -m pip install -r tests/scripts/thread-cert/requirements.txt
     - name: Build
       run: |
-        ./bootstrap
         ./script/test build
     - name: Run
       run: |
@@ -74,15 +75,23 @@
         ./script/test prepare_coredump_upload
         ./script/test unit
         ./script/test cert_suite tests/scripts/thread-cert/v1_2_*
-    - name: Copy Shared Libraries
+    - name: Check Crash
       if: ${{ failure() }}
       run: |
-          ./script/test copy_so_lib
+          CRASHED=$(./script/test check_crash | tail -1)
+          [[ $CRASHED -eq "1" ]] && echo "Crashed!" || echo "Not crashed."
+          echo "CRASHED=$CRASHED" >> $GITHUB_ENV
     - uses: actions/upload-artifact@v2
       if: ${{ failure() }}
       with:
         name: thread-1-2-${{ matrix.compiler.c }}-${{ matrix.arch }}-pcaps
         path: "*.pcap"
+    - uses: actions/upload-artifact@v2
+      if: ${{ failure() && env.CRASHED == '1' }}
+      with:
+        name: core-packet-verification-thread-1-2
+        path: |
+          ./ot-core-dump/*
     - name: Generate Coverage
       run: |
         ./script/test generate_coverage "${{ matrix.compiler.gcov }}"
@@ -101,8 +110,11 @@
       THREAD_VERSION: 1.2
       MAC_FILTER: 1
       INTER_OP: 1
+      INTER_OP_BBR: 0
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -122,10 +134,12 @@
         do
           ./script/test cert_suite ./tests/scripts/thread-cert/v1_2_LowPower*.py
         done
-    - name: Copy Shared Libraries
+    - name: Check Crash
       if: ${{ failure() }}
       run: |
-          ./script/test copy_so_lib
+          CRASHED=$(./script/test check_crash | tail -1)
+          [[ $CRASHED -eq "1" ]] && echo "Crashed!" || echo "Not crashed."
+          echo "CRASHED=$CRASHED" >> $GITHUB_ENV
     - uses: actions/upload-artifact@v2
       if: ${{ failure() }}
       with:
@@ -134,14 +148,11 @@
           *.pcap
           *.json
     - uses: actions/upload-artifact@v2
-      if: ${{ failure() }}
+      if: ${{ failure() && env.CRASHED == '1' }}
       with:
         name: core-packet-verification-low-power
         path: |
-          ./build/openthread-simulation-1.2/examples/apps/cli/ot-cli-*
-          ./build/openthread-simulation-1.1/examples/apps/cli/ot-cli-*
           ./ot-core-dump/*
-          ./so-lib/*
     - name: Generate Coverage
       run: |
         ./script/test generate_coverage gcc
@@ -159,6 +170,8 @@
       THREAD_VERSION: 1.2
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
@@ -199,6 +212,8 @@
       VIRTUAL_TIME: 0
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo apt-get --no-install-recommends install -y expect ninja-build lcov socat
@@ -207,20 +222,18 @@
         ulimit -c unlimited
         ./script/test prepare_coredump_upload
         OT_OPTIONS=-DOT_READLINE=OFF OT_NODE_TYPE=rcp ./script/test build expect
-    - name: Copy Shared Libraries
+    - name: Check Crash
       if: ${{ failure() }}
       run: |
-          ./script/test copy_so_lib
+          CRASHED=$(./script/test check_crash | tail -1)
+          [[ $CRASHED -eq "1" ]] && echo "Crashed!" || echo "Not crashed."
+          echo "CRASHED=$CRASHED" >> $GITHUB_ENV
     - uses: actions/upload-artifact@v2
-      if: ${{ failure() }}
+      if: ${{ failure() && env.CRASHED == '1' }}
       with:
         name: core-expect-1-2
         path: |
-          ./build/openthread-simulation-1.2/examples/apps/cli/ot-cli-mtd
-          ./build/openthread-simulation-1.2/examples/apps/ncp/ot-rcp
-          ./build/openthread-posix-1.2/src/posix/ot-cli
           ./ot-core-dump/*
-          ./so-lib/*
     - name: Generate Coverage
       run: |
         ./script/test generate_coverage gcc
@@ -229,59 +242,62 @@
         name: cov-expects
         path: tmp/coverage.info
 
-  thread-1-2-backbone:
-    runs-on: ubuntu-18.04
+  thread-1-2-posix:
+    runs-on: ubuntu-20.04
     env:
-      REFERENCE_DEVICE: 1
-      VIRTUAL_TIME: 0
-      PACKET_VERIFICATION: 1
-      THREAD_VERSION: 1.2
-      INTER_OP: 1
       COVERAGE: 1
-      MULTIPLY: 1
       PYTHONUNBUFFERED: 1
-      VERBOSE: 1
+      READLINE: readline
+      THREAD_VERSION: 1.2
+      OT_NODE_TYPE: rcp
+      USE_MTD: 1
+      VIRTUAL_TIME: 1
+      INTER_OP: 1
     steps:
     - uses: actions/checkout@v2
-    - name: Build OTBR Docker
-      env:
-        GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
-      run: |
-        ./script/test build_otbr_docker
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo rm /etc/apt/sources.list.d/* && sudo apt-get update
-        sudo apt-get --no-install-recommends install -y python3-setuptools python3-wheel ninja-build socat lcov
+        sudo apt-get --no-install-recommends install -y libreadline6-dev g++-multilib ninja-build python3-setuptools python3-wheel llvm lcov
         python3 -m pip install -r tests/scripts/thread-cert/requirements.txt
     - name: Build
       run: |
         ./script/test build
-    - name: Get Thread-Wireshark
-      run: |
-        ./script/test get_thread_wireshark
     - name: Run
       run: |
-        export CI_ENV="$(bash <(curl -s https://codecov.io/env)) -e GITHUB_ACTIONS -e COVERAGE"
-        echo "CI_ENV=${CI_ENV}"
-        sudo -E ./script/test cert_suite ./tests/scripts/thread-cert/backbone/*.py || (sudo chmod a+r *.log *.json *.pcap && false)
-    - uses: actions/upload-artifact@v2
-      with:
-        name: cov-thread-1-2-backbone-docker
-        path: /tmp/coverage/
+        ulimit -c unlimited
+        ./script/test prepare_coredump_upload
+        ./script/test cert tests/scripts/thread-cert/v1_2_LowPower_5_3_01_SSEDAttachment.py
+        ./script/test cert tests/scripts/thread-cert/v1_2_LowPower_6_1_07_PreferringARouterOverAReed.py
+        ./script/test cert tests/scripts/thread-cert/v1_2_router_5_1_1.py
+        ./script/test cert tests/scripts/thread-cert/v1_2_test_csl_transmission.py
+        ./script/test cert tests/scripts/thread-cert/v1_2_test_enhanced_frame_pending.py
+        ./script/test cert tests/scripts/thread-cert/v1_2_test_parent_selection.py
+    - name: Check Crash
+      if: ${{ failure() }}
+      run: |
+          CRASHED=$(./script/test check_crash | tail -1)
+          [[ $CRASHED -eq "1" ]] && echo "Crashed!" || echo "Not crashed."
+          echo "CRASHED=$CRASHED" >> $GITHUB_ENV
     - uses: actions/upload-artifact@v2
       if: ${{ failure() }}
       with:
-        name: thread-1-2-backbone-results
+        name: thread-1-2-posix-pcaps
+        path: "*.pcap"
+    - uses: actions/upload-artifact@v2
+      if: ${{ failure() && env.CRASHED == '1' }}
+      with:
+        name: core-thread-1-2-posix
         path: |
-          *.pcap
-          *.json
-          *.log
+            ./ot-core-dump/*
     - name: Generate Coverage
       run: |
         ./script/test generate_coverage gcc
     - uses: actions/upload-artifact@v2
       with:
-        name: cov-thread-1-2-backbone
+        name: cov-thread-1-2-posix
         path: tmp/coverage.info
 
   upload-coverage:
@@ -290,10 +306,12 @@
     - packet-verification-low-power
     - packet-verification-1-1-on-1-2
     - expects
-    - thread-1-2-backbone
+    - thread-1-2-posix
     runs-on: ubuntu-20.04
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo apt-get --no-install-recommends install -y lcov
diff --git a/.github/workflows/size.yml b/.github/workflows/size.yml
index c498589..95c67cf 100644
--- a/.github/workflows/size.yml
+++ b/.github/workflows/size.yml
@@ -38,7 +38,7 @@
     - uses: rokroskar/workflow-run-cleanup-action@master
       env:
         GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
-      if: "github.ref != 'refs/heads/master'"
+      if: "github.ref != 'refs/heads/main'"
 
   size-report:
     runs-on: ubuntu-20.04
@@ -53,6 +53,7 @@
       env:
         OT_BASE_BRANCH: "${{ github.base_ref }}"
         SIZE_REPORT_URL: "https://openthread-size-report.glitch.me/size-report/1354027"
+        GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
       run: |
         export PATH=$PATH:$HOME/.local/bin
         ./script/check-size
diff --git a/.github/workflows/toranj.yml b/.github/workflows/toranj.yml
index 6646312..4536cd0 100644
--- a/.github/workflows/toranj.yml
+++ b/.github/workflows/toranj.yml
@@ -38,7 +38,7 @@
     - uses: rokroskar/workflow-run-cleanup-action@master
       env:
         GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
-      if: "github.ref != 'refs/heads/master'"
+      if: "github.ref != 'refs/heads/main'"
 
   toranj-ncp:
     runs-on: ubuntu-18.04
@@ -47,6 +47,8 @@
       TORANJ_RADIO : 15.4
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       env:
         GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
@@ -85,6 +87,8 @@
       TORANJ_RADIO : 15.4
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       env:
         GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
@@ -223,6 +227,8 @@
     runs-on: ubuntu-18.04
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Bootstrap
       run: |
         sudo apt-get --no-install-recommends install -y lcov
diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml
index 0dcf05d..37b110c 100644
--- a/.github/workflows/version.yml
+++ b/.github/workflows/version.yml
@@ -37,12 +37,14 @@
     - uses: rokroskar/workflow-run-cleanup-action@master
       env:
         GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
-      if: "github.ref != 'refs/heads/master'"
+      if: "github.ref != 'refs/heads/main'"
 
   api-version:
     runs-on: ubuntu-20.04
     steps:
     - uses: actions/checkout@v2
+      with:
+        submodules: true
     - name: Check
       run: |
         script/check-api-version
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..c01f63e
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "third_party/Qorvo/repo"]
+	path = third_party/Qorvo/repo
+	url = https://github.com/Qorvo/qpg-openthread.git
diff --git a/Android.mk b/Android.mk
index 04ee056..3c303af 100644
--- a/Android.mk
+++ b/Android.mk
@@ -28,6 +28,8 @@
 
 LOCAL_PATH := $(call my-dir)
 
+ifeq ($(OPENTHREAD_ENABLE_ANDROID_MK),1)
+
 OPENTHREAD_DEFAULT_VERSION := $(shell cat $(LOCAL_PATH)/.default-version)
 OPENTHREAD_SOURCE_VERSION := $(shell git -C $(LOCAL_PATH) describe --always --match "[0-9].*" 2> /dev/null)
 
@@ -43,7 +45,6 @@
     -DOPENTHREAD_CONFIG_MAC_FILTER_ENABLE=1                         \
     -DOPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE=1                      \
     -DOPENTHREAD_FTD=1                                              \
-    -DOPENTHREAD_POSIX=1                                            \
     -DOPENTHREAD_SPINEL_CONFIG_OPENTHREAD_MESSAGE_ENABLE=1          \
     $(NULL)
 
@@ -145,14 +146,24 @@
     -pedantic-errors                                                           \
     $(NULL)
 
+ifeq ($(ANDROID_NDK),1)
+LOCAL_SHARED_LIBRARIES := libcutils
+
+LOCAL_CFLAGS                                             += \
+    -DOPENTHREAD_ENABLE_ANDROID_NDK=1                       \
+    $(NULL)
+endif
+
 LOCAL_SRC_FILES                                          := \
     src/core/api/backbone_router_api.cpp                    \
     src/core/api/backbone_router_ftd_api.cpp                \
+    src/core/api/border_agent_api.cpp                       \
     src/core/api/border_router_api.cpp                      \
     src/core/api/channel_manager_api.cpp                    \
     src/core/api/channel_monitor_api.cpp                    \
     src/core/api/child_supervision_api.cpp                  \
     src/core/api/coap_api.cpp                               \
+    src/core/api/coap_secure_api.cpp                        \
     src/core/api/commissioner_api.cpp                       \
     src/core/api/crypto_api.cpp                             \
     src/core/api/dataset_api.cpp                            \
@@ -160,6 +171,9 @@
     src/core/api/dataset_updater_api.cpp                    \
     src/core/api/diags_api.cpp                              \
     src/core/api/dns_api.cpp                                \
+    src/core/api/entropy_api.cpp                            \
+    src/core/api/error_api.cpp                              \
+    src/core/api/heap_api.cpp                               \
     src/core/api/icmp6_api.cpp                              \
     src/core/api/instance_api.cpp                           \
     src/core/api/ip6_api.cpp                                \
@@ -173,9 +187,14 @@
     src/core/api/multi_radio_api.cpp                        \
     src/core/api/netdata_api.cpp                            \
     src/core/api/netdiag_api.cpp                            \
+    src/core/api/network_time_api.cpp                       \
+    src/core/api/ping_sender_api.cpp                        \
     src/core/api/random_crypto_api.cpp                      \
     src/core/api/random_noncrypto_api.cpp                   \
     src/core/api/server_api.cpp                             \
+    src/core/api/sntp_api.cpp                               \
+    src/core/api/srp_client_api.cpp                         \
+    src/core/api/srp_server_api.cpp                         \
     src/core/api/tasklet_api.cpp                            \
     src/core/api/thread_api.cpp                             \
     src/core/api/thread_ftd_api.cpp                         \
@@ -186,10 +205,14 @@
     src/core/backbone_router/bbr_manager.cpp                \
     src/core/backbone_router/multicast_listeners_table.cpp  \
     src/core/backbone_router/ndproxy_table.cpp              \
+    src/core/border_router/infra_if_platform.cpp            \
+    src/core/border_router/router_advertisement.cpp         \
+    src/core/border_router/routing_manager.cpp              \
     src/core/coap/coap.cpp                                  \
     src/core/coap/coap_message.cpp                          \
     src/core/coap/coap_secure.cpp                           \
     src/core/common/crc16.cpp                               \
+    src/core/common/error.cpp                               \
     src/core/common/instance.cpp                            \
     src/core/common/logging.cpp                             \
     src/core/common/message.cpp                             \
@@ -204,6 +227,7 @@
     src/core/common/trickle_timer.cpp                       \
     src/core/crypto/aes_ccm.cpp                             \
     src/core/crypto/aes_ecb.cpp                             \
+    src/core/crypto/ecdsa.cpp                               \
     src/core/crypto/hkdf_sha256.cpp                         \
     src/core/crypto/hmac_sha256.cpp                         \
     src/core/crypto/mbedtls.cpp                             \
@@ -213,6 +237,7 @@
     src/core/mac/channel_mask.cpp                           \
     src/core/mac/data_poll_handler.cpp                      \
     src/core/mac/data_poll_sender.cpp                       \
+    src/core/mac/link_raw.cpp                               \
     src/core/mac/mac.cpp                                    \
     src/core/mac/mac_filter.cpp                             \
     src/core/mac/mac_frame.cpp                              \
@@ -227,6 +252,7 @@
     src/core/meshcop/dataset_local.cpp                      \
     src/core/meshcop/dataset_manager.cpp                    \
     src/core/meshcop/dataset_manager_ftd.cpp                \
+    src/core/meshcop/dataset_updater.cpp                    \
     src/core/meshcop/dtls.cpp                               \
     src/core/meshcop/energy_scan_client.cpp                 \
     src/core/meshcop/joiner.cpp                             \
@@ -240,7 +266,8 @@
     src/core/net/dhcp6_client.cpp                           \
     src/core/net/dhcp6_server.cpp                           \
     src/core/net/dns_client.cpp                             \
-    src/core/net/dns_headers.cpp                            \
+    src/core/net/dns_types.cpp                              \
+    src/core/net/dnssd_server.cpp                           \
     src/core/net/icmp6.cpp                                  \
     src/core/net/ip6.cpp                                    \
     src/core/net/ip6_address.cpp                            \
@@ -248,6 +275,10 @@
     src/core/net/ip6_headers.cpp                            \
     src/core/net/ip6_mpl.cpp                                \
     src/core/net/netif.cpp                                  \
+    src/core/net/sntp_client.cpp                            \
+    src/core/net/socket.cpp                                 \
+    src/core/net/srp_client.cpp                             \
+    src/core/net/srp_server.cpp                             \
     src/core/net/udp6.cpp                                   \
     src/core/radio/radio.cpp                                \
     src/core/radio/radio_callbacks.cpp                      \
@@ -281,23 +312,27 @@
     src/core/thread/network_data_leader_ftd.cpp             \
     src/core/thread/network_data_local.cpp                  \
     src/core/thread/network_data_notifier.cpp               \
+    src/core/thread/network_data_service.cpp                \
     src/core/thread/network_diagnostic.cpp                  \
     src/core/thread/panid_query_server.cpp                  \
     src/core/thread/radio_selector.cpp                      \
     src/core/thread/router_table.cpp                        \
     src/core/thread/src_match_controller.cpp                \
     src/core/thread/thread_netif.cpp                        \
+    src/core/thread/time_sync_service.cpp                   \
     src/core/thread/tmf.cpp                                 \
     src/core/thread/topology.cpp                            \
     src/core/thread/uri_paths.cpp                           \
     src/core/utils/channel_manager.cpp                      \
     src/core/utils/channel_monitor.cpp                      \
     src/core/utils/child_supervision.cpp                    \
-    src/core/utils/dataset_updater.cpp                      \
+    src/core/utils/flash.cpp                                \
     src/core/utils/heap.cpp                                 \
     src/core/utils/jam_detector.cpp                         \
     src/core/utils/lookup_table.cpp                         \
+    src/core/utils/otns.cpp                                 \
     src/core/utils/parse_cmdline.cpp                        \
+    src/core/utils/ping_sender.cpp                          \
     src/core/utils/slaac_address.cpp                        \
     src/lib/hdlc/hdlc.cpp                                   \
     src/lib/platform/exit_code.c                            \
@@ -307,9 +342,12 @@
     src/lib/url/url.cpp                                     \
     src/posix/platform/alarm.cpp                            \
     src/posix/platform/backbone.cpp                         \
+    src/posix/platform/daemon.cpp                           \
     src/posix/platform/entropy.cpp                          \
     src/posix/platform/hdlc_interface.cpp                   \
+    src/posix/platform/infra_if.cpp                         \
     src/posix/platform/logging.cpp                          \
+    src/posix/platform/memory.cpp                           \
     src/posix/platform/misc.cpp                             \
     src/posix/platform/multicast_routing.cpp                \
     src/posix/platform/netif.cpp                            \
@@ -318,7 +356,6 @@
     src/posix/platform/settings.cpp                         \
     src/posix/platform/spi_interface.cpp                    \
     src/posix/platform/system.cpp                           \
-    src/posix/platform/uart.cpp                             \
     src/posix/platform/udp.cpp                              \
     third_party/mbedtls/repo/library/aes.c                  \
     third_party/mbedtls/repo/library/asn1parse.c            \
@@ -351,9 +388,9 @@
     third_party/mbedtls/repo/library/platform.c             \
     third_party/mbedtls/repo/library/platform_util.c        \
     third_party/mbedtls/repo/library/sha256.c               \
-    third_party/mbedtls/repo/library/ssl_cookie.c           \
     third_party/mbedtls/repo/library/ssl_ciphersuites.c     \
     third_party/mbedtls/repo/library/ssl_cli.c              \
+    third_party/mbedtls/repo/library/ssl_cookie.c           \
     third_party/mbedtls/repo/library/ssl_srv.c              \
     third_party/mbedtls/repo/library/ssl_ticket.c           \
     third_party/mbedtls/repo/library/ssl_tls.c              \
@@ -385,7 +422,6 @@
 LOCAL_CFLAGS                                                                := \
     $(OPENTHREAD_PUBLIC_CFLAGS)                                                \
     $(OPENTHREAD_PRIVATE_CFLAGS)                                               \
-    -DOPENTHREAD_CONFIG_UART_CLI_RAW=1                                         \
     $(OPENTHREAD_PROJECT_CFLAGS)                                               \
     $(NULL)
 
@@ -399,11 +435,11 @@
     src/cli/cli_coap.cpp                      \
     src/cli/cli_coap_secure.cpp               \
     src/cli/cli_commissioner.cpp              \
-    src/cli/cli_console.cpp                   \
     src/cli/cli_dataset.cpp                   \
     src/cli/cli_joiner.cpp                    \
     src/cli/cli_network_data.cpp              \
-    src/cli/cli_uart.cpp                      \
+    src/cli/cli_srp_client.cpp                \
+    src/cli/cli_srp_server.cpp                \
     src/cli/cli_udp.cpp                       \
     $(NULL)
 
@@ -414,6 +450,10 @@
 LOCAL_MODULE := ot-cli
 LOCAL_MODULE_TAGS := eng
 
+ifneq ($(ANDROID_NDK),1)
+LOCAL_SHARED_LIBRARIES := libcutils
+endif
+
 LOCAL_C_INCLUDES                                         := \
     $(OPENTHREAD_PROJECT_INCLUDES)                          \
     $(LOCAL_PATH)/include                                   \
@@ -443,93 +483,13 @@
     -lutil
 
 LOCAL_SRC_FILES                            := \
+    src/posix/cli.cpp                         \
     src/posix/main.c                          \
     $(NULL)
 
 LOCAL_STATIC_LIBRARIES = libopenthread-cli ot-core
 include $(BUILD_EXECUTABLE)
 
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := libopenthread-ncp
-LOCAL_MODULE_TAGS := eng
-
-LOCAL_C_INCLUDES                                         := \
-    $(OPENTHREAD_PROJECT_INCLUDES)                          \
-    $(LOCAL_PATH)/include                                   \
-    $(LOCAL_PATH)/src                                       \
-    $(LOCAL_PATH)/src/core                                  \
-    $(LOCAL_PATH)/src/ncp                                   \
-    $(LOCAL_PATH)/src/posix/platform                        \
-    $(LOCAL_PATH)/src/posix/platform/include                \
-    $(LOCAL_PATH)/third_party/mbedtls                       \
-    $(LOCAL_PATH)/third_party/mbedtls/repo/include          \
-    $(NULL)
-
-LOCAL_CFLAGS                                                                := \
-    $(OPENTHREAD_PUBLIC_CFLAGS)                                                \
-    $(OPENTHREAD_PRIVATE_CFLAGS)                                               \
-    $(OPENTHREAD_PROJECT_CFLAGS)                                               \
-    $(NULL)
-
-LOCAL_CPPFLAGS                                                              := \
-    -std=c++11                                                                 \
-    -pedantic-errors                                                           \
-    $(NULL)
-
-LOCAL_SRC_FILES                            := \
-    src/lib/spinel/spinel_buffer.cpp          \
-    src/ncp/changed_props_set.cpp             \
-    src/ncp/ncp_base.cpp                      \
-    src/ncp/ncp_base_mtd.cpp                  \
-    src/ncp/ncp_base_ftd.cpp                  \
-    src/ncp/ncp_base_dispatcher.cpp           \
-    src/ncp/ncp_uart.cpp                      \
-    $(NULL)
-
-include $(BUILD_STATIC_LIBRARY)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := ot-ncp
-LOCAL_MODULE_TAGS := eng
-
-LOCAL_C_INCLUDES                                         := \
-    $(OPENTHREAD_PROJECT_INCLUDES)                          \
-    $(LOCAL_PATH)/include                                   \
-    $(LOCAL_PATH)/src                                       \
-    $(LOCAL_PATH)/src/core                                  \
-    $(LOCAL_PATH)/src/ncp                                   \
-    $(LOCAL_PATH)/src/posix/platform                        \
-    $(LOCAL_PATH)/src/posix/platform/include                \
-    $(LOCAL_PATH)/third_party/mbedtls                       \
-    $(LOCAL_PATH)/third_party/mbedtls/repo/include          \
-    $(NULL)
-
-LOCAL_CFLAGS                                                                := \
-    $(OPENTHREAD_PUBLIC_CFLAGS)                                                \
-    $(OPENTHREAD_PRIVATE_CFLAGS)                                               \
-    -DOPENTHREAD_POSIX_APP_TYPE=OT_POSIX_APP_TYPE_NCP                          \
-    $(OPENTHREAD_PROJECT_CFLAGS)                                               \
-    $(NULL)
-
-LOCAL_CPPFLAGS                                                              := \
-    -std=c++11                                                                 \
-    -pedantic-errors                                                           \
-    $(NULL)
-
-LOCAL_SRC_FILES                            := \
-    src/posix/main.c                          \
-    $(NULL)
-
-LOCAL_LDLIBS                               := \
-    -lrt                                      \
-    -lutil
-
-LOCAL_STATIC_LIBRARIES = libopenthread-ncp ot-core
-
-include $(BUILD_EXECUTABLE)
-
 ifeq ($(USE_OTBR_DAEMON), 1)
 include $(CLEAR_VARS)
 
@@ -564,3 +524,5 @@
 ifneq ($(OPENTHREAD_PROJECT_ANDROID_MK),)
 include $(OPENTHREAD_PROJECT_ANDROID_MK)
 endif
+
+endif # ($(OPENTHREAD_ENABLE_ANDROID_MK),1)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b13e96a..2e7ae4a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -37,6 +37,7 @@
 option(OT_BUILD_EXECUTABLES "Build executables" ON)
 option(OT_COVERAGE "enable coverage" OFF)
 set(OT_EXTERNAL_MBEDTLS "" CACHE STRING "Specify external mbedtls library")
+option(OT_MBEDTLS_THREADING "enable mbedtls threading" OFF)
 
 add_library(ot-config INTERFACE)
 
@@ -57,9 +58,16 @@
 include("${PROJECT_SOURCE_DIR}/etc/cmake/functions.cmake")
 
 if(NOT CMAKE_BUILD_TYPE)
-    set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "default build type: Debug" FORCE)
+    # Check if this is a top-level CMake.
+    # If it is not, do not set the CMAKE_BUILD_TYPE because OpenThread is a part of something bigger.
+    if ("${CMAKE_PROJECT_NAME}" STREQUAL "openthread")
+        set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "default build type: Debug" FORCE)
+    endif ()
 endif()
-message(STATUS "CMake build type: ${CMAKE_BUILD_TYPE}")
+
+if (CMAKE_BUILD_TYPE)
+    message(STATUS "OpenThread CMake build type: ${CMAKE_BUILD_TYPE}")
+endif ()
 
 if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_C_COMPILER_ID MATCHES "(Apple)?[Cc]lang")
     option(OT_COMPILE_WARNING_AS_ERROR "whether to include -Werror -pedantic-errors with gcc-compatible compilers")
@@ -91,7 +99,7 @@
     "PACKAGE_VERSION=\"${OT_VERSION}\""
 )
 
-set(OT_THREAD_VERSION "1.1" CACHE STRING "Thread version chosen by the user at configure time")
+set(OT_THREAD_VERSION "1.2" CACHE STRING "Thread version chosen by the user at configure time")
 set_property(CACHE OT_THREAD_VERSION PROPERTY STRINGS "1.1" "1.2")
 if(${OT_THREAD_VERSION} EQUAL "1.1")
     target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_THREAD_VERSION=OT_THREAD_VERSION_1_1")
@@ -159,7 +167,13 @@
     add_subdirectory("${PROJECT_SOURCE_DIR}/src/posix/platform")
 elseif(OT_PLATFORM STREQUAL "external")
     # skip in this case
-elseif(OT_PLATFORM)
+elseif(OT_PLATFORM MATCHES "^nrf*")
+    target_include_directories(ot-config INTERFACE ${PROJECT_SOURCE_DIR}/examples/platforms/nrf528xx/${OT_PLATFORM})
+    add_subdirectory("${PROJECT_SOURCE_DIR}/examples/platforms/nrf528xx")
+elseif(OT_PLATFORM MATCHES "^efr*")
+    target_include_directories(ot-config INTERFACE ${PROJECT_SOURCE_DIR}/examples/platforms/efr32/${OT_PLATFORM})
+    add_subdirectory("${PROJECT_SOURCE_DIR}/examples/platforms/efr32")
+else()
     target_include_directories(ot-config INTERFACE ${PROJECT_SOURCE_DIR}/examples/platforms/${OT_PLATFORM})
     add_subdirectory("${PROJECT_SOURCE_DIR}/examples/platforms/${OT_PLATFORM}")
 endif()
@@ -186,7 +200,12 @@
 
 if(OT_PLATFORM STREQUAL "simulation")
     enable_testing()
-    add_subdirectory(tests)
 endif()
 
-add_custom_target(print-ot-config ALL COMMAND echo -e "$<JOIN:$<TARGET_PROPERTY:ot-config,INTERFACE_COMPILE_DEFINITIONS>,\"\\n\">")
+add_subdirectory(tests)
+
+add_custom_target(print-ot-config ALL
+                  COMMAND ${CMAKE_COMMAND}
+                  -DLIST="$<TARGET_PROPERTY:ot-config,INTERFACE_COMPILE_DEFINITIONS>"
+                  -P ${PROJECT_SOURCE_DIR}/etc/cmake/print.cmake
+)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 6ead4f6..c33dbad 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -63,7 +63,7 @@
 
 ```bash
 # Create a working branch for your new feature
-git branch --track <branch-name> origin/master
+git branch --track <branch-name> origin/main
 
 # Checkout the branch
 git checkout <branch-name>
@@ -85,16 +85,16 @@
 
 Prior to submitting your pull request, you might want to do a few things to clean up your branch and make it as simple as possible for the original repo's maintainer to test, accept, and merge your work.
 
-If any commits have been made to the upstream master branch, you should rebase your development branch so that merging it will be a simple fast-forward that won't require any conflict resolution work.
+If any commits have been made to the upstream main branch, you should rebase your development branch so that merging it will be a simple fast-forward that won't require any conflict resolution work.
 
 ```bash
-# Fetch upstream master and merge with your repo's master branch
-git checkout master
-git pull upstream master
+# Fetch upstream main and merge with your repo's main branch
+git checkout main
+git pull upstream main
 
 # If there were any new commits, rebase your development branch
 git checkout <branch-name>
-git rebase master
+git rebase main
 ```
 
 Now, it may be desirable to squash some of your smaller commits down into a small number of larger more cohesive commits. You can do this with an interactive rebase:
@@ -102,7 +102,7 @@
 ```bash
 # Rebase all commits on your development branch
 git checkout
-git rebase -i master
+git rebase -i main
 ```
 
 This will open up a text editor where you can specify which commits to squash.
diff --git a/README.md b/README.md
index 09f3066..7b1f542 100644
--- a/README.md
+++ b/README.md
@@ -17,16 +17,16 @@
 [thread]: http://threadgroup.org/technology/ourtechnology
 [ot-repo]: https://github.com/openthread/openthread
 [ot-logo]: doc/images/openthread_logo.png
-[ot-gh-action-build]: https://github.com/openthread/openthread/actions?query=workflow%3ABuild+branch%3Amaster+event%3Apush
-[ot-gh-action-build-svg]: https://github.com/openthread/openthread/workflows/Build/badge.svg?branch=master&event=push
-[ot-gh-action-simulation]: https://github.com/openthread/openthread/actions?query=workflow%3ASimulation+branch%3Amaster+event%3Apush
-[ot-gh-action-simulation-svg]: https://github.com/openthread/openthread/workflows/Simulation/badge.svg?branch=master&event=push
-[ot-gh-action-docker]: https://github.com/openthread/openthread/actions?query=workflow%3ADocker+branch%3Amaster+event%3Apush
-[ot-gh-action-docker-svg]: https://github.com/openthread/openthread/workflows/Docker/badge.svg?branch=master&event=push
+[ot-gh-action-build]: https://github.com/openthread/openthread/actions?query=workflow%3ABuild+branch%3Amain+event%3Apush
+[ot-gh-action-build-svg]: https://github.com/openthread/openthread/workflows/Build/badge.svg?branch=main&event=push
+[ot-gh-action-simulation]: https://github.com/openthread/openthread/actions?query=workflow%3ASimulation+branch%3Amain+event%3Apush
+[ot-gh-action-simulation-svg]: https://github.com/openthread/openthread/workflows/Simulation/badge.svg?branch=main&event=push
+[ot-gh-action-docker]: https://github.com/openthread/openthread/actions?query=workflow%3ADocker+branch%3Amain+event%3Apush
+[ot-gh-action-docker-svg]: https://github.com/openthread/openthread/workflows/Docker/badge.svg?branch=main&event=push
 [ot-lgtm]: https://lgtm.com/projects/g/openthread/openthread/context:cpp
 [ot-lgtm-svg]: https://img.shields.io/lgtm/grade/cpp/g/openthread/openthread.svg?logo=lgtm&logoWidth=18
 [ot-codecov]: https://codecov.io/gh/openthread/openthread
-[ot-codecov-svg]: https://codecov.io/gh/openthread/openthread/branch/master/graph/badge.svg
+[ot-codecov-svg]: https://codecov.io/gh/openthread/openthread/branch/main/graph/badge.svg
 
 # Who supports OpenThread?
 
@@ -51,9 +51,9 @@
 
 # Contributing
 
-We would love for you to contribute to OpenThread and help make it even better than it is today! See our [Contributing Guidelines](https://github.com/openthread/openthread/blob/master/CONTRIBUTING.md) for more information.
+We would love for you to contribute to OpenThread and help make it even better than it is today! See our [Contributing Guidelines](https://github.com/openthread/openthread/blob/main/CONTRIBUTING.md) for more information.
 
-Contributors are required to abide by our [Code of Conduct](https://github.com/openthread/openthread/blob/master/CODE_OF_CONDUCT.md) and [Coding Conventions and Style Guide](https://github.com/openthread/openthread/blob/master/STYLE_GUIDE.md).
+Contributors are required to abide by our [Code of Conduct](https://github.com/openthread/openthread/blob/main/CODE_OF_CONDUCT.md) and [Coding Conventions and Style Guide](https://github.com/openthread/openthread/blob/main/STYLE_GUIDE.md).
 
 # Versioning
 
@@ -61,7 +61,7 @@
 
 # License
 
-OpenThread is released under the [BSD 3-Clause license](https://github.com/openthread/openthread/blob/master/LICENSE). See the [`LICENSE`](https://github.com/openthread/openthread/blob/master/LICENSE) file for more information.
+OpenThread is released under the [BSD 3-Clause license](https://github.com/openthread/openthread/blob/main/LICENSE). See the [`LICENSE`](https://github.com/openthread/openthread/blob/main/LICENSE) file for more information.
 
 Please only use the OpenThread name and marks when accurately referencing this software distribution. Do not use the marks in a way that suggests you are endorsed by or otherwise affiliated with Nest, Google, or The Thread Group.
 
diff --git a/configure.ac b/configure.ac
index a6ed3be..8682eb0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -781,14 +781,14 @@
 AC_ARG_WITH(examples,
     [AS_HELP_STRING([--with-examples=TARGET],
         [Build example applications for one of: simulation, cc1352, cc2538, cc2650, cc2652, efr32mg1, efr32mg12, efr32mg13, efr32mg21,
-         gp712, jn5189, k32w061, kw41z, nrf52811, nrf52833, nrf52840, qpg6095, samr21 @<:@default=no@:>@.
+         gp712, jn5189, k32w061, kw41z, nrf52811, nrf52833, nrf52840, qpg6095, qpg6100, qpg7015m, samr21 @<:@default=no@:>@.
          Note that building example applications also builds the associated OpenThread platform libraries
          and any third_party libraries needed to support the examples.])],
     [
         case "${with_examples}" in
         no)
             ;;
-        simulation|cc1352|cc2538|cc2650|cc2652|efr32mg1|efr32mg12|efr32mg13|efr32mg21|gp712|jn5189|k32w061|kw41z|nrf52811|nrf52833|nrf52840|qpg6095|samr21)
+        simulation|cc1352|cc2538|cc2650|cc2652|efr32mg1|efr32mg12|efr32mg13|efr32mg21|gp712|jn5189|k32w061|kw41z|nrf52811|nrf52833|nrf52840|qpg6095|qpg6100|qpg7015m|samr21)
             ;;
         *)
             AC_MSG_RESULT(ERROR)
@@ -817,6 +817,8 @@
 AM_CONDITIONAL([OPENTHREAD_EXAMPLES_NRF52833],  [test "${with_examples}" = "nrf52833"])
 AM_CONDITIONAL([OPENTHREAD_EXAMPLES_NRF52840],  [test "${with_examples}" = "nrf52840"])
 AM_CONDITIONAL([OPENTHREAD_EXAMPLES_QPG6095],   [test "${with_examples}" = "qpg6095"])
+AM_CONDITIONAL([OPENTHREAD_EXAMPLES_QPG6100],   [test "${with_examples}" = "qpg6100"])
+AM_CONDITIONAL([OPENTHREAD_EXAMPLES_QPG7015M],  [test "${with_examples}" = "qpg7015m"])
 AM_CONDITIONAL([OPENTHREAD_EXAMPLES_SAMR21],    [test "${with_examples}" = "samr21"])
 
 AM_COND_IF([OPENTHREAD_EXAMPLES_SIMULATION], CPPFLAGS="${CPPFLAGS} -DOPENTHREAD_EXAMPLES_SIMULATION=1", CPPFLAGS="${CPPFLAGS} -DOPENTHREAD_EXAMPLES_SIMULATION=0")
@@ -842,11 +844,11 @@
 AC_ARG_WITH(platform,
     [AS_HELP_STRING([--with-platform=TARGET],
         [Build OpenThread platform libraries for one of: cc1352, cc2538, cc2650, cc2652,
-         efr32mg1, efr32mg12, efr32mg13, efr32mg21, gp712, jn5189, kw41z, nrf52811, nrf52833, nrf52840, posix, qpg6095, samr21, simulation @<:@default=simulation@:>@.])],
+         efr32mg1, efr32mg12, efr32mg13, efr32mg21, gp712, jn5189, kw41z, nrf52811, nrf52833, nrf52840, posix, qpg6095, qpg6100, qpg7015m, samr21, simulation @<:@default=simulation@:>@.])],
     [
         # Make sure the given target is valid.
         case "${with_platform}" in
-        no|cc1352|cc2538|cc2650|cc2652|efr32mg1|efr32mg12|efr32mg13|efr32mg21|gp712|jn5189|kw41z|nrf52811|nrf52833|nrf52840|posix|qpg6095|samr21|simulation)
+        no|cc1352|cc2538|cc2650|cc2652|efr32mg1|efr32mg12|efr32mg13|efr32mg21|gp712|jn5189|kw41z|nrf52811|nrf52833|nrf52840|posix|qpg6095|qpg6100|qpg7015m|samr21|simulation)
             ;;
         *)
             AC_MSG_RESULT(ERROR)
@@ -893,6 +895,8 @@
 AM_CONDITIONAL([OPENTHREAD_PLATFORM_NRF52840],  [test "${with_platform}" = "nrf52840"])
 AM_CONDITIONAL([OPENTHREAD_PLATFORM_POSIX],     [test "${with_platform}" = "posix"])
 AM_CONDITIONAL([OPENTHREAD_PLATFORM_QPG6095],   [test "${with_platform}" = "qpg6095"])
+AM_CONDITIONAL([OPENTHREAD_PLATFORM_QPG6100],   [test "${with_platform}" = "qpg6100"])
+AM_CONDITIONAL([OPENTHREAD_PLATFORM_QPG7015M],  [test "${with_platform}" = "qpg7015m"])
 AM_CONDITIONAL([OPENTHREAD_PLATFORM_SAMR21],    [test "${with_platform}" = "samr21"])
 AM_CONDITIONAL([OPENTHREAD_PLATFORM_SIMULATION],[test "${with_platform}" = "simulation"])
 
@@ -1050,6 +1054,8 @@
 examples/platforms/kw41z/Makefile
 examples/platforms/nrf528xx/Makefile
 examples/platforms/qpg6095/Makefile
+examples/platforms/qpg6100/Makefile
+examples/platforms/qpg7015m/Makefile
 examples/platforms/samr21/Makefile
 examples/platforms/simulation/Makefile
 examples/platforms/utils/Makefile
diff --git a/doc/ot_api_doc.h b/doc/ot_api_doc.h
index 02af682..af7348e 100644
--- a/doc/ot_api_doc.h
+++ b/doc/ot_api_doc.h
@@ -56,6 +56,8 @@
  * @defgroup api-dns                  DNSv6
  * @defgroup api-icmp6                ICMPv6
  * @defgroup api-ip6                  IPv6
+ * @defgroup api-srp                  SRP
+ * @defgroup api-ping-sender          Ping Sender
  * @defgroup api-udp-group            UDP
  *
  * @{
@@ -92,6 +94,7 @@
  * @defgroup api-thread-general       General
  * @brief This module includes functions for all Thread roles.
  * @defgroup api-joiner               Joiner
+ * @defgroup api-operational-dataset  Operational Dataset
  * @defgroup api-thread-router        Router/Leader
  * @brief This module includes functions for Thread Routers and Leaders.
  * @defgroup api-server               Server
diff --git a/doc/site/en/guides/porting/certification-and-readme.md b/doc/site/en/guides/porting/certification-and-readme.md
index 7ecb8c5..217568f 100644
--- a/doc/site/en/guides/porting/certification-and-readme.md
+++ b/doc/site/en/guides/porting/certification-and-readme.md
@@ -23,5 +23,5 @@
 -   Versions of libraries and toolchains used for validation of the port
 
 See the
-[EFR32MG12 README](https://github.com/openthread/openthread/blob/master/examples/platforms/efr32/efr32mg12/README.md)
+[EFR32MG12 README](https://github.com/openthread/openthread/blob/main/examples/platforms/efr32/efr32mg12/README.md)
 for an example.
diff --git a/doc/site/en/guides/porting/implement-advanced-features.md b/doc/site/en/guides/porting/implement-advanced-features.md
index 1880539..70c95c0 100644
--- a/doc/site/en/guides/porting/implement-advanced-features.md
+++ b/doc/site/en/guides/porting/implement-advanced-features.md
@@ -26,7 +26,7 @@
 
 If the radio supports dynamically setting the Frame Pending bit in outgoing
 acknowledgments to SEDs, the drivers must implement the
-[source address match](https://github.com/openthread/openthread/blob/master/include/openthread/platform/radio.h#L288)
+[source address match](https://github.com/openthread/openthread/blob/main/include/openthread/platform/radio.h#L288)
 API to enable this capability. OpenThread uses this API to tell the radio which
 SEDs to set the Frame Pending bit for.
 
@@ -55,7 +55,7 @@
 > Note:  This feature is optional.
 
 mbedTLS defines several macros in the main configuration header file,
-[`mbedtls-config.h`](https://github.com/openthread/openthread/blob/master/third_party/mbedtls/mbedtls-config.h),
+[`mbedtls-config.h`](https://github.com/openthread/openthread/blob/main/third_party/mbedtls/mbedtls-config.h),
 to allow users to enable alternative implementations of AES, SHA1, SHA2, and
 other modules, as well as individual functions for the Elliptic curve
 cryptography (ECC) over GF(p) module. See
@@ -89,7 +89,7 @@
 the user-specific configuration header file for hardware acceleration.
 
 For a complete example user-specific configuration, see the
-[`mbedtls_config_autogen.h`](https://github.com/openthread/openthread/blob/master/examples/platforms/efr32/efr32mg12/crypto/mbedtls_config_autogen.h)
+[`mbedtls_config_autogen.h`](https://github.com/openthread/openthread/blob/main/examples/platforms/efr32/efr32mg12/crypto/mbedtls_config_autogen.h)
 file.
 
 ### AES module
diff --git a/doc/site/en/guides/porting/implement-platform-abstraction-layer-apis.md b/doc/site/en/guides/porting/implement-platform-abstraction-layer-apis.md
index a224bc7..147d4fd 100644
--- a/doc/site/en/guides/porting/implement-platform-abstraction-layer-apis.md
+++ b/doc/site/en/guides/porting/implement-platform-abstraction-layer-apis.md
@@ -36,30 +36,30 @@
 
 API declaration:
 
-[`/openthread/include/openthread/platform/alarm-milli.h`](https://github.com/openthread/openthread/blob/master/include/openthread/platform/alarm-milli.h)
+[`/openthread/include/openthread/platform/alarm-milli.h`](https://github.com/openthread/openthread/blob/main/include/openthread/platform/alarm-milli.h)
 
 The Alarm API provides fundamental timing and alarm services for the upper layer
 timer implementation.
 
 There are two alarm service types,
-[millisecond](https://github.com/openthread/openthread/blob/master/include/openthread/platform/alarm-milli.h)
-and [microsecond](https://github.com/openthread/openthread/blob/master/include/openthread/platform/alarm-micro.h).
+[millisecond](https://github.com/openthread/openthread/blob/main/include/openthread/platform/alarm-milli.h)
+and [microsecond](https://github.com/openthread/openthread/blob/main/include/openthread/platform/alarm-micro.h).
 Millisecond is required for a new hardware platform. Microsecond is optional.
 
-## Step 2: UART  
+## Step 2: UART
 
 > Note: This API is optional.
 
 API declaration:
 
-[`/openthread/include/openthread/platform/uart.h`](https://github.com/openthread/openthread/blob/master/include/openthread/platform/uart.h)
+[`/openthread/examples/platforms/utils/uart.h`](https://github.com/openthread/openthread/blob/main/examples/platforms/utils/uart.h)
 
 The UART API implements fundamental serial port communication via the UART
 interface.
 
 While the OpenThread
-[CLI](https://github.com/openthread/openthread/tree/master/examples/apps/cli)
-and [NCP](https://github.com/openthread/openthread/tree/master/examples/apps/ncp)
+[CLI](https://github.com/openthread/openthread/tree/main/examples/apps/cli)
+and [NCP](https://github.com/openthread/openthread/tree/main/examples/apps/ncp)
 add-ons depend on the UART interface to interact with the host side, UART API
 support is optional. However, even if you do not plan to use these add-ons on
 your new hardware platform example, we highly recommend you add support for a
@@ -79,7 +79,7 @@
 
 API declaration:
 
-[`/openthread/include/openthread/platform/radio.h`](https://github.com/openthread/openthread/blob/master/include/openthread/platform/radio.h)
+[`/openthread/include/openthread/platform/radio.h`](https://github.com/openthread/openthread/blob/main/include/openthread/platform/radio.h)
 
 The Radio API defines all necessary functions called by the upper IEEE 802.15.4
 MAC layer. The Radio chip must be fully compliant with the 2.4GHz IEEE
@@ -101,7 +101,7 @@
 
 API declaration:
 
-[`/openthread/include/openthread/platform/misc.h`](https://github.com/openthread/openthread/blob/master/include/openthread/platform/misc.h)
+[`/openthread/include/openthread/platform/misc.h`](https://github.com/openthread/openthread/blob/main/include/openthread/platform/misc.h)
 
 The Misc/Reset API provides a method to reset the software on the chip, and
 query the reason for last reset.
@@ -112,7 +112,7 @@
 
 API declaration:
 
-[`/openthread/include/openthread/platform/entropy.h`](https://github.com/openthread/openthread/blob/master/include/openthread/platform/entropy.h)
+[`/openthread/include/openthread/platform/entropy.h`](https://github.com/openthread/openthread/blob/main/include/openthread/platform/entropy.h)
 
 The Entropy API provides a true random number generator (TRNG) for the upper
 layer, which is used to maintain security assets for the entire OpenThread
@@ -142,11 +142,11 @@
 
 API declarations:
 
-[`/openthread/include/openthread/platform/flash.h`](https://github.com/openthread/openthread/blob/master/include/openthread/platform/flash.h)
+[`/openthread/include/openthread/platform/flash.h`](https://github.com/openthread/openthread/blob/main/include/openthread/platform/flash.h)
 
 **or**
 
-[`/openthread/include/openthread/platform/settings.h`](https://github.com/openthread/openthread/blob/master/include/openthread/platform/settings.h)
+[`/openthread/include/openthread/platform/settings.h`](https://github.com/openthread/openthread/blob/main/include/openthread/platform/settings.h)
 
 The Non-volatile storage requirement can be satisfied by implementing one of the
 two APIs listed above. The Flash API implements a flash storage driver, while
@@ -175,7 +175,7 @@
 
 API declaration:
 
-[`/openthread/include/openthread/platform/logging.h`](https://github.com/openthread/openthread/blob/master/include/openthread/platform/logging.h)
+[`/openthread/include/openthread/platform/logging.h`](https://github.com/openthread/openthread/blob/main/include/openthread/platform/logging.h)
 
 The Logging API implements OpenThread's logging and debug functionality, with
 multiple levels of debug output available.  This API is optional if you do not
@@ -189,7 +189,7 @@
 
 API declaration:
 
-[`/openthread/examples/platforms/openthread-system.h`](https://github.com/openthread/openthread/blob/master/examples/platforms/openthread-system.h)
+[`/openthread/examples/platforms/openthread-system.h`](https://github.com/openthread/openthread/blob/main/examples/platforms/openthread-system.h)
 
 The System-specific API primarily provides initialization and deinitialization
 operations for the selected hardware platform. This API is not called by the
@@ -199,6 +199,6 @@
 
 Implementation of this API depends on your use case. If you wish to use the
 generated [CLI and NCP applications](https://openthread.io/guides/build#binaries) for an [example
-platform](https://github.com/openthread/openthread/tree/master/examples/platforms),
+platform](https://github.com/openthread/openthread/tree/main/examples/platforms),
 you must implement this API. Otherwise, any API can be implemented to integrate
 the example platform drivers into your system/RTOS.
diff --git a/doc/site/en/guides/porting/index.md b/doc/site/en/guides/porting/index.md
index d721d75..ef97988 100644
--- a/doc/site/en/guides/porting/index.md
+++ b/doc/site/en/guides/porting/index.md
@@ -2,11 +2,11 @@
 
 Porting the OpenThread stack to a new hardware platform consists of five steps:
 
-1.  [Set up the build environment](https://github.com/openthread/openthread/blob/master/doc/site/en/guides/porting/set-up-the-build-environment.md)
-1.  [Implement Platform Abstraction Layer APIs](https://github.com/openthread/openthread/blob/master/doc/site/en/guides/porting/implement-platform-abstraction-layer-apis.md)
-1.  [Implement advanced features (Hardware Abstraction Layer)](https://github.com/openthread/openthread/blob/master/doc/site/en/guides/porting/implement-advanced-features.md)
-1.  [Validate the port](https://github.com/openthread/openthread/blob/master/doc/site/en/guides/porting/validate-the-port.md)
-1.  [Certification and README](https://github.com/openthread/openthread/blob/master/doc/site/en/guides/porting/certification-and-readme.md)
+1.  [Set up the build environment](https://github.com/openthread/openthread/blob/main/doc/site/en/guides/porting/set-up-the-build-environment.md)
+1.  [Implement Platform Abstraction Layer APIs](https://github.com/openthread/openthread/blob/main/doc/site/en/guides/porting/implement-platform-abstraction-layer-apis.md)
+1.  [Implement advanced features (Hardware Abstraction Layer)](https://github.com/openthread/openthread/blob/main/doc/site/en/guides/porting/implement-advanced-features.md)
+1.  [Validate the port](https://github.com/openthread/openthread/blob/main/doc/site/en/guides/porting/validate-the-port.md)
+1.  [Certification and README](https://github.com/openthread/openthread/blob/main/doc/site/en/guides/porting/certification-and-readme.md)
 
 ## Hardware platform requirements
 
diff --git a/doc/site/en/guides/porting/set-up-the-build-environment.md b/doc/site/en/guides/porting/set-up-the-build-environment.md
index 73cc493..f7a318b 100644
--- a/doc/site/en/guides/porting/set-up-the-build-environment.md
+++ b/doc/site/en/guides/porting/set-up-the-build-environment.md
@@ -20,7 +20,7 @@
 conditional compilation in other Makefiles during the pre-compiling phase.
 
 The OpenThread Autoconf script is located at:
-[`/openthread/configure.ac`](https://github.com/openthread/openthread/blob/master/configure.ac)
+[`/openthread/configure.ac`](https://github.com/openthread/openthread/blob/main/configure.ac)
 
 ### Platform example name
 
@@ -106,14 +106,14 @@
 -   `/openthread/examples/platforms/{platform-name}/Makefile.am`
 -   `/openthread/examples/platforms/{platform-name}/Makefile.platform.am`
 
-See [`/examples`](https://github.com/openthread/openthread/tree/master/examples/) for sample implementations of
+See [`/examples`](https://github.com/openthread/openthread/tree/main/examples/) for sample implementations of
 these files.
 
 The following Automake files also need to be updated with your platform
 information:
 
--   [`/openthread/examples/platforms/Makefile.am`](https://github.com/openthread/openthread/blob/master/examples/platforms/Makefile.am)
--   [`/openthread/examples/platforms/Makefile.platform.am`](https://github.com/openthread/openthread/blob/master/examples/platforms/Makefile.platform.am)
+-   [`/openthread/examples/platforms/Makefile.am`](https://github.com/openthread/openthread/blob/main/examples/platforms/Makefile.am)
+-   [`/openthread/examples/platforms/Makefile.platform.am`](https://github.com/openthread/openthread/blob/main/examples/platforms/Makefile.platform.am)
 
 ### Linker script configuration
 
@@ -145,7 +145,7 @@
 ```
 
 Add the platform's linker script configuration to the
-[`/openthread/examples/platforms/Makefile.platform.am`](https://github.com/openthread/openthread/blob/master/examples/platforms/Makefile.platform.am)
+[`/openthread/examples/platforms/Makefile.platform.am`](https://github.com/openthread/openthread/blob/main/examples/platforms/Makefile.platform.am)
 utility Makefile:
 
 ```
@@ -156,7 +156,7 @@
 
 ### Subdirectory configuration
 
-Modify [`/openthread/examples/platforms/Makefile.am`](https://github.com/openthread/openthread/blob/master/examples/platforms/Makefile.platform.am)
+Modify [`/openthread/examples/platforms/Makefile.am`](https://github.com/openthread/openthread/blob/main/examples/platforms/Makefile.platform.am)
 to configure the package subdirectories for the new platform example.
 
 Add the platform subdirectory name in the list for `make dist`, in alphabetical
@@ -214,4 +214,4 @@
 
 > Note: Any non-original derivative code (for example, linker script or
 toolchain startup code) must be contained in
-[`/openthread/third_party`](https://github.com/openthread/openthread/tree/master/third_party).
+[`/openthread/third_party`](https://github.com/openthread/openthread/tree/main/third_party).
diff --git a/etc/cmake/functions.cmake b/etc/cmake/functions.cmake
index 4cb0bef..f2ceff8 100755
--- a/etc/cmake/functions.cmake
+++ b/etc/cmake/functions.cmake
@@ -30,14 +30,18 @@
 function(ot_get_platforms arg_platforms)
     list(APPEND result "NO" "posix" "external")
     set(platforms_dir "${PROJECT_SOURCE_DIR}/examples/platforms")
-    file(GLOB platforms RELATIVE "${platforms_dir}" "${platforms_dir}/*")
+    file(GLOB platforms RELATIVE "${platforms_dir}" "${platforms_dir}/*"
+            RELATIVE "${platforms_dir}/nrf528xx" "${platforms_dir}/nrf528xx/nrf*"
+            RELATIVE "${platforms_dir}/efr32" "${platforms_dir}/efr32/efr32*")
     foreach(platform IN LISTS platforms)
-        if(IS_DIRECTORY "${platforms_dir}/${platform}")
+        if((IS_DIRECTORY "${platforms_dir}/${platform}") OR
+            (IS_DIRECTORY "${platforms_dir}/nrf528xx/${platform}") OR
+            (IS_DIRECTORY "${platforms_dir}/efr32/${platform}"))
             list(APPEND result "${platform}")
         endif()
     endforeach()
 
-    list(REMOVE_ITEM result utils)
+    list(REMOVE_ITEM result utils nrf528xx)
     list(SORT result)
     set(${arg_platforms} "${result}" PARENT_SCOPE)
 endfunction()
diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake
index 22f338b..f4cb897 100644
--- a/etc/cmake/options.cmake
+++ b/etc/cmake/options.cmake
@@ -72,6 +72,11 @@
     target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE=1")
 endif()
 
+option(OT_BORDER_ROUTING "enable (duckhorn) border routing support")
+if(OT_BORDER_ROUTING)
+    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE=1")
+endif()
+
 if(NOT OT_EXTERNAL_MBEDTLS)
     set(OT_MBEDTLS mbedtls)
     target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_ENABLE_BUILTIN_MBEDTLS=1")
@@ -112,6 +117,11 @@
     target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE=1")
 endif()
 
+option(OT_COAP_BLOCK "enable coap block-wise transfer (RFC7959) api support")
+if(OT_COAP_BLOCK)
+    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE=1")
+endif()
+
 option(OT_COAP_OBSERVE "enable coap observe (RFC7641) api support")
 if(OT_COAP_OBSERVE)
     list(APPEND OT_PRIVATE_DEFINES "OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE=1")
@@ -157,11 +167,21 @@
     target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE=1")
 endif()
 
+option(OT_DNSSD_SERVER "enable DNS-SD server support")
+if(OT_DNSSD_SERVER)
+    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE=1")
+endif()
+
 option(OT_ECDSA "enable ECDSA support")
 if(OT_ECDSA)
     target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_ECDSA_ENABLE=1")
 endif()
 
+option(OT_SRP_CLIENT "enable SRP client support")
+if (OT_SRP_CLIENT)
+    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE=1")
+endif()
+
 option(OT_DUA "enable Domain Unicast Address feature for Thread 1.2")
 if(OT_DUA)
     target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_DUA_ENABLE=1")
@@ -237,6 +257,11 @@
     target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE=1")
 endif()
 
+option(OT_PING_SENDER "enable ping sender support" ${OT_APP_CLI})
+if(OT_PING_SENDER)
+    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_PING_SENDER_ENABLE=1")
+endif()
+
 option(OT_PLATFORM_NETIF "enable platform netif support")
 if(OT_PLATFORM_NETIF)
     target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE=1")
@@ -272,11 +297,21 @@
     target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE=1")
 endif()
 
+option(OT_SRP_SERVER "enable SRP server")
+if (OT_SRP_SERVER)
+    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_SRP_SERVER_ENABLE=1")
+endif()
+
 option(OT_TIME_SYNC "enable the time synchronization service feature")
 if(OT_TIME_SYNC)
     target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_TIME_SYNC_ENABLE=1")
 endif()
 
+option(OT_TREL "enable TREL radio link for Thread over Infrastructure feature")
+if (OT_TREL)
+    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE=1")
+endif()
+
 option(OT_UDP_FORWARD "enable UDP forward support")
 if(OT_UDP_FORWARD)
     target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE=1")
@@ -287,23 +322,6 @@
     if(NOT OT_LOG_LEVEL)
         target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_LEVEL=OT_LOG_LEVEL_DEBG")
     endif()
-    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_API=1")
-    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_ARP=1")
-    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_BBR=1")
-    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_CLI=1")
-    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_COAP=1")
-    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_DUA=1")
-    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_ICMP=1")
-    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_IP6=1")
-    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_MAC=1")
-    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_MEM=1")
-    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_MESHCOP=1")
-    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_MLE=1")
-    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_MLR=1")
-    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_NETDATA=1")
-    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_NETDIAG=1")
-    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_PKT_DUMP=1")
-    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_PLATFORM=1")
     target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_PREPEND_LEVEL=1")
     target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_LOG_PREPEND_REGION=1")
 endif()
diff --git a/tests/scripts/expect/cli-anycast.exp b/etc/cmake/print.cmake
similarity index 71%
copy from tests/scripts/expect/cli-anycast.exp
copy to etc/cmake/print.cmake
index 3081a6b..13fcd64 100644
--- a/tests/scripts/expect/cli-anycast.exp
+++ b/etc/cmake/print.cmake
@@ -1,6 +1,5 @@
-#!/usr/bin/expect -f
 #
-#  Copyright (c) 2020, The OpenThread Authors.
+#  Copyright (c) 2021, The OpenThread Authors.
 #  All rights reserved.
 #
 #  Redistribution and use in source and binary forms, with or without
@@ -27,30 +26,17 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
-source "tests/scripts/expect/_common.exp"
 
+# Purpose of this CMake script is to support printing of properties fetched
+# using a generator expression.
+#
+# Depending on the generator in use, Ninja, Makefile, other, it is not possible
+# to always ensure proper new line on all platforms when calling echo.
+# The print.cmake handles this issue by taking a CMake list and prints each item
+# in the list on a new line.
+#
+# This script can be invoked as: `cmake -DLIST="itemA;itemB;..." -P print.cmake`
 
-set spawn_id [spawn_node 1]
-
-send "panid 0xface\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-
-wait_for "state" "leader"
-expect "Done"
-
-send "ping fdde:ad00:beef:0:0:ff:fe00:fc00\n"
-expect "Done"
-expect "16 bytes from "
-expect {
-    "fdde:ad00:beef:0:0:ff:fe00:fc00" abort
-
-    -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})} {}
-
-    timeout abort
-}
-
-dispose
+foreach(item ${LIST})
+  execute_process(COMMAND ${CMAKE_COMMAND} -E echo ${item})
+endforeach()
diff --git a/etc/gn/openthread.gni b/etc/gn/openthread.gni
index e692612..6774c3b 100644
--- a/etc/gn/openthread.gni
+++ b/etc/gn/openthread.gni
@@ -55,9 +55,6 @@
   # Use external mbedtls. If blank, internal mbedtls will be used.
   openthread_external_mbedtls = ""
 
-  # Enable building for POSIX platforms.
-  openthread_posix = current_os == "linux"
-
   # Package name for OpenThread.
   openthread_package_name = "OPENTHREAD"
 
@@ -73,7 +70,7 @@
 if (openthread_enable_core_config_args) {
   declare_args() {
     # Thread version: 1.1, 1.2
-    openthread_config_thread_version = ""
+    openthread_config_thread_version = "1.1"
 
     # Log output: none, debug_uart, app, platform
     openthread_config_log_output = ""
@@ -90,6 +87,9 @@
     # Enable border router support
     openthread_config_border_router_enable = false
 
+    # Enable border routing support
+    openthread_config_border_routing_enable = false
+
     # Enable channel manager support
     openthread_config_channel_manager_enable = false
 
@@ -126,6 +126,9 @@
     # Enable DNS client support
     openthread_config_dns_client_enable = false
 
+    # Enable DNS-SD server support
+    openthread_config_dnssd_server_enable = false
+
     # Enable ECDSA support
     openthread_config_ecdsa_enable = false
 
@@ -195,6 +198,15 @@
     # Enable SNTP Client support
     openthread_config_sntp_client_enable = false
 
+    # Enable SRP Client support
+    openthread_config_srp_client_enable = false
+
+    # Enable SRP Server support
+    openthread_config_srp_server_enable = false
+
+    # Enable ping sender support
+    openthread_config_ping_sender = false
+
     # Enable the time synchronization service feature
     openthread_config_time_sync_enable = false
 
@@ -210,8 +222,8 @@
     # Enable NCP SPI support
     openthread_config_ncp_spi_enable = false
 
-    # Enable NCP UART support
-    openthread_config_ncp_uart_enable = false
+    # Enable NCP HDLC support
+    openthread_config_ncp_hdlc_enable = false
 
     # Enable builtin mbedtls management
     openthread_config_enable_builtin_mbedtls_management =
diff --git a/examples/Makefile-efr32mg1 b/examples/Makefile-efr32mg1
index 4fc7590..0bb0703 100644
--- a/examples/Makefile-efr32mg1
+++ b/examples/Makefile-efr32mg1
@@ -73,15 +73,18 @@
 EFR32_MBEDTLS_CPPFLAGS += -D$(MCU)
 EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/examples/platforms/efr32/src
 EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/examples/platforms/efr32/$(PLATFORM_LOWERCASE)/crypto
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/third_party/mbedtls/configs
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/third_party/mbedtls/include
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/third_party/mbedtls/include/mbedtls
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/third_party/mbedtls/sl_crypto/include
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/CMSIS/Include
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/Device/SiliconLabs/EFR32MG1P/Include
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/emlib/inc
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/radio/rail_lib/chip/efr32/efr32xg1x
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/silicon_labs/silabs_core/memory_manager
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/mbedtls/include
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/mbedtls/include/mbedtls
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/mbedtls/library
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/sl_alt/include
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/sl_mbedtls_support/config
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/sl_mbedtls_support/inc
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/sl_psa_driver/inc
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/CMSIS/Include
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/Device/SiliconLabs/EFR32MG1P/Include
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/emlib/inc
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/radio/rail_lib/chip/efr32/efr32xg1x
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/silicon_labs/silabs_core/memory_manager
 
 CONFIG_FILE_PATH = $(AbsTopSourceDir)/examples/platforms/efr32/
 HAL_CONF_DIR     = $(CONFIG_FILE_PATH)/$(EFR32_PLATFORM)/$(BOARD_LOWERCASE)
diff --git a/examples/Makefile-efr32mg12 b/examples/Makefile-efr32mg12
index 02a8d3b..74fee97 100644
--- a/examples/Makefile-efr32mg12
+++ b/examples/Makefile-efr32mg12
@@ -43,14 +43,14 @@
 PLATFORM_LOWERCASE = $(shell echo $(EFR32_PLATFORM) | tr A-Z a-z)
 BOARD_LOWERCASE = $(shell echo $(BOARD) | tr A-Z a-z)
 
-configure_OPTIONS               = \
-    --enable-cli                  \
-    --enable-ftd                  \
-    --enable-mtd                  \
-    --enable-ncp                  \
-    --enable-radio-only           \
-    --enable-linker-map           \
-    --enable-builtin-mbedtls=no   \
+configure_OPTIONS                              = \
+    --enable-cli                                 \
+    --enable-ftd                                 \
+    --enable-mtd                                 \
+    --enable-ncp                                 \
+    --enable-radio-only                          \
+    --enable-linker-map                          \
+    --enable-builtin-mbedtls=no                  \
     --with-examples=$(EFR32_PLATFORM)            \
     MBEDTLS_CPPFLAGS="$(EFR32_MBEDTLS_CPPFLAGS)" \
     $(NULL)
@@ -91,15 +91,18 @@
 EFR32_MBEDTLS_CPPFLAGS += -D$(MCU)
 EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/examples/platforms/efr32/src
 EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/examples/platforms/efr32/$(PLATFORM_LOWERCASE)/crypto
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/third_party/mbedtls/configs
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/third_party/mbedtls/include
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/third_party/mbedtls/include/mbedtls
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/third_party/mbedtls/sl_crypto/include
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/CMSIS/Include
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/Device/SiliconLabs/EFR32MG12P/Include
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/emlib/inc
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/radio/rail_lib/chip/efr32/efr32xg1x
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/silicon_labs/silabs_core/memory_manager
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/mbedtls/include
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/mbedtls/include/mbedtls
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/mbedtls/library
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/sl_alt/include
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/sl_mbedtls_support/config
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/sl_mbedtls_support/inc
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/sl_psa_driver/inc
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/CMSIS/Include
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/Device/SiliconLabs/EFR32MG12P/Include
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/emlib/inc
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/radio/rail_lib/chip/efr32/efr32xg1x
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/silicon_labs/silabs_core/memory_manager
 
 CONFIG_FILE_PATH = $(AbsTopSourceDir)/examples/platforms/efr32/
 HAL_CONF_DIR     = $(CONFIG_FILE_PATH)/$(EFR32_PLATFORM)/$(BOARD_LOWERCASE)
diff --git a/examples/Makefile-efr32mg13 b/examples/Makefile-efr32mg13
index 00828d9..59b9ec2 100644
--- a/examples/Makefile-efr32mg13
+++ b/examples/Makefile-efr32mg13
@@ -43,14 +43,14 @@
 PLATFORM_LOWERCASE = $(shell echo $(EFR32_PLATFORM) | tr A-Z a-z)
 BOARD_LOWERCASE = $(shell echo $(BOARD) | tr A-Z a-z)
 
-configure_OPTIONS               = \
-    --enable-cli                  \
-    --enable-ftd                  \
-    --enable-mtd                  \
-    --enable-ncp                  \
-    --enable-radio-only           \
-    --enable-linker-map           \
-    --enable-builtin-mbedtls=no   \
+configure_OPTIONS                              = \
+    --enable-cli                                 \
+    --enable-ftd                                 \
+    --enable-mtd                                 \
+    --enable-ncp                                 \
+    --enable-radio-only                          \
+    --enable-linker-map                          \
+    --enable-builtin-mbedtls=no                  \
     --with-examples=$(EFR32_PLATFORM)            \
     MBEDTLS_CPPFLAGS="$(EFR32_MBEDTLS_CPPFLAGS)" \
     $(NULL)
@@ -76,16 +76,19 @@
 EFR32_MBEDTLS_CPPFLAGS  = -DMBEDTLS_CONFIG_FILE='\"mbedtls_config.h\"'
 EFR32_MBEDTLS_CPPFLAGS += -D$(MCU)
 EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/examples/platforms/efr32/src
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/examples/platforms/efr32/efr32mg13/crypto
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/third_party/mbedtls/configs
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/third_party/mbedtls/include
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/third_party/mbedtls/include/mbedtls
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/third_party/mbedtls/sl_crypto/include
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/CMSIS/Include
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/Device/SiliconLabs/EFR32MG13P/Include
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/emlib/inc
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/radio/rail_lib/chip/efr32/efr32xg1x
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/silicon_labs/silabs_core/memory_manager
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/examples/platforms/efr32/$(PLATFORM_LOWERCASE)/crypto
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/mbedtls/include
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/mbedtls/include/mbedtls
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/mbedtls/library
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/sl_alt/include
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/sl_mbedtls_support/config
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/sl_mbedtls_support/inc
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/sl_psa_driver/inc
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/CMSIS/Include
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/Device/SiliconLabs/EFR32MG13P/Include
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/emlib/inc
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/radio/rail_lib/chip/efr32/efr32xg1x
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/silicon_labs/silabs_core/memory_manager
 
 CONFIG_FILE_PATH = $(AbsTopSourceDir)/examples/platforms/efr32/
 HAL_CONF_DIR     = $(CONFIG_FILE_PATH)/$(EFR32_PLATFORM)/$(BOARD_LOWERCASE)
diff --git a/examples/Makefile-efr32mg21 b/examples/Makefile-efr32mg21
index c2987aa..d003e33 100644
--- a/examples/Makefile-efr32mg21
+++ b/examples/Makefile-efr32mg21
@@ -43,14 +43,14 @@
 PLATFORM_LOWERCASE = $(shell echo $(EFR32_PLATFORM) | tr A-Z a-z)
 BOARD_LOWERCASE = $(shell echo $(BOARD) | tr A-Z a-z)
 
-configure_OPTIONS               = \
-    --enable-cli                  \
-    --enable-ftd                  \
-    --enable-mtd                  \
-    --enable-ncp                  \
-    --enable-radio-only           \
-    --enable-linker-map           \
-    --enable-builtin-mbedtls=no   \
+configure_OPTIONS                              = \
+    --enable-cli                                 \
+    --enable-ftd                                 \
+    --enable-mtd                                 \
+    --enable-ncp                                 \
+    --enable-radio-only                          \
+    --enable-linker-map                          \
+    --enable-builtin-mbedtls=no                  \
     --with-examples=$(EFR32_PLATFORM)            \
     MBEDTLS_CPPFLAGS="$(EFR32_MBEDTLS_CPPFLAGS)" \
     $(NULL)
@@ -81,15 +81,20 @@
 EFR32_MBEDTLS_CPPFLAGS += -D$(MCU)
 EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/examples/platforms/efr32/src
 EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/examples/platforms/efr32/$(PLATFORM_LOWERCASE)/crypto
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/third_party/mbedtls/configs
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/third_party/mbedtls/include
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/third_party/mbedtls/include/mbedtls
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/third_party/mbedtls/sl_crypto/include
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/CMSIS/Include
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/Device/SiliconLabs/EFR32MG21/Include
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/emlib/inc
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/radio/rail_lib/chip/efr32/efr32xg2x
-EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.0/util/silicon_labs/silabs_core/memory_manager
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/mbedtls/include
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/mbedtls/include/mbedtls
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/mbedtls/library
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/se_manager/inc
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/se_manager/src
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/sl_alt/include
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/sl_mbedtls_support/config
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/sl_mbedtls_support/inc
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/third_party/crypto/sl_component/sl_psa_driver/inc
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/CMSIS/Include
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/Device/SiliconLabs/EFR32MG21/Include
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/emlib/inc
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/radio/rail_lib/chip/efr32/efr32xg2x
+EFR32_MBEDTLS_CPPFLAGS += -I$(AbsTopSourceDir)/third_party/silabs/gecko_sdk_suite/v3.1/util/silicon_labs/silabs_core/memory_manager
 EFR32_MBEDTLS_CPPFLAGS += -Wno-unused-function
 EFR32_MBEDTLS_CPPFLAGS += -Wno-unused-parameter
 
diff --git a/examples/Makefile-gp712 b/examples/Makefile-gp712
index ad08880..d14efa9 100644
--- a/examples/Makefile-gp712
+++ b/examples/Makefile-gp712
@@ -1,5 +1,5 @@
 #
-#  Copyright (c) 2017, The OpenThread Authors.
+#  Copyright (c) 2019, The OpenThread Authors.
 #  All rights reserved.
 #
 #  Redistribution and use in source and binary forms, with or without
@@ -28,8 +28,10 @@
 
 .NOTPARALLEL:
 
-TARGET        ?= arm-bcm2708hardfp-linux-gnueabi
-PPREFIX        = gp712
+TARGET        ?= arm-linux-gnueabihf
+NAME           = GP712
+PREFIX_UC      = $(NAME)
+PREFIX_LC     := $(shell echo $(PREFIX_UC) | tr A-Z a-z)
 CROSS_COMPILE ?= $(TARGET)-
 
 AR                              = $(CROSS_COMPILE)ar
@@ -45,63 +47,67 @@
 
 BuildJobs                      ?= 9
 
-configure_OPTIONS               = \
-    --enable-cli                  \
-    --enable-ftd                  \
-    --enable-mtd                  \
-    --with-examples=$(PPREFIX)    \
+configure_OPTIONS               =     \
+    --disable-tools                   \
+    --enable-cli                      \
+    --enable-ftd                      \
+    --enable-mtd                      \
+    --enable-ncp                      \
+    --enable-cli-app                  \
+    --enable-radio-only               \
+    --with-ncp-vendor-hook-source=no  \
+    --with-ncp-bus=uart               \
+    --with-examples=$(PREFIX_LC)      \
     $(NULL)
 
-DEFAULT_LOGGING ?= 1
-
 ifeq ($(CLI_LOGGING),1)
 configure_OPTIONS              += --enable-cli-logging
 endif
 
-GP712_CONFIG_FILE_CPPFLAGS  = -DOPENTHREAD_PROJECT_CORE_CONFIG_FILE='\"openthread-core-gp712-config.h\"'
-GP712_CONFIG_FILE_CPPFLAGS += -DOPENTHREAD_CORE_CONFIG_PLATFORM_CHECK_FILE='\"openthread-core-gp712-config-check.h\"'
-GP712_CONFIG_FILE_CPPFLAGS += -I$(PWD)/examples/platforms/gp712/
+$(PREFIX_UC)_CONFIG_FILE_CPPFLAGS  = -DOPENTHREAD_PROJECT_CORE_CONFIG_FILE='\"openthread-core-$(PREFIX_LC)-config.h\"'
+$(PREFIX_UC)_CONFIG_FILE_CPPFLAGS += -DOPENTHREAD_CORE_CONFIG_PLATFORM_CHECK_FILE='\"openthread-core-$(PREFIX_LC)-config-check.h\"'
+$(PREFIX_UC)_CONFIG_FILE_CPPFLAGS += -I$(PWD)/examples/platforms/$(PREFIX_LC)/
 
-COMMONCFLAGS                   := \
-    -fdata-sections               \
-    -ffunction-sections           \
-    -Os                           \
-    -g                            \
-    $(GP712_CONFIG_FILE_CPPFLAGS) \
-    -W                            \
-    -Wall                         \
+COMMONCFLAGS                             := \
+    -fdata-sections                         \
+    -ffunction-sections                     \
+    -Os                                     \
+    $($(PREFIX_UC)_CONFIG_FILE_CPPFLAGS)    \
+    -Wextra                                 \
+    -Wall                                   \
+    -Wno-psabi                              \
     $(NULL)
 
 include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/common-switches.mk
 
-CPPFLAGS                       += \
-    $(COMMONCFLAGS)               \
-    $(target_CPPFLAGS)            \
+CPPFLAGS                                 += \
+    $(COMMONCFLAGS)                         \
+    $(target_CPPFLAGS)                      \
     $(NULL)
 
-CFLAGS                         += \
-    $(COMMONCFLAGS)               \
-    -D_BSD_SOURCE=1               \
-    -D_DEFAULT_SOURCE=1           \
-    $(target_CFLAGS)              \
+CFLAGS                                   += \
+    $(COMMONCFLAGS)                         \
+    -D_BSD_SOURCE=1                         \
+    -D_DEFAULT_SOURCE=1                     \
+    $(target_CFLAGS)                        \
     $(NULL)
 
-CXXFLAGS                       += \
-    $(COMMONCFLAGS)               \
-    $(target_CXXFLAGS)            \
-    -fno-exceptions               \
-    -fno-rtti                     \
+CXXFLAGS                                 += \
+    $(COMMONCFLAGS)                         \
+    $(target_CXXFLAGS)                      \
+    -fno-exceptions                         \
+    -fno-rtti                               \
     $(NULL)
 
-LDFLAGS                        += \
-    $(COMMONCFLAGS)               \
-    $(target_LDFLAGS)             \
-    -Wl,--gc-sections             \
-    -lrt                          \
-    -lpthread                     \
+LDFLAGS                                  += \
+    $(COMMONCFLAGS)                         \
+    $(target_LDFLAGS)                       \
+    -Wl,--gc-sections                       \
+    -Wl,-Map=$(PREFIX_LC).map               \
+    -lrt                                    \
+    -pthread                                \
     $(NULL)
 
-
 ECHO                            := @echo
 MAKE                            := make
 MKDIR_P                         := mkdir -p
@@ -122,15 +128,13 @@
 TopResultDir                    = $(ResultPath)
 AbsTopResultDir                 = $(PWD)/$(TopResultDir)
 
-TargetTuple                     = $(PPREFIX)
+TargetTuple                     = $(PREFIX_LC)
 
 ifndef BuildJobs
 BuildJobs := $(shell getconf _NPROCESSORS_ONLN)
 endif
 JOBSFLAG := -j$(BuildJobs)
 
-PREFIX                         = $(PPREFIX)
-
 #
 # configure-arch <arch>
 #
@@ -139,15 +143,15 @@
 #   arch - The architecture to configure.
 #
 define configure-arch
-$(ECHO) "  CONFIG   $(1)..."
+$(ECHO) "  CONFIG   $(1)"
 (cd $(BuildPath)/$(1) && $(AbsTopSourceDir)/configure \
 INSTALL="$(INSTALL) $(INSTALLFLAGS)" \
 CPP="$(CPP)" CC="$(CC)" CXX="$(CXX)" OBJC="$(OBJC)" OBJCXX="$(OBJCXX)" AR="$(AR)" RANLIB="$(RANLIB)" NM="$(NM)" STRIP="$(STRIP)" CPPFLAGS="$(CPPFLAGS)" CFLAGS="$(CFLAGS)" CXXFLAGS="$(CXXFLAGS)" LDFLAGS="$(LDFLAGS)" \
---prefix=/$(PREFIX) \
+--prefix=/$(TargetTuple) \
 --host=$(TARGET) \
 --target=$(TARGET) \
---exec-prefix=/$(PREFIX) \
---program-prefix=$(PREFIX)- \
+--exec-prefix=/$(TargetTuple) \
+--program-prefix=$(TargetTuple)- \
 $(configure_OPTIONS))
 endef # configure-arch
 
diff --git a/examples/Makefile-nrf52811 b/examples/Makefile-nrf52811
index 317bd9c..6e38b30 100644
--- a/examples/Makefile-nrf52811
+++ b/examples/Makefile-nrf52811
@@ -91,14 +91,13 @@
 # Otherwise, the default serial transport is UART.
 #
 ifndef DISABLE_TRANSPORTS
-configure_OPTIONS              += --enable-ncp
 configure_OPTIONS              += --enable-radio-only
 ifeq ($(NCP_SPI),1)
 COMMONCFLAGS                   += -DSPIS_AS_SERIAL_TRANSPORT=1
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_NCP_SPI_ENABLE=1
 else
 COMMONCFLAGS                   += -DUART_AS_SERIAL_TRANSPORT=1
-COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_NCP_UART_ENABLE=1
+COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1
 configure_OPTIONS              += --enable-cli
 endif # NCP_SPI == 1
 endif # DISABLE_TRANSPORTS
diff --git a/examples/Makefile-nrf52833 b/examples/Makefile-nrf52833
index 5f6ad35..9a5bb53 100644
--- a/examples/Makefile-nrf52833
+++ b/examples/Makefile-nrf52833
@@ -113,7 +113,7 @@
 COMMONCFLAGS                   += -DSPIS_AS_SERIAL_TRANSPORT=1
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_NCP_SPI_ENABLE=1
 else
-COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_NCP_UART_ENABLE=1
+COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1
 configure_OPTIONS              += --enable-cli
 ifeq ($(USB),1)
 COMMONCFLAGS                   += -DUSB_CDC_AS_SERIAL_TRANSPORT=1
diff --git a/examples/Makefile-nrf52840 b/examples/Makefile-nrf52840
index e0b1576..8ea88f3 100644
--- a/examples/Makefile-nrf52840
+++ b/examples/Makefile-nrf52840
@@ -149,7 +149,7 @@
 COMMONCFLAGS                   += -DSPIS_AS_SERIAL_TRANSPORT=1
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_NCP_SPI_ENABLE=1
 else
-COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_NCP_UART_ENABLE=1
+COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1
 configure_OPTIONS              += --enable-cli
 ifeq ($(USB),1)
 COMMONCFLAGS                   += -DUSB_CDC_AS_SERIAL_TRANSPORT=1
diff --git a/examples/Makefile-qpg6095 b/examples/Makefile-qpg6095
index 1b056ad..6354b12 100644
--- a/examples/Makefile-qpg6095
+++ b/examples/Makefile-qpg6095
@@ -27,12 +27,15 @@
 #
 
 .NOTPARALLEL:
+NAME           = QPG6095
+PREFIX_UC      = $(NAME)
+PREFIX_LC     := $(shell echo $(PREFIX_UC) | tr A-Z a-z)
 
 AR                              = arm-none-eabi-ar
-CCAS                            = $(CCPREFIX) arm-none-eabi-gcc
+CCAS                            = arm-none-eabi-gcc
 CPP                             = arm-none-eabi-cpp
-CC                              = $(CCPREFIX) arm-none-eabi-gcc
-CXX                             = $(CCPREFIX) arm-none-eabi-g++
+CC                              = arm-none-eabi-gcc
+CXX                             = arm-none-eabi-g++
 LD                              = arm-none-eabi-ld
 STRIP                           = arm-none-eabi-strip
 NM                              = arm-none-eabi-nm
@@ -42,63 +45,64 @@
 BuildJobs                      ?= 10
 GCCVersion                      = $(shell expr `$(CC) -dumpversion | cut -f1 -d.`)
 
-configure_OPTIONS                                 = \
-    --enable-cli                                    \
-    --enable-ftd                                    \
-    --enable-mtd                                    \
-    --enable-ncp                                    \
-    --enable-radio-only                             \
-    --enable-linker-map                             \
-    --with-examples=qpg6095                         \
-    MBEDTLS_CPPFLAGS="$(QPG6095_MBEDTLS_CPPFLAGS)"  \
+configure_OPTIONS                                    += \
+    --disable-tools                                     \
+    --enable-cli                                        \
+    --enable-ftd                                        \
+    --enable-mtd                                        \
+    --with-examples=$(PREFIX_LC)                        \
+    MBEDTLS_CPPFLAGS="$($(PREFIX_UC)_MBEDTLS_CPPFLAGS)" \
     $(NULL)
 
 TopSourceDir                    := $(dir $(shell readlink $(firstword $(MAKEFILE_LIST))))..
 AbsTopSourceDir                 := $(dir $(realpath $(firstword $(MAKEFILE_LIST))))..
 
-QPG6095_CONFIG_FILE_CPPFLAGS  = -DOPENTHREAD_PROJECT_CORE_CONFIG_FILE='\"openthread-core-qpg6095-config.h\"'
-QPG6095_CONFIG_FILE_CPPFLAGS += -DOPENTHREAD_CORE_CONFIG_PLATFORM_CHECK_FILE='\"openthread-core-qpg6095-config-check.h\"'
-QPG6095_CONFIG_FILE_CPPFLAGS += -I$(PWD)/examples/platforms/qpg6095/
-QPG6095_MBEDTLS_CPPFLAGS      = -DMBEDTLS_CONFIG_FILE='\"mbedtls-config.h\"'
-QPG6095_MBEDTLS_CPPFLAGS     += -DMBEDTLS_USER_CONFIG_FILE='\"qpg6095-mbedtls-config.h\"'
-QPG6095_MBEDTLS_CPPFLAGS     += -I$(PWD)/examples/platforms/qpg6095/crypto
-QPG6095_MBEDTLS_CPPFLAGS     += -I$(PWD)/third_party/mbedtls
-QPG6095_MBEDTLS_CPPFLAGS     += -I$(PWD)/third_party/mbedtls/repo/include
-QPG6095_MBEDTLS_CPPFLAGS     += -I$(PWD)/third_party/mbedtls/repo/include/mbedtls
+$(PREFIX_UC)_CONFIG_FILE_CPPFLAGS  = -DOPENTHREAD_PROJECT_CORE_CONFIG_FILE='\"openthread-core-$(PREFIX_LC)-config.h\"'
+$(PREFIX_UC)_CONFIG_FILE_CPPFLAGS += -DOPENTHREAD_CORE_CONFIG_PLATFORM_CHECK_FILE='\"openthread-core-$(PREFIX_LC)-config-check.h\"'
+$(PREFIX_UC)_CONFIG_FILE_CPPFLAGS += -I$(PWD)/examples/platforms/$(PREFIX_LC)/
+$(PREFIX_UC)_MBEDTLS_CPPFLAGS      = -DMBEDTLS_CONFIG_FILE='\"mbedtls-config.h\"'
+$(PREFIX_UC)_MBEDTLS_CPPFLAGS     += -DMBEDTLS_USER_CONFIG_FILE='\"$(PREFIX_LC)-mbedtls-config.h\"'
+$(PREFIX_UC)_MBEDTLS_CPPFLAGS     += -I$(PWD)/examples/platforms/$(PREFIX_LC)/crypto
+$(PREFIX_UC)_MBEDTLS_CPPFLAGS     += -I$(PWD)/third_party/mbedtls
+$(PREFIX_UC)_MBEDTLS_CPPFLAGS     += -I$(PWD)/third_party/mbedtls/repo/include
+$(PREFIX_UC)_MBEDTLS_CPPFLAGS     += -I$(PWD)/third_party/mbedtls/repo/include/mbedtls
 
-COMMONCFLAGS                     := \
-    -fdata-sections                 \
-    -ffunction-sections             \
-    -Os                             \
-    -g                              \
-    $(QPG6095_CONFIG_FILE_CPPFLAGS) \
+COMMONCFLAGS                                         := \
+    -fdata-sections                                     \
+    -ffunction-sections                                 \
+    -Os                                                 \
+    $($(PREFIX_UC)_CONFIG_FILE_CPPFLAGS)                \
+    -Wall                                               \
+    -Wextra                                             \
     $(NULL)
 
 include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/common-switches.mk
 
-CPPFLAGS                       += \
-    $(COMMONCFLAGS)               \
-    $(target_CPPFLAGS)            \
+CPPFLAGS                                             += \
+    $(COMMONCFLAGS)                                     \
+    $(target_CPPFLAGS)                                  \
     $(NULL)
 
-CFLAGS                         += \
-    $(COMMONCFLAGS)               \
-    $(target_CFLAGS)              \
+CFLAGS                                               += \
+    $(COMMONCFLAGS)                                     \
+    $(target_CFLAGS)                                    \
     $(NULL)
 
-CXXFLAGS                       += \
-    $(COMMONCFLAGS)               \
-    $(target_CXXFLAGS)            \
-    -fno-exceptions               \
-    -fno-rtti                     \
+CXXFLAGS                                             += \
+    $(COMMONCFLAGS)                                     \
+    $(target_CXXFLAGS)                                  \
+    -fno-exceptions                                     \
+    -fno-rtti                                           \
     $(NULL)
 
-LDFLAGS                        += \
-    $(COMMONCFLAGS)               \
-    $(target_LDFLAGS)             \
-    -specs=nano.specs             \
-    -specs=nosys.specs            \
-    -Wl,--gc-sections             \
+LDFLAGS                                              += \
+    $(COMMONCFLAGS)                                     \
+    $(target_LDFLAGS)                                   \
+    -specs=nano.specs                                   \
+    -specs=nosys.specs                                  \
+    -Wl,--gc-sections                                   \
+    -nostdlib                                           \
+    -Wl,-Map=$(PREFIX_LC).map,--cref                    \
     $(NULL)
 
 ECHO                            := @echo
@@ -118,7 +122,7 @@
 TopResultDir                    = $(ResultPath)
 AbsTopResultDir                 = $(PWD)/$(TopResultDir)
 
-TargetTuple                     = qpg6095
+TargetTuple                     = $(PREFIX_LC)
 
 ARCHS                           = cortex-m4
 
@@ -137,13 +141,13 @@
 #   arch - The architecture to configure.
 #
 define configure-arch
-$(ECHO) "  CONFIG   $(TargetTuple)..."
+$(ECHO) "  CONFIG   $(TargetTuple)"
 (cd $(BuildPath)/$(TargetTuple) && $(AbsTopSourceDir)/configure \
 INSTALL="$(INSTALL) $(INSTALLFLAGS)" \
 CPP="$(CPP)" CC="$(CC)" CXX="$(CXX)" CCAS="$(CCAS)" OBJC="$(OBJC)" OBJCXX="$(OBJCXX)" AR="$(AR)" RANLIB="$(RANLIB)" NM="$(NM)" STRIP="$(STRIP)" CPPFLAGS="$(CPPFLAGS)" CFLAGS="$(CFLAGS)" CXXFLAGS="$(CXXFLAGS)" CCASFLAGS="$(CCASFLAGS)" LDFLAGS="$(LDFLAGS)" \
 --host=arm-none-eabi \
---prefix=/ \
---exec-prefix=/$(TargetTuple) \
+--prefix=/$(TargetTuple) \
+--program-prefix=$(TargetTuple)- \
 $(configure_OPTIONS))
 endef # configure-arch
 
@@ -179,8 +183,8 @@
 # @FIXME: get the filenames of the targets of OBJCOPY (now they are hardcoded)
 define hex-arch
 $(ECHO) "  HEX       $(TargetTuple)"
-$(OBJCOPY) -O ihex $(AbsTopResultDir)/$(TargetTuple)/bin/ot-cli-mtd $(AbsTopResultDir)/$(TargetTuple)/bin/ot-cli-mtd.hex
-$(OBJCOPY) -O ihex $(AbsTopResultDir)/$(TargetTuple)/bin/ot-cli-ftd $(AbsTopResultDir)/$(TargetTuple)/bin/ot-cli-ftd.hex
+$(OBJCOPY) -O ihex $(AbsTopResultDir)/$(TargetTuple)/bin/$(TargetTuple)-ot-cli-mtd $(AbsTopResultDir)/$(TargetTuple)/bin/$(TargetTuple)-ot-cli-mtd.hex
+$(OBJCOPY) -O ihex $(AbsTopResultDir)/$(TargetTuple)/bin/$(TargetTuple)-ot-cli-ftd $(AbsTopResultDir)/$(TargetTuple)/bin/$(TargetTuple)-ot-cli-ftd.hex
 endef # hex-arch
 
 #
diff --git a/examples/Makefile-qpg6100 b/examples/Makefile-qpg6100
new file mode 100644
index 0000000..eab6ac6
--- /dev/null
+++ b/examples/Makefile-qpg6100
@@ -0,0 +1,290 @@
+#
+#  Copyright (c) 2019, The OpenThread Authors.
+#  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.
+#
+
+.NOTPARALLEL:
+NAME           = QPG6100
+PREFIX_UC      = $(NAME)
+PREFIX_LC     := $(shell echo $(PREFIX_UC) | tr A-Z a-z)
+
+AR                              = arm-none-eabi-ar
+CCAS                            = arm-none-eabi-gcc
+CPP                             = arm-none-eabi-cpp
+CC                              = arm-none-eabi-gcc
+CXX                             = arm-none-eabi-g++
+LD                              = arm-none-eabi-ld
+STRIP                           = arm-none-eabi-strip
+NM                              = arm-none-eabi-nm
+RANLIB                          = arm-none-eabi-ranlib
+OBJCOPY                         = arm-none-eabi-objcopy
+
+BuildJobs                      ?= 10
+GCCVersion                      = $(shell expr `$(CC) -dumpversion | cut -f1 -d.`)
+
+configure_OPTIONS                                    += \
+    --disable-tools                                     \
+    --enable-cli                                        \
+    --enable-ftd                                        \
+    --enable-mtd                                        \
+    --with-examples=$(PREFIX_LC)                        \
+    MBEDTLS_CPPFLAGS="$($(PREFIX_UC)_MBEDTLS_CPPFLAGS)" \
+    $(NULL)
+
+TopSourceDir                    := $(dir $(shell readlink $(firstword $(MAKEFILE_LIST))))..
+AbsTopSourceDir                 := $(dir $(realpath $(firstword $(MAKEFILE_LIST))))..
+
+$(PREFIX_UC)_CONFIG_FILE_CPPFLAGS  = -DOPENTHREAD_PROJECT_CORE_CONFIG_FILE='\"openthread-core-$(PREFIX_LC)-config.h\"'
+$(PREFIX_UC)_CONFIG_FILE_CPPFLAGS += -DOPENTHREAD_CORE_CONFIG_PLATFORM_CHECK_FILE='\"openthread-core-$(PREFIX_LC)-config-check.h\"'
+$(PREFIX_UC)_CONFIG_FILE_CPPFLAGS += -I$(PWD)/examples/platforms/$(PREFIX_LC)/
+$(PREFIX_UC)_MBEDTLS_CPPFLAGS      = -DMBEDTLS_CONFIG_FILE='\"mbedtls-config.h\"'
+$(PREFIX_UC)_MBEDTLS_CPPFLAGS     += -DMBEDTLS_USER_CONFIG_FILE='\"$(PREFIX_LC)-mbedtls-config.h\"'
+$(PREFIX_UC)_MBEDTLS_CPPFLAGS     += -I$(PWD)/examples/platforms/$(PREFIX_LC)/crypto
+$(PREFIX_UC)_MBEDTLS_CPPFLAGS     += -DQORVO_CRYPTO_ENGINE
+$(PREFIX_UC)_MBEDTLS_CPPFLAGS     += -I$(PWD)/third_party/mbedtls
+$(PREFIX_UC)_MBEDTLS_CPPFLAGS     += -I$(PWD)/third_party/mbedtls/repo/include
+$(PREFIX_UC)_MBEDTLS_CPPFLAGS     += -I$(PWD)/third_party/mbedtls/repo/include/mbedtls
+
+COMMONCFLAGS                                         := \
+    -fdata-sections                                     \
+    -ffunction-sections                                 \
+    -Os                                                 \
+    $($(PREFIX_UC)_CONFIG_FILE_CPPFLAGS)                \
+    -Wall                                               \
+    -Wextra                                             \
+    $(NULL)
+
+include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/common-switches.mk
+
+CPPFLAGS                                             += \
+    $(COMMONCFLAGS)                                     \
+    $(target_CPPFLAGS)                                  \
+    $(NULL)
+
+CFLAGS                                               += \
+    $(COMMONCFLAGS)                                     \
+    $(target_CFLAGS)                                    \
+    $(NULL)
+
+CXXFLAGS                                             += \
+    $(COMMONCFLAGS)                                     \
+    $(target_CXXFLAGS)                                  \
+    -fno-exceptions                                     \
+    -fno-rtti                                           \
+    $(NULL)
+
+LDFLAGS                                              += \
+    $(COMMONCFLAGS)                                     \
+    $(target_LDFLAGS)                                   \
+    -specs=nano.specs                                   \
+    -specs=nosys.specs                                  \
+    -Wl,--gc-sections                                   \
+    -nostdlib                                           \
+    -Wl,-Map=$(PREFIX_LC).map,--cref                    \
+    $(NULL)
+
+ECHO                            := @echo
+MAKE                            := make
+MKDIR_P                         := mkdir -p
+LN_S                            := ln -s
+RM_F                            := rm -f
+
+INSTALL                         := /usr/bin/install
+INSTALLFLAGS                    := -p
+
+BuildPath                       = build
+TopBuildDir                     = $(BuildPath)
+AbsTopBuildDir                  = $(PWD)/$(TopBuildDir)
+
+ResultPath                      = output
+TopResultDir                    = $(ResultPath)
+AbsTopResultDir                 = $(PWD)/$(TopResultDir)
+
+TargetTuple                     = $(PREFIX_LC)
+
+ARCHS                           = cortex-m4
+
+TopTargetLibDir                 = $(TopResultDir)/$(TargetTuple)/lib
+
+ifndef BuildJobs
+BuildJobs := $(shell getconf _NPROCESSORS_ONLN)
+endif
+JOBSFLAG := -j$(BuildJobs)
+
+#
+# configure-arch <arch>
+#
+# Configure OpenThread for the specified architecture.
+#
+#   arch - The architecture to configure.
+#
+define configure-arch
+$(ECHO) "  CONFIG   $(TargetTuple)"
+(cd $(BuildPath)/$(TargetTuple) && $(AbsTopSourceDir)/configure \
+INSTALL="$(INSTALL) $(INSTALLFLAGS)" \
+CPP="$(CPP)" CC="$(CC)" CXX="$(CXX)" CCAS="$(CCAS)" OBJC="$(OBJC)" OBJCXX="$(OBJCXX)" AR="$(AR)" RANLIB="$(RANLIB)" NM="$(NM)" STRIP="$(STRIP)" CPPFLAGS="$(CPPFLAGS)" CFLAGS="$(CFLAGS)" CXXFLAGS="$(CXXFLAGS)" CCASFLAGS="$(CCASFLAGS)" LDFLAGS="$(LDFLAGS)" \
+--host=arm-none-eabi \
+--prefix=/$(TargetTuple) \
+--program-prefix=$(TargetTuple)- \
+$(configure_OPTIONS))
+endef # configure-arch
+
+#
+# build-arch <arch>
+#
+# Build the OpenThread intermediate build products for the specified
+# architecture.
+#
+#   arch - The architecture to build.
+#
+define build-arch
+$(ECHO) "  BUILD    $(TargetTuple)"
+$(MAKE) $(JOBSFLAG) -C $(BuildPath)/$(TargetTuple) --no-print-directory \
+all
+endef # build-arch
+
+#
+# stage-arch <arch>
+#
+# Stage (install) the OpenThread final build products for the specified
+# architecture.
+#
+#   arch - The architecture to stage.
+#
+define stage-arch
+$(ECHO) "  STAGE    $(TargetTuple)"
+$(MAKE) $(JOBSFLAG) -C $(BuildPath)/$(TargetTuple) --no-print-directory \
+DESTDIR=$(AbsTopResultDir) \
+install
+endef # stage-arch
+
+# @FIXME: get the filenames of the targets of OBJCOPY (now they are hardcoded)
+define hex-arch
+$(ECHO) "  HEX       $(TargetTuple)"
+$(OBJCOPY) -O ihex $(AbsTopResultDir)/$(TargetTuple)/bin/$(TargetTuple)-ot-cli-mtd $(AbsTopResultDir)/$(TargetTuple)/bin/$(TargetTuple)-ot-cli-mtd.hex
+$(OBJCOPY) -O ihex $(AbsTopResultDir)/$(TargetTuple)/bin/$(TargetTuple)-ot-cli-ftd $(AbsTopResultDir)/$(TargetTuple)/bin/$(TargetTuple)-ot-cli-ftd.hex
+endef # hex-arch
+
+#
+# ARCH_template <arch>
+#
+# Define macros, targets and rules to configure, build, and stage the
+# OpenThread for a single architecture.
+#
+#   arch - The architecture to instantiate the template for.
+#
+define ARCH_template
+CONFIGURE_TARGETS += configure-$(1)
+BUILD_TARGETS     += do-build-$(1)
+HEX_TARGETS       += hex-$(1)
+STAGE_TARGETS     += stage-$(1)
+BUILD_DIRS        += $(BuildPath)/$(TargetTuple)
+DIRECTORIES       += $(BuildPath)/$(TargetTuple)
+
+configure-$(1): target_CPPFLAGS=$($(1)_target_CPPFLAGS)
+configure-$(1): target_CFLAGS=$($(1)_target_CFLAGS)
+configure-$(1): target_CXXFLAGS=$($(1)_target_CXXFLAGS)
+configure-$(1): target_LDFLAGS=$($(1)_target_LDFLAGS)
+configure-$(1): target_CCASFLAGS=$($(1)_target_CCASFLAGS)
+
+configure-$(1): $(BuildPath)/$(TargetTuple)/config.status
+
+$(BuildPath)/$(TargetTuple)/config.status: | $(BuildPath)/$(TargetTuple)
+	$$(call configure-arch,$(1))
+
+do-build-$(1): configure-$(1)
+
+do-build-$(1):
+	+$$(call build-arch,$(1))
+
+hex-$(1): stage-$(1)
+
+hex-$(1):
+	$$(call hex-arch,$(1))
+
+$(1): hex-$(1)
+
+stage-$(1): do-build-$(1)
+
+stage-$(1): | $(TopResultDir)
+	$$(call stage-arch,$(1))
+endef # ARCH_template
+
+.DEFAULT_GOAL := all
+
+all: hex
+
+#
+# cortex-m4
+#
+
+cortex-m4_target_ABI                  = cortex-m4
+cortex-m4_target_CPPFLAGS             = -mcpu=cortex-m4 -mthumb -ffunction-sections
+cortex-m4_target_CFLAGS               = -mcpu=cortex-m4 -mthumb -ffunction-sections
+cortex-m4_target_CXXFLAGS             = -mcpu=cortex-m4 -mthumb -ffunction-sections
+cortex-m4_target_LDFLAGS              = -mcpu=cortex-m4 -mthumb -ffunction-sections
+
+# Instantiate an architecture-specific build template for each target
+# architecture.
+
+$(foreach arch,$(ARCHS),$(eval $(call ARCH_template,$(arch))))
+
+#
+# Common / Finalization
+#
+
+configure: $(CONFIGURE_TARGETS)
+
+build: $(BUILD_TARGETS)
+
+stage: $(STAGE_TARGETS)
+
+hex: $(HEX_TARGETS)
+
+DIRECTORIES     = $(TopResultDir) $(TopResultDir)/$(TargetTuple)/lib $(BUILD_DIRS)
+
+CLEAN_DIRS      = $(TopResultDir) $(BUILD_DIRS)
+
+all: hex
+
+$(DIRECTORIES):
+	$(ECHO) "  MKDIR    $@"
+	@$(MKDIR_P) "$@"
+
+clean:
+	$(ECHO) "  CLEAN"
+	@$(RM_F) -r $(CLEAN_DIRS)
+
+help:
+	$(ECHO) "Simply type 'make -f $(firstword $(MAKEFILE_LIST))' to build OpenThread for the following "
+	$(ECHO) "architectures: "
+	$(ECHO) ""
+	$(ECHO) "    $(ARCHS)"
+	$(ECHO) ""
+	$(ECHO) "To build only a particular architecture, specify: "
+	$(ECHO) ""
+	$(ECHO) "    make -f $(firstword $(MAKEFILE_LIST)) <architecture>"
+	$(ECHO) ""
diff --git a/examples/Makefile-qpg7015m b/examples/Makefile-qpg7015m
new file mode 100755
index 0000000..1ad3366
--- /dev/null
+++ b/examples/Makefile-qpg7015m
@@ -0,0 +1,277 @@
+#
+#  Copyright (c) 2019, The OpenThread Authors.
+#  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.
+#
+
+.NOTPARALLEL:
+
+TARGET        ?= arm-linux-gnueabihf
+NAME           = QPG7015M
+PREFIX_UC      = $(NAME)
+PREFIX_LC     := $(shell echo $(PREFIX_UC) | tr A-Z a-z)
+CROSS_COMPILE ?= $(TARGET)-
+
+AR                              = $(CROSS_COMPILE)ar
+AS                              = $(CROSS_COMPILE)as
+CPP                             = $(CROSS_COMPILE)cpp
+CC                              = $(CROSS_COMPILE)gcc
+CXX                             = $(CROSS_COMPILE)g++
+LD                              = $(CROSS_COMPILE)ld
+STRIP                           = $(CROSS_COMPILE)strip
+NM                              = $(CROSS_COMPILE)nm
+RANLIB                          = $(CROSS_COMPILE)ranlib
+OBJCOPY                         = $(CROSS_COMPILE)objcopy
+
+BuildJobs                      ?= 9
+
+configure_OPTIONS               =     \
+    --disable-tools                   \
+    --enable-cli                      \
+    --enable-ftd                      \
+    --enable-mtd                      \
+    --enable-ncp                      \
+    --enable-cli-app                  \
+    --enable-radio-only               \
+    --with-ncp-vendor-hook-source=no  \
+    --with-ncp-bus=uart               \
+    --with-examples=$(PREFIX_LC)      \
+    $(NULL)
+
+ifeq ($(CLI_LOGGING),1)
+configure_OPTIONS              += --enable-cli-logging
+endif
+
+$(PREFIX_UC)_CONFIG_FILE_CPPFLAGS  = -DOPENTHREAD_PROJECT_CORE_CONFIG_FILE='\"openthread-core-$(PREFIX_LC)-config.h\"'
+$(PREFIX_UC)_CONFIG_FILE_CPPFLAGS += -DOPENTHREAD_CORE_CONFIG_PLATFORM_CHECK_FILE='\"openthread-core-$(PREFIX_LC)-config-check.h\"'
+$(PREFIX_UC)_CONFIG_FILE_CPPFLAGS += -I$(PWD)/examples/platforms/$(PREFIX_LC)/
+
+COMMONCFLAGS                             := \
+    -fdata-sections                         \
+    -ffunction-sections                     \
+    -Os                                     \
+    $($(PREFIX_UC)_CONFIG_FILE_CPPFLAGS)    \
+    -Wextra                                 \
+    -Wall                                   \
+    -Wno-psabi                              \
+    $(NULL)
+
+include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/common-switches.mk
+
+CPPFLAGS                                 += \
+    $(COMMONCFLAGS)                         \
+    $(target_CPPFLAGS)                      \
+    $(NULL)
+
+CFLAGS                                   += \
+    $(COMMONCFLAGS)                         \
+    -D_BSD_SOURCE=1                         \
+    -D_DEFAULT_SOURCE=1                     \
+    $(target_CFLAGS)                        \
+    $(NULL)
+
+CXXFLAGS                                 += \
+    $(COMMONCFLAGS)                         \
+    $(target_CXXFLAGS)                      \
+    -fno-exceptions                         \
+    -fno-rtti                               \
+    $(NULL)
+
+LDFLAGS                                  += \
+    $(COMMONCFLAGS)                         \
+    $(target_LDFLAGS)                       \
+    -Wl,--gc-sections                       \
+    -Wl,-Map=$(PREFIX_LC).map               \
+    -lrt                                    \
+    -pthread                                \
+    $(NULL)
+
+ECHO                            := @echo
+MAKE                            := make
+MKDIR_P                         := mkdir -p
+LN_S                            := ln -s
+RM_F                            := rm -f
+
+INSTALL                         := /usr/bin/install
+INSTALLFLAGS                    := -p
+
+TopSourceDir                    := $(dir $(shell readlink $(firstword $(MAKEFILE_LIST))))..
+AbsTopSourceDir                 := $(dir $(realpath $(firstword $(MAKEFILE_LIST))))..
+
+BuildPath                       = build
+TopBuildDir                     = $(BuildPath)
+AbsTopBuildDir                  = $(PWD)/$(TopBuildDir)
+
+ResultPath                      = output
+TopResultDir                    = $(ResultPath)
+AbsTopResultDir                 = $(PWD)/$(TopResultDir)
+
+TargetTuple                     = $(PREFIX_LC)
+
+ifndef BuildJobs
+BuildJobs := $(shell getconf _NPROCESSORS_ONLN)
+endif
+JOBSFLAG := -j$(BuildJobs)
+
+#
+# configure-arch <arch>
+#
+# Configure OpenThread for the specified architecture.
+#
+#   arch - The architecture to configure.
+#
+define configure-arch
+$(ECHO) "  CONFIG   $(1)"
+(cd $(BuildPath)/$(1) && $(AbsTopSourceDir)/configure \
+INSTALL="$(INSTALL) $(INSTALLFLAGS)" \
+CPP="$(CPP)" CC="$(CC)" CXX="$(CXX)" OBJC="$(OBJC)" OBJCXX="$(OBJCXX)" AR="$(AR)" RANLIB="$(RANLIB)" NM="$(NM)" STRIP="$(STRIP)" CPPFLAGS="$(CPPFLAGS)" CFLAGS="$(CFLAGS)" CXXFLAGS="$(CXXFLAGS)" LDFLAGS="$(LDFLAGS)" \
+--prefix=/$(TargetTuple) \
+--host=$(TARGET) \
+--target=$(TARGET) \
+--exec-prefix=/$(TargetTuple) \
+--program-prefix=$(TargetTuple)- \
+$(configure_OPTIONS))
+endef # configure-arch
+
+#
+# build-arch <arch>
+#
+# Build the OpenThread intermediate build products for the specified
+# architecture.
+#
+#   arch - The architecture to build.
+#
+define build-arch
+$(ECHO) "  BUILD    $(1)"
+$(MAKE) $(JOBSFLAG) -C $(BuildPath)/$(1) -w \
+all
+endef # build-arch
+
+#
+# stage-arch <arch>
+#
+# Stage (install) the OpenThread final build products for the specified
+# architecture.
+#
+#   arch - The architecture to stage.
+#
+define stage-arch
+$(ECHO) "  STAGE    $(1)"
+$(MAKE) $(JOBSFLAG) -C $(BuildPath)/$(1) -w \
+DESTDIR=$(AbsTopResultDir) \
+install
+endef # stage-arch
+
+#
+# ARCH_template <arch>
+#
+# Define macros, targets and rules to configure, build, and stage the
+# OpenThread for a single architecture.
+#
+#   arch - The architecture to instantiate the template for.
+#
+define ARCH_template
+CONFIGURE_TARGETS += configure-$(1)
+BUILD_TARGETS     += do-build-$(1)
+STAGE_TARGETS     += stage-$(1)
+BUILD_DIRS        += $(BuildPath)/$(1)
+DIRECTORIES       += $(BuildPath)/$(1)
+
+configure-$(1): target_CPPFLAGS=$($(1)_target_CPPFLAGS)
+configure-$(1): target_CFLAGS=$($(1)_target_CFLAGS)
+configure-$(1): target_CXXFLAGS=$($(1)_target_CXXFLAGS)
+configure-$(1): target_LDFLAGS=$($(1)_target_LDFLAGS)
+
+configure-$(1): $(BuildPath)/$(1)/config.status
+
+$(BuildPath)/$(1)/config.status: | $(BuildPath)/$(1)
+	$$(call configure-arch,$(1))
+
+do-build-$(1): configure-$(1)
+
+do-build-$(1):
+	+$$(call build-arch,$(1))
+
+stage-$(1): do-build-$(1)
+
+stage-$(1): | $(TopResultDir)
+	$$(call stage-arch,$(1))
+
+$(1): stage-$(1)
+endef # ARCH_template
+
+.DEFAULT_GOAL := all
+
+all: stage
+
+#
+# rpi_bcm2708
+#
+
+rpi_bcm2708_target_ABI                  = rpi_bcm2708
+rpi_bcm2708_target_CPPFLAGS             = -march=armv6j -fomit-frame-pointer  -fno-strict-aliasing  -fno-pic  -ffreestanding  -mfloat-abi=hard  -mfpu=vfp  -pipe
+rpi_bcm2708_target_CFLAGS               = -march=armv6j -fomit-frame-pointer  -fno-strict-aliasing  -fno-pic  -ffreestanding  -mfloat-abi=hard  -mfpu=vfp  -pipe
+rpi_bcm2708_target_CXXFLAGS             = -march=armv6j -fomit-frame-pointer  -fno-strict-aliasing  -fno-pic  -ffreestanding  -mfloat-abi=hard  -mfpu=vfp  -pipe
+rpi_bcm2708_target_LDFLAGS              = -march=armv6j -fomit-frame-pointer  -fno-strict-aliasing  -fno-pic  -ffreestanding  -mfloat-abi=hard  -mfpu=vfp  -pipe
+
+# Instantiate an architecture-specific build template for each target
+# architecture.
+
+$(foreach arch,$(TargetTuple),$(eval $(call ARCH_template,$(arch))))
+
+#
+# Common / Finalization
+#
+
+configure: $(CONFIGURE_TARGETS)
+
+build: $(BUILD_TARGETS)
+
+stage: $(STAGE_TARGETS)
+
+DIRECTORIES     = $(TopResultDir) $(TopResultDir)/$(TargetTuple)/lib $(BUILD_DIRS)
+
+CLEAN_DIRS      = $(TopResultDir) $(BUILD_DIRS)
+
+all: stage
+
+$(DIRECTORIES):
+	$(ECHO) "  MKDIR    $@"
+	@$(MKDIR_P) "$@"
+
+clean:
+	$(ECHO) "  CLEAN"
+	@$(RM_F) -r $(CLEAN_DIRS)
+
+help:
+	$(ECHO) "Simply type 'make -f $(firstword $(MAKEFILE_LIST))' to build OpenThread for the following "
+	$(ECHO) "architectures: "
+	$(ECHO) ""
+	$(ECHO) "    $(TargetTuple)"
+	$(ECHO) ""
+	$(ECHO) "To build only a particular architecture, specify: "
+	$(ECHO) ""
+	$(ECHO) "    make -f $(firstword $(MAKEFILE_LIST)) <architecture>"
+	$(ECHO) ""
diff --git a/examples/Makefile-simulation b/examples/Makefile-simulation
index 755e2c6..d1d4a11 100644
--- a/examples/Makefile-simulation
+++ b/examples/Makefile-simulation
@@ -38,6 +38,7 @@
 BORDER_AGENT                   ?= 1
 BORDER_ROUTER                  ?= 1
 COAP                           ?= 1
+COAP_BLOCK                     ?= 1
 COAP_OBSERVE                   ?= 1
 COAPS                          ?= 1
 COMMISSIONER                   ?= 1
@@ -49,6 +50,7 @@
 DHCP6_SERVER                   ?= 1
 DIAGNOSTIC                     ?= 1
 DNS_CLIENT                     ?= 1
+DNSSD_SERVER                   ?= 1
 ECDSA                          ?= 1
 IP6_FRAGM                      ?= 1
 JAM_DETECTION                  ?= 1
@@ -57,9 +59,12 @@
 LINK_RAW                       ?= 1
 MAC_FILTER                     ?= 1
 MTD_NETDIAG                    ?= 1
+PING_SENDER                    ?= 1
 REFERENCE_DEVICE               ?= 1
 SERVICE                        ?= 1
 SNTP_CLIENT                    ?= 1
+SRP_CLIENT                     ?= 1
+SRP_SERVER                     ?= 1
 UDP_FORWARD                    ?= 1
 
 COMMONCFLAGS                   := \
@@ -89,7 +94,7 @@
 ifeq ($(NCP_SPI),1)
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_NCP_SPI_ENABLE=1
 else
-COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_NCP_UART_ENABLE=1
+COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1
 endif # NCP_SPI == 1
 
 ifeq ($(OTNS),1)
diff --git a/examples/README.md b/examples/README.md
index 0ad87a1..f88c4b6 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -14,7 +14,6 @@
 | CHANNEL_MANAGER | OT_CHANNEL_MANAGER | Enables support for channel manager. Enable this switch on devices that are supposed to request a Thread network channel change. This switch should be used only with an FTD build. |
 | CHANNEL_MONITOR | OT_CHANNEL_MONITOR | Enables support for channel monitor. Enable this switch on devices that are supposed to determine the cleaner channels. |
 | CHILD_SUPERVISION | OT_CHILD_SUPERVISION | Enables support for [child supervision](https://openthread.io/guides/build/features/child-supervision). Enable this switch on a parent or child node with custom OpenThread application that manages the supervision, checks timeout intervals, and verifies connectivity between parent and child. |
-| CLI_TRANSPORT | not implemented | Selects the transport of CLI. You can set this switch to UART (default) or CONSOLE. |
 | COAP | OT_COAP | Enables support for the CoAP API. Enable this switch if you want to control Constrained Application Protocol communication. |
 | COAP_OBSERVE | OT_COAP_OBSERVE | Enables support for CoAP Observe (RFC7641) API. |
 | COAPS | OT_COAPS | Enables support for the secure CoAP API. Enable this switch if you want to control Constrained Application Protocol Secure (CoAP over DTLS) communication. |
@@ -32,6 +31,7 @@
 | DEBUG_UART | not implemented | Enables the Debug UART platform feature. |
 | DEBUG_UART_LOG | not implemented | Enables the log output for the debug UART. Requires OPENTHREAD_CONFIG_ENABLE_DEBUG_UART to be enabled. |
 | DNS_CLIENT | OT_DNS_CLIENT | Enables support for DNS client. Enable this switch on a device that sends a DNS query for AAAA (IPv6) record. |
+| DNSSD_SERVER | OT_DNSSD_SERVER | Enables support for DNS-SD server. DNS-SD server use service information from local SRP server to resolve DNS-SD query questions. |
 | DUA | OT_DUA | Enables the Domain Unicast Address feature for Thread 1.2. |
 | DYNAMIC_LOG_LEVEL | not implemented | Enables the dynamic log level feature. Enable this switch if OpenThread log level is required to be set at runtime. See [Logging guide](https://openthread.io/guides/build/logs) to learn more. |
 | ECDSA | OT_ECDSA | Enables support for Elliptic Curve Digital Signature Algorithm. Enable this switch if ECDSA digital signature is used by application. |
@@ -48,6 +48,7 @@
 | MLR | OT_MLR | Enables Multicast Listener Registration feature for Thread 1.2. |
 | MTD_NETDIAG | OT_MTD_NETDIAG | Enables the TMF network diagnostics on MTDs. |
 | MULTIPLE_INSTANCE | OT_MULTIPLE_INSTANCE | Enables multiple OpenThread instances. |
+| PING_SENDER | OT_PING_SENDER | Enables support for ping sender. |
 | OTNS | OT_OTNS | Enables support for [OpenThread Network Simulator](https://github.com/openthread/ot-ns). Enable this switch if you are building OpenThread for OpenThread Network Simulator. |
 | PLATFORM_UDP | OT_PLATFORM_UDP | Enables platform UDP support. |
 | REFERENCE_DEVICE | OT_REFERENCE_DEVICE | Enables support for Thread Test Harness reference device. Enable this switch on the reference device during certification. |
@@ -56,6 +57,9 @@
 | SLAAC | OT_SLAAC | Enables support for adding auto-configured SLAAC addresses by OpenThread. This feature is enabled by default. |
 | SNTP_CLIENT | OT_SNTP_CLIENT | Enables support for SNTP Client. |
 | SPINEL_ENCRYPTER_LIBS | not implemented | Specifies library files (absolute paths) for implementing the NCP Spinel Encrypter. |
+| SRP_CLIENT | OT_SRP_CLIENT | Enable support for SRP client. |
+| SRP_SERVER | OT_SRP_SERVER | Enable support for SRP server. |
 | THREAD_VERSION | OT_THREAD_VERSION | Enables the chosen Thread version (1.1 (default) / 1.2). For example, set to `1.2` for Thread 1.2. |
 | TIME_SYNC | OT_TIME_SYNC | Enables the time synchronization service feature. **Note: Enabling this feature breaks conformance to the Thread Specification.** |  |
+| TREL | OT_TREL | Enables TREL radio link for Thread over Infrastructure feature. |
 | UDP_FORWARD | OT_UDP_FORWARD | Enables support for UDP forward. | Enable this switch on the Border Router device (running on the NCP design) with External Commissioning support to service Thread Commissioner packets on the NCP side. |
diff --git a/examples/apps/Makefile.am b/examples/apps/Makefile.am
index 8abfc8b..004a98f 100644
--- a/examples/apps/Makefile.am
+++ b/examples/apps/Makefile.am
@@ -47,6 +47,10 @@
 
 if OPENTHREAD_ENABLE_NCP
 SUBDIRS                                += ncp
+else
+if OPENTHREAD_ENABLE_RADIO_ONLY
+SUBDIRS                                += ncp
+endif
 endif
 endif
 
diff --git a/examples/apps/cli/Makefile.am b/examples/apps/cli/Makefile.am
index 0d3e741..f5ba445 100644
--- a/examples/apps/cli/Makefile.am
+++ b/examples/apps/cli/Makefile.am
@@ -49,6 +49,7 @@
     $(NULL)
 
 SOURCES_COMMON                                                        += \
+    cli_uart.cpp                                                         \
     main.c                                                               \
     $(NULL)
 
diff --git a/examples/apps/cli/cli_uart.cpp b/examples/apps/cli/cli_uart.cpp
new file mode 100644
index 0000000..0f3e541
--- /dev/null
+++ b/examples/apps/cli/cli_uart.cpp
@@ -0,0 +1,420 @@
+/*
+ *  Copyright (c) 2016, The OpenThread Authors.
+ *  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.
+ */
+#include "openthread-core-config.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <openthread-system.h>
+#include <openthread/cli.h>
+
+#include "cli/cli_config.h"
+#include "common/code_utils.hpp"
+#include "common/debug.hpp"
+#include "common/logging.hpp"
+#include "utils/uart.h"
+
+#if OPENTHREAD_POSIX
+#include <signal.h>
+#include <sys/types.h>
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_CLI_UART_RX_BUFFER_SIZE
+ *
+ * The size of CLI UART RX buffer in bytes.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_CLI_UART_RX_BUFFER_SIZE
+#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
+#define OPENTHREAD_CONFIG_CLI_UART_RX_BUFFER_SIZE 640
+#else
+#define OPENTHREAD_CONFIG_CLI_UART_RX_BUFFER_SIZE 512
+#endif
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_CLI_TX_BUFFER_SIZE
+ *
+ * The size of CLI message buffer in bytes.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_CLI_UART_TX_BUFFER_SIZE
+#define OPENTHREAD_CONFIG_CLI_UART_TX_BUFFER_SIZE 1024
+#endif
+
+#if OPENTHREAD_CONFIG_DIAG_ENABLE
+#if OPENTHREAD_CONFIG_DIAG_OUTPUT_BUFFER_SIZE > OPENTHREAD_CONFIG_CLI_UART_TX_BUFFER_SIZE
+#error "diag output buffer should be smaller than CLI UART tx buffer"
+#endif
+#if OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE > OPENTHREAD_CONFIG_CLI_UART_RX_BUFFER_SIZE
+#error "diag command line should be smaller than CLI UART rx buffer"
+#endif
+#endif
+
+#if OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH > OPENTHREAD_CONFIG_CLI_UART_RX_BUFFER_SIZE
+#error "command line should be should be smaller than CLI rx buffer"
+#endif
+
+enum
+{
+    kRxBufferSize = OPENTHREAD_CONFIG_CLI_UART_RX_BUFFER_SIZE,
+    kTxBufferSize = OPENTHREAD_CONFIG_CLI_UART_TX_BUFFER_SIZE,
+};
+
+char     sRxBuffer[kRxBufferSize];
+uint16_t sRxLength;
+
+char     sTxBuffer[kTxBufferSize];
+uint16_t sTxHead;
+uint16_t sTxLength;
+
+uint16_t sSendLength;
+
+#ifdef OT_CLI_UART_LOCK_HDR_FILE
+
+#include OT_CLI_UART_LOCK_HDR_FILE
+
+#else
+
+/**
+ * Macro to acquire an exclusive lock of uart cli output
+ * Default implementation does nothing
+ *
+ */
+#ifndef OT_CLI_UART_OUTPUT_LOCK
+#define OT_CLI_UART_OUTPUT_LOCK() \
+    do                            \
+    {                             \
+    } while (0)
+#endif
+
+/**
+ * Macro to release the exclusive lock of uart cli output
+ * Default implementation does nothing
+ *
+ */
+#ifndef OT_CLI_UART_OUTPUT_UNLOCK
+#define OT_CLI_UART_OUTPUT_UNLOCK() \
+    do                              \
+    {                               \
+    } while (0)
+#endif
+
+#endif // OT_CLI_UART_LOCK_HDR_FILE
+
+static int     Output(const char *aBuf, uint16_t aBufLength);
+static otError ProcessCommand(void);
+
+static void ReceiveTask(const uint8_t *aBuf, uint16_t aBufLength)
+{
+    static const char sEraseString[]   = {'\b', ' ', '\b'};
+    static const char CRNL[]           = {'\r', '\n'};
+    static const char sCommandPrompt[] = {'>', ' '};
+    const uint8_t *   end;
+
+    end = aBuf + aBufLength;
+
+    for (; aBuf < end; aBuf++)
+    {
+        switch (*aBuf)
+        {
+        case '\r':
+        case '\n':
+            Output(CRNL, sizeof(CRNL));
+            if (sRxLength > 0)
+            {
+                sRxBuffer[sRxLength] = '\0';
+                IgnoreError(ProcessCommand());
+            }
+
+            Output(sCommandPrompt, sizeof(sCommandPrompt));
+
+            break;
+
+#if OPENTHREAD_POSIX && !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+        case 0x03: // ASCII for Ctrl-C
+            kill(0, SIGINT);
+            break;
+
+        case 0x04: // ASCII for Ctrl-D
+            exit(EXIT_SUCCESS);
+            break;
+#endif
+
+        case '\b':
+        case 127:
+            if (sRxLength > 0)
+            {
+                Output(sEraseString, sizeof(sEraseString));
+                sRxBuffer[--sRxLength] = '\0';
+            }
+
+            break;
+
+        default:
+            if (sRxLength < kRxBufferSize - 1)
+            {
+                Output(reinterpret_cast<const char *>(aBuf), 1);
+                sRxBuffer[sRxLength++] = static_cast<char>(*aBuf);
+            }
+
+            break;
+        }
+    }
+}
+
+static otError ProcessCommand(void)
+{
+    otError error = OT_ERROR_NONE;
+
+    while (sRxBuffer[sRxLength - 1] == '\n' || sRxBuffer[sRxLength - 1] == '\r')
+    {
+        sRxBuffer[--sRxLength] = '\0';
+    }
+
+#if OPENTHREAD_CONFIG_LOG_OUTPUT != OPENTHREAD_CONFIG_LOG_OUTPUT_NONE
+    /*
+     * Note this is here for this reason:
+     *
+     * TEXT (command) input ... in a test automation script occurs
+     * rapidly and often without gaps between the command and the
+     * terminal CR
+     *
+     * In contrast as a human is typing there is a delay between the
+     * last character of a command and the terminal CR which executes
+     * a command.
+     *
+     * During that human induced delay a tasklet may be scheduled and
+     * the LOG becomes confusing and it is hard to determine when
+     * something happened.  Which happened first? the command-CR or
+     * the tasklet.
+     *
+     * Yes, while rare it is a race condition that is hard to debug.
+     *
+     * Thus this is here to affirmatively LOG exactly when the CLI
+     * command is being executed.
+     */
+#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
+    /* TODO: how exactly do we get the instance here? */
+#else
+    otLogInfoCli("execute command: %s", sRxBuffer);
+#endif
+#endif
+    if (sRxLength > 0)
+    {
+        otCliInputLine(sRxBuffer);
+    }
+
+    sRxLength = 0;
+
+    return error;
+}
+
+static void Send(void)
+{
+    VerifyOrExit(sSendLength == 0);
+
+    if (sTxLength > kTxBufferSize - sTxHead)
+    {
+        sSendLength = kTxBufferSize - sTxHead;
+    }
+    else
+    {
+        sSendLength = sTxLength;
+    }
+
+    if (sSendLength > 0)
+    {
+#if OPENTHREAD_CONFIG_ENABLE_DEBUG_UART
+        /* duplicate the output to the debug uart */
+        otSysDebugUart_write_bytes(reinterpret_cast<uint8_t *>(sTxBuffer + sTxHead), sSendLength);
+#endif
+        IgnoreError(otPlatUartSend(reinterpret_cast<uint8_t *>(sTxBuffer + sTxHead), sSendLength));
+    }
+
+exit:
+    return;
+}
+
+static void SendDoneTask(void)
+{
+    sTxHead = (sTxHead + sSendLength) % kTxBufferSize;
+    sTxLength -= sSendLength;
+    sSendLength = 0;
+
+    Send();
+}
+
+static int Output(const char *aBuf, uint16_t aBufLength)
+{
+    OT_CLI_UART_OUTPUT_LOCK();
+    uint16_t sent = 0;
+
+    while (aBufLength > 0)
+    {
+        uint16_t remaining = kTxBufferSize - sTxLength;
+        uint16_t tail;
+        uint16_t sendLength = aBufLength;
+
+        if (sendLength > remaining)
+        {
+            sendLength = remaining;
+        }
+
+        for (uint16_t i = 0; i < sendLength; i++)
+        {
+            tail            = (sTxHead + sTxLength) % kTxBufferSize;
+            sTxBuffer[tail] = *aBuf++;
+            aBufLength--;
+            sTxLength++;
+        }
+
+        Send();
+
+        sent += sendLength;
+
+        if (aBufLength > 0)
+        {
+            // More to send, so flush what's waiting now
+            otError err = otPlatUartFlush();
+
+            if (err == OT_ERROR_NONE)
+            {
+                // Flush successful, reset the pointers
+                SendDoneTask();
+            }
+            else
+            {
+                // Flush did not succeed, so abort here.
+                break;
+            }
+        }
+    }
+
+    OT_CLI_UART_OUTPUT_UNLOCK();
+
+    return sent;
+}
+
+static int CliUartOutput(void *aContext, const char *aFormat, va_list aArguments)
+{
+    OT_UNUSED_VARIABLE(aContext);
+
+    int rval;
+
+    if (sTxLength == 0)
+    {
+        rval = vsnprintf(sTxBuffer, kTxBufferSize, aFormat, aArguments);
+        VerifyOrExit(rval > 0 && rval < kTxBufferSize, otLogWarnPlat("Failed to format CLI output `%s`", aFormat));
+        sTxHead     = 0;
+        sTxLength   = static_cast<uint16_t>(rval);
+        sSendLength = 0;
+    }
+    else
+    {
+        va_list  retryArguments;
+        uint16_t tail      = (sTxHead + sTxLength) % kTxBufferSize;
+        uint16_t remaining = (sTxHead > tail ? (sTxHead - tail) : (kTxBufferSize - tail));
+
+        va_copy(retryArguments, aArguments);
+
+        rval = vsnprintf(&sTxBuffer[tail], remaining, aFormat, aArguments);
+
+        if (rval < 0)
+        {
+            otLogWarnPlat("Failed to format CLI output `%s`", aFormat);
+        }
+        else if (rval < remaining)
+        {
+            sTxLength += rval;
+        }
+        else if (rval < kTxBufferSize)
+        {
+            while (sTxLength != 0)
+            {
+                otError error;
+
+                Send();
+
+                error = otPlatUartFlush();
+
+                if (error == OT_ERROR_NONE)
+                {
+                    // Flush successful, reset the pointers
+                    SendDoneTask();
+                }
+                else
+                {
+                    // Flush did not succeed, so abort here.
+                    otLogWarnPlat("Failed to output CLI: %s", otThreadErrorToString(error));
+                    ExitNow();
+                }
+            }
+            rval = vsnprintf(sTxBuffer, kTxBufferSize, aFormat, retryArguments);
+            OT_ASSERT(rval > 0);
+            sTxLength   = static_cast<uint16_t>(rval);
+            sTxHead     = 0;
+            sSendLength = 0;
+        }
+        else
+        {
+            otLogWarnPlat("CLI output `%s` truncated", aFormat);
+        }
+
+        va_end(retryArguments);
+    }
+
+    Send();
+
+exit:
+    return rval;
+}
+
+void otPlatUartReceived(const uint8_t *aBuf, uint16_t aBufLength)
+{
+    ReceiveTask(aBuf, aBufLength);
+}
+
+void otPlatUartSendDone(void)
+{
+    SendDoneTask();
+}
+
+extern "C" void otAppCliInit(otInstance *aInstance)
+{
+    sRxLength   = 0;
+    sTxHead     = 0;
+    sTxLength   = 0;
+    sSendLength = 0;
+
+    IgnoreError(otPlatUartEnable());
+
+    otCliInit(aInstance, CliUartOutput, aInstance);
+}
diff --git a/examples/apps/cli/ftd.cmake b/examples/apps/cli/ftd.cmake
index 5a55455..2372153 100644
--- a/examples/apps/cli/ftd.cmake
+++ b/examples/apps/cli/ftd.cmake
@@ -27,6 +27,7 @@
 #
 
 add_executable(ot-cli-ftd
+    cli_uart.cpp
     main.c
 )
 
@@ -37,6 +38,7 @@
     ${OT_PLATFORM_LIB}
     openthread-ftd
     ${OT_PLATFORM_LIB}
+    openthread-cli-ftd
     ${OT_MBEDTLS}
     ot-config
 )
diff --git a/examples/apps/cli/main.c b/examples/apps/cli/main.c
index 376e04d..e03d51f 100644
--- a/examples/apps/cli/main.c
+++ b/examples/apps/cli/main.c
@@ -38,6 +38,14 @@
 #include "openthread-system.h"
 #include "cli/cli_config.h"
 
+/**
+ * This function initializes the CLI app.
+ *
+ * @param[in]  aInstance  The OpenThread instance structure.
+ *
+ */
+extern void otAppCliInit(otInstance *aInstance);
+
 #if OPENTHREAD_EXAMPLES_SIMULATION
 #include <setjmp.h>
 #include <unistd.h>
@@ -51,7 +59,7 @@
 #define OPENTHREAD_ENABLE_COVERAGE 0
 #endif
 
-#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
+#if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
 void *otPlatCAlloc(size_t aNum, size_t aSize)
 {
     return calloc(aNum, aSize);
@@ -107,9 +115,7 @@
 #endif
     assert(instance);
 
-#if OPENTHREAD_CONFIG_CLI_TRANSPORT == OT_CLI_TRANSPORT_UART
-    otCliUartInit(instance);
-#endif
+    otAppCliInit(instance);
 
     while (!otSysPseudoResetWasRequested())
     {
diff --git a/examples/apps/cli/mtd.cmake b/examples/apps/cli/mtd.cmake
index 45b8923..2d758db 100644
--- a/examples/apps/cli/mtd.cmake
+++ b/examples/apps/cli/mtd.cmake
@@ -27,6 +27,7 @@
 #
 
 add_executable(ot-cli-mtd
+    cli_uart.cpp
     main.c
 )
 
@@ -37,6 +38,7 @@
     ${OT_PLATFORM_LIB}
     openthread-mtd
     ${OT_PLATFORM_LIB}
+    openthread-cli-mtd
     ${OT_MBEDTLS}
     ot-config
 )
diff --git a/examples/apps/ncp/Makefile.am b/examples/apps/ncp/Makefile.am
index 430585e..9cf17e4 100644
--- a/examples/apps/ncp/Makefile.am
+++ b/examples/apps/ncp/Makefile.am
@@ -34,6 +34,7 @@
 
 CPPFLAGS_COMMON                                                       += \
     -I$(top_srcdir)/include                                              \
+    -I$(top_srcdir)/src                                                  \
     -I$(top_srcdir)/src/core                                             \
     -I$(top_srcdir)/examples/platforms                                   \
     $(NULL)
@@ -49,6 +50,7 @@
 
 SOURCES_COMMON                                                        += \
     main.c                                                               \
+    ncp.c                                                                \
     $(NULL)
 
 LDADD_MBEDTLS                                                          = \
@@ -76,11 +78,13 @@
     $(NULL)
 endif # OPENTHREAD_ENABLE_NCP_SPINEL_ENCRYPTER
 
+if OPENTHREAD_ENABLE_NCP
 if OPENTHREAD_ENABLE_FTD
 bin_PROGRAMS                                                          += \
     ot-ncp-ftd                                                           \
     $(NULL)
 endif
+endif
 
 ot_ncp_ftd_CPPFLAGS                                                    = \
     $(CPPFLAGS_COMMON)                                                   \
@@ -110,11 +114,13 @@
     $(SOURCES_COMMON)                                                    \
     $(NULL)
 
+if OPENTHREAD_ENABLE_NCP
 if OPENTHREAD_ENABLE_MTD
 bin_PROGRAMS                                                          += \
     ot-ncp-mtd                                                           \
     $(NULL)
 endif
+endif
 
 ot_ncp_mtd_CPPFLAGS                                                    = \
     $(CPPFLAGS_COMMON)                                                   \
diff --git a/examples/apps/ncp/ftd.cmake b/examples/apps/ncp/ftd.cmake
index d822b4a..f09e9db 100644
--- a/examples/apps/ncp/ftd.cmake
+++ b/examples/apps/ncp/ftd.cmake
@@ -28,6 +28,7 @@
 
 add_executable(ot-ncp-ftd
     main.c
+    ncp.c
 )
 
 target_include_directories(ot-ncp-ftd PRIVATE ${COMMON_INCLUDES})
@@ -37,6 +38,7 @@
     ${OT_PLATFORM_LIB}
     openthread-ftd
     ${OT_PLATFORM_LIB}
+    openthread-ncp-ftd
     ${OT_MBEDTLS}
     ot-config
 )
diff --git a/examples/apps/ncp/main.c b/examples/apps/ncp/main.c
index 2d9c630..fa2558c 100644
--- a/examples/apps/ncp/main.c
+++ b/examples/apps/ncp/main.c
@@ -36,6 +36,14 @@
 
 #include "openthread-system.h"
 
+/**
+ * This function initializes the NCP app.
+ *
+ * @param[in]  aInstance  The OpenThread instance structure.
+ *
+ */
+extern void otAppNcpInit(otInstance *aInstance);
+
 #if OPENTHREAD_EXAMPLES_SIMULATION
 #include <setjmp.h>
 #include <unistd.h>
@@ -49,7 +57,7 @@
 #define OPENTHREAD_ENABLE_COVERAGE 0
 #endif
 
-#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
+#if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
 void *otPlatCAlloc(size_t aNum, size_t aSize)
 {
     return calloc(aNum, aSize);
@@ -105,7 +113,7 @@
 #endif
     assert(instance);
 
-    otNcpInit(instance);
+    otAppNcpInit(instance);
 
     while (!otSysPseudoResetWasRequested())
     {
diff --git a/examples/apps/ncp/mtd.cmake b/examples/apps/ncp/mtd.cmake
index 2cb9fad..b25eedf 100644
--- a/examples/apps/ncp/mtd.cmake
+++ b/examples/apps/ncp/mtd.cmake
@@ -28,6 +28,7 @@
 
 add_executable(ot-ncp-mtd
     main.c
+    ncp.c
 )
 
 target_include_directories(ot-ncp-mtd PRIVATE ${COMMON_INCLUDES})
@@ -37,6 +38,7 @@
     ${OT_PLATFORM_LIB}
     openthread-mtd
     ${OT_PLATFORM_LIB}
+    openthread-ncp-mtd
     ${OT_MBEDTLS}
     ot-config
 )
diff --git a/examples/apps/ncp/ncp.c b/examples/apps/ncp/ncp.c
new file mode 100644
index 0000000..ee7ccf7
--- /dev/null
+++ b/examples/apps/ncp/ncp.c
@@ -0,0 +1,70 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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.
+ */
+
+#include "openthread-core-config.h"
+
+#include <openthread/ncp.h>
+
+#include "common/code_utils.hpp"
+
+#include "ncp/ncp_config.h"
+
+#if !OPENTHREAD_CONFIG_NCP_SPI_ENABLE
+#include "utils/uart.h"
+
+void otPlatUartReceived(const uint8_t *aBuf, uint16_t aBufLength)
+{
+    otNcpHdlcReceive(aBuf, aBufLength);
+}
+
+void otPlatUartSendDone(void)
+{
+    otNcpHdlcSendDone();
+}
+#endif
+
+#if !OPENTHREAD_ENABLE_NCP_VENDOR_HOOK
+#if !OPENTHREAD_CONFIG_NCP_SPI_ENABLE
+static int NcpSend(const uint8_t *aBuf, uint16_t aBufLength)
+{
+    IgnoreError(otPlatUartSend(aBuf, aBufLength));
+    return aBufLength;
+}
+#endif
+
+void otAppNcpInit(otInstance *aInstance)
+{
+#if OPENTHREAD_CONFIG_NCP_SPI_ENABLE
+    otNcpSpiInit(aInstance);
+#else
+    IgnoreError(otPlatUartEnable());
+
+    otNcpHdlcInit(aInstance, NcpSend);
+#endif
+}
+#endif // !OPENTHREAD_ENABLE_NCP_VENDOR_HOOK
diff --git a/examples/apps/ncp/rcp.cmake b/examples/apps/ncp/rcp.cmake
index 0298df7..923d31a 100644
--- a/examples/apps/ncp/rcp.cmake
+++ b/examples/apps/ncp/rcp.cmake
@@ -28,6 +28,7 @@
 
 add_executable(ot-rcp
     main.c
+    ncp.c
 )
 
 target_include_directories(ot-rcp PRIVATE ${COMMON_INCLUDES})
@@ -37,6 +38,7 @@
     ${OT_PLATFORM_LIB}
     openthread-radio
     ${OT_PLATFORM_LIB}
+    openthread-rcp
     ot-config
 )
 
diff --git a/examples/common-switches.mk b/examples/common-switches.mk
index b1cc708..7768f66 100644
--- a/examples/common-switches.mk
+++ b/examples/common-switches.mk
@@ -32,7 +32,9 @@
 BIG_ENDIAN                ?= 0
 BORDER_AGENT              ?= 0
 BORDER_ROUTER             ?= 0
+BORDER_ROUTING            ?= 0
 COAP                      ?= 0
+COAP_BLOCK                ?= 0
 COAP_OBSERVE              ?= 0
 COAPS                     ?= 0
 COMMISSIONER              ?= 0
@@ -40,7 +42,6 @@
 CHANNEL_MANAGER           ?= 0
 CHANNEL_MONITOR           ?= 0
 CHILD_SUPERVISION         ?= 0
-CLI_TRANSPORT             ?= UART
 DATASET_UPDATER           ?= 0
 DEBUG                     ?= 0
 DHCP6_CLIENT              ?= 0
@@ -49,6 +50,7 @@
 DISABLE_DOC               ?= 0
 DISABLE_TOOLS             ?= 0
 DNS_CLIENT                ?= 0
+DNSSD_SERVER              ?= 0
 DUA                       ?= 0
 DYNAMIC_LOG_LEVEL         ?= 0
 ECDSA                     ?= 0
@@ -68,6 +70,7 @@
 MTD_NETDIAG               ?= 0
 MULTIPLE_INSTANCE         ?= 0
 OTNS                      ?= 0
+PING_SENDER               ?= 1
 PLATFORM_UDP              ?= 0
 REFERENCE_DEVICE          ?= 0
 SERVICE                   ?= 0
@@ -75,8 +78,11 @@
 # SLAAC is enabled by default
 SLAAC                     ?= 1
 SNTP_CLIENT               ?= 0
-THREAD_VERSION            ?= 1.1
+SRP_CLIENT                ?= 0
+SRP_SERVER                ?= 0
+THREAD_VERSION            ?= 1.2
 TIME_SYNC                 ?= 0
+TREL                      ?= 0
 UDP_FORWARD               ?= 0
 RCP_RESTORATION_MAX_COUNT ?= 0
 
@@ -97,6 +103,10 @@
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE=1
 endif
 
+ifeq ($(BORDER_ROUTING),1)
+COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE=1
+endif
+
 ifeq ($(COAP),1)
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_COAP_API_ENABLE=1
 endif
@@ -105,6 +115,10 @@
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE=1
 endif
 
+ifeq ($(COAP_BLOCK),1)
+COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE=1
+endif
+
 ifeq ($(COAP_OBSERVE),1)
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE=1
 endif
@@ -129,10 +143,6 @@
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_CHILD_SUPERVISION_ENABLE=1
 endif
 
-ifneq ($(CLI_TRANSPORT),)
-COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_CLI_TRANSPORT=OT_CLI_TRANSPORT_$(CLI_TRANSPORT)
-endif
-
 ifeq ($(CSL_RECEIVER),1)
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE=1
 endif
@@ -173,6 +183,10 @@
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_DNS_CLIENT_ENABLE=1
 endif
 
+ifeq ($(DNSSD_SERVER),1)
+COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE=1
+endif
+
 ifeq ($(DUA),1)
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_DUA_ENABLE=1
 endif
@@ -242,6 +256,10 @@
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE=1
 endif
 
+ifeq ($(PING_SENDER),1)
+COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_PING_SENDER_ENABLE=1
+endif
+
 ifeq ($(PLATFORM_UDP),1)
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE=1
 endif
@@ -263,6 +281,14 @@
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE=1
 endif
 
+ifeq ($(SRP_CLIENT),1)
+COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_SRP_CLIENT_ENABLE=1
+endif
+
+ifeq ($(SRP_SERVER),1)
+COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_SRP_SERVER_ENABLE=1
+endif
+
 ifeq ($(THREAD_VERSION),1.1)
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_THREAD_VERSION=2
 else ifeq ($(THREAD_VERSION),1.2)
@@ -273,6 +299,10 @@
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_TIME_SYNC_ENABLE=1 -DOPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT=1
 endif
 
+ifeq ($(TREL),1)
+COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE=1
+endif
+
 ifeq ($(UDP_FORWARD),1)
 COMMONCFLAGS                   += -DOPENTHREAD_CONFIG_UDP_FORWARD_ENABLE=1
 endif
@@ -314,28 +344,5 @@
 COMMONCFLAGS += -DOPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT=${RCP_RESTORATION_MAX_COUNT}
 
 ifeq ($(FULL_LOGS),1)
-# HINT: Add more here, or comment out ones you do not need/want
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_LEVEL=OT_LOG_LEVEL_DEBG
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_API=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_ARP=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_BBR=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_CLI=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_COAP=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_DUA=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_ICMP=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_IP6=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_MAC=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_MEM=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_MESHCOP=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_MLE=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_MLR=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_NETDATA=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_NETDIAG=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_PKT_DUMP=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_PLATFORM=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_PREPEND_LEVEL=1
-LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_PREPEND_REGION=1
+COMMONCFLAGS += -DOPENTHREAD_CONFIG_LOG_LEVEL=OT_LOG_LEVEL_DEBG -DOPENTHREAD_CONFIG_LOG_PREPEND_LEVEL=1 -DOPENTHREAD_CONFIG_LOG_PREPEND_REGION=1
 endif
-
-CFLAGS += ${LOG_FLAGS}
-CXXFLAGS += ${LOG_FLAGS}
diff --git a/examples/platforms/Makefile.am b/examples/platforms/Makefile.am
index 750ff2e..b090fa3 100644
--- a/examples/platforms/Makefile.am
+++ b/examples/platforms/Makefile.am
@@ -42,6 +42,8 @@
     nrf528xx                              \
     simulation                            \
     qpg6095                               \
+    qpg6100                               \
+    qpg7015m                              \
     samr21                                \
     utils                                 \
     $(NULL)
@@ -96,7 +98,15 @@
 SUBDIRS                                += qpg6095
 endif
 
-if OPENTHREAD_PLATFORM_SAMR21
+if OPENTHREAD_PLATFORM_QPG6100
+SUBDIRS                                += qpg6100
+endif
+
+if OPENTHREAD_PLATFORM_QPG7015M
+SUBDIRS                                += qpg7015m
+endif
+
+if OPENTHREAD_EXAMPLES_SAMR21
 SUBDIRS                                += samr21
 endif
 
diff --git a/examples/platforms/Makefile.platform.am b/examples/platforms/Makefile.platform.am
index e0a1a63..085ec55 100644
--- a/examples/platforms/Makefile.platform.am
+++ b/examples/platforms/Makefile.platform.am
@@ -93,6 +93,14 @@
 include $(top_srcdir)/examples/platforms/qpg6095/Makefile.platform.am
 endif
 
+if OPENTHREAD_EXAMPLES_QPG6100
+include $(top_srcdir)/examples/platforms/qpg6100/Makefile.platform.am
+endif
+
+if OPENTHREAD_EXAMPLES_QPG7015M
+include $(top_srcdir)/examples/platforms/qpg7015m/Makefile.platform.am
+endif
+
 if OPENTHREAD_EXAMPLES_SAMR21
 include $(top_srcdir)/examples/platforms/samr21/Makefile.platform.am
 endif # OPENTHREAD_EXAMPLES_SAMR21
diff --git a/examples/platforms/cc1352/openthread-core-cc1352-config.h b/examples/platforms/cc1352/openthread-core-cc1352-config.h
index ad73700..449ed8c 100644
--- a/examples/platforms/cc1352/openthread-core-cc1352-config.h
+++ b/examples/platforms/cc1352/openthread-core-cc1352-config.h
@@ -48,11 +48,11 @@
 #define OPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE 1
 
 /**
- * @def OPENTHREAD_CONFIG_NCP_UART_ENABLE
+ * @def OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
  *
- * Define to 1 to enable NCP UART support.
+ * Define to 1 to enable NCP HDLC support.
  *
  */
-#define OPENTHREAD_CONFIG_NCP_UART_ENABLE 1
+#define OPENTHREAD_CONFIG_NCP_HDLC_ENABLE 1
 
 #endif /* OPENTHREAD_CORE_CC1352_CONFIG_H_ */
diff --git a/examples/platforms/cc1352/uart.c b/examples/platforms/cc1352/uart.c
index 34cd865..c4cb87f 100644
--- a/examples/platforms/cc1352/uart.c
+++ b/examples/platforms/cc1352/uart.c
@@ -41,11 +41,9 @@
 
 #include <openthread/platform/debug_uart.h>
 #include <openthread/platform/logging.h>
-#include <openthread/platform/uart.h>
 
 #include "utils/code_utils.h"
-
-#include <utils/code_utils.h>
+#include "utils/uart.h"
 
 #include <driverlib/ioc.h>
 #include <driverlib/prcm.h>
@@ -116,7 +114,7 @@
 }
 
 /**
- * Function documented in platform/uart.h
+ * Function documented in utils/uart.h
  */
 otError otPlatUartEnable(void)
 {
@@ -138,7 +136,7 @@
 }
 
 /**
- * Function documented in platform/uart.h
+ * Function documented in utils/uart.h
  */
 otError otPlatUartDisable(void)
 {
@@ -154,7 +152,7 @@
 }
 
 /**
- * Function documented in platform/uart.h
+ * Function documented in utils/uart.h
  */
 otError otPlatUartSend(const uint8_t *aBuf, uint16_t aBufLength)
 {
diff --git a/examples/platforms/cc2538/CMakeLists.txt b/examples/platforms/cc2538/CMakeLists.txt
index 341c5f1..c69d1e5 100644
--- a/examples/platforms/cc2538/CMakeLists.txt
+++ b/examples/platforms/cc2538/CMakeLists.txt
@@ -35,7 +35,7 @@
 
 list(APPEND OT_PLATFORM_DEFINES
     "OPENTHREAD_CORE_CONFIG_PLATFORM_CHECK_FILE=\"openthread-core-cc2538-config-check.h\""
-    "OPENTHREAD_CONFIG_NCP_UART_ENABLE=1"
+    "OPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1"
 )
 set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE)
 
diff --git a/examples/platforms/cc2538/openthread-core-cc2538-config.h b/examples/platforms/cc2538/openthread-core-cc2538-config.h
index fabb6fa..1f26217 100644
--- a/examples/platforms/cc2538/openthread-core-cc2538-config.h
+++ b/examples/platforms/cc2538/openthread-core-cc2538-config.h
@@ -83,12 +83,12 @@
 #define OPENTHREAD_CONFIG_MAC_SOFTWARE_ENERGY_SCAN_ENABLE 1
 
 /**
- * @def OPENTHREAD_CONFIG_NCP_UART_ENABLE
+ * @def OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
  *
- * Define to 1 to enable NCP UART support.
+ * Define to 1 to enable NCP HDLC support.
  *
  */
-#define OPENTHREAD_CONFIG_NCP_UART_ENABLE 1
+#define OPENTHREAD_CONFIG_NCP_HDLC_ENABLE 1
 
 /**
  * @def OPENTHREAD_CONFIG_CC2538_USE_RADIO_RX_INTERRUPT
diff --git a/examples/platforms/cc2538/uart.c b/examples/platforms/cc2538/uart.c
index 992af65..3325c99 100644
--- a/examples/platforms/cc2538/uart.c
+++ b/examples/platforms/cc2538/uart.c
@@ -41,10 +41,10 @@
 
 #include <openthread/platform/debug_uart.h>
 #include <openthread/platform/logging.h>
-#include <openthread/platform/uart.h>
 
 #include "platform-cc2538.h"
 #include "utils/code_utils.h"
+#include "utils/uart.h"
 
 enum
 {
diff --git a/examples/platforms/cc2650/openthread-core-cc2650-config.h b/examples/platforms/cc2650/openthread-core-cc2650-config.h
index 31d2fb1..94de3b5 100644
--- a/examples/platforms/cc2650/openthread-core-cc2650-config.h
+++ b/examples/platforms/cc2650/openthread-core-cc2650-config.h
@@ -45,12 +45,12 @@
 #define OPENTHREAD_CONFIG_NUM_MESSAGE_BUFFERS 32
 
 /**
- * @def OPENTHREAD_CONFIG_NCP_UART_ENABLE
+ * @def OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
  *
- * Define to 1 to enable NCP UART support.
+ * Define to 1 to enable NCP HDLC support.
  *
  */
-#define OPENTHREAD_CONFIG_NCP_UART_ENABLE 1
+#define OPENTHREAD_CONFIG_NCP_HDLC_ENABLE 1
 
 /**
  * @def OPENTHREAD_SETTINGS_RAM
diff --git a/examples/platforms/cc2650/uart.c b/examples/platforms/cc2650/uart.c
index d6f0483..44515fa 100644
--- a/examples/platforms/cc2650/uart.c
+++ b/examples/platforms/cc2650/uart.c
@@ -34,7 +34,8 @@
 #include <driverlib/uart.h>
 
 #include <utils/code_utils.h>
-#include <openthread/platform/uart.h>
+
+#include "utils/uart.h"
 
 /**
  * \note this will configure the uart for 115200 baud 8-N-1, no HW flow control
@@ -58,7 +59,7 @@
 void UART0_intHandler(void);
 
 /**
- * Function documented in platform/uart.h
+ * Function documented in utils/uart.h
  */
 otError otPlatUartEnable(void)
 {
@@ -86,7 +87,7 @@
 }
 
 /**
- * Function documented in platform/uart.h
+ * Function documented in utils/uart.h
  */
 otError otPlatUartDisable(void)
 {
@@ -109,7 +110,7 @@
 }
 
 /**
- * Function documented in platform/uart.h
+ * Function documented in utils/uart.h
  */
 otError otPlatUartSend(const uint8_t *aBuf, uint16_t aBufLength)
 {
diff --git a/examples/platforms/cc2652/openthread-core-cc2652-config.h b/examples/platforms/cc2652/openthread-core-cc2652-config.h
index 352c322..b9dccf6 100644
--- a/examples/platforms/cc2652/openthread-core-cc2652-config.h
+++ b/examples/platforms/cc2652/openthread-core-cc2652-config.h
@@ -48,11 +48,11 @@
 #define OPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE 1
 
 /**
- * @def OPENTHREAD_CONFIG_NCP_UART_ENABLE
+ * @def OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
  *
- * Define to 1 to enable NCP UART support.
+ * Define to 1 to enable NCP HDLC support.
  *
  */
-#define OPENTHREAD_CONFIG_NCP_UART_ENABLE 1
+#define OPENTHREAD_CONFIG_NCP_HDLC_ENABLE 1
 
 #endif /* OPENTHREAD_CORE_CC2650_CONFIG_H_ */
diff --git a/examples/platforms/cc2652/uart.c b/examples/platforms/cc2652/uart.c
index b342a52..c8d951a 100644
--- a/examples/platforms/cc2652/uart.c
+++ b/examples/platforms/cc2652/uart.c
@@ -41,11 +41,9 @@
 
 #include <openthread/platform/debug_uart.h>
 #include <openthread/platform/logging.h>
-#include <openthread/platform/uart.h>
 
 #include "utils/code_utils.h"
-
-#include <utils/code_utils.h>
+#include "utils/uart.h"
 
 #include <driverlib/ioc.h>
 #include <driverlib/prcm.h>
@@ -116,7 +114,7 @@
 }
 
 /**
- * Function documented in platform/uart.h
+ * Function documented in utils/uart.h
  */
 otError otPlatUartEnable(void)
 {
@@ -138,7 +136,7 @@
 }
 
 /**
- * Function documented in platform/uart.h
+ * Function documented in utils/uart.h
  */
 otError otPlatUartDisable(void)
 {
@@ -154,7 +152,7 @@
 }
 
 /**
- * Function documented in platform/uart.h
+ * Function documented in utils/uart.h
  */
 otError otPlatUartSend(const uint8_t *aBuf, uint16_t aBufLength)
 {
diff --git a/examples/platforms/efr32/CMakeLists.txt b/examples/platforms/efr32/CMakeLists.txt
new file mode 100644
index 0000000..00bee25
--- /dev/null
+++ b/examples/platforms/efr32/CMakeLists.txt
@@ -0,0 +1,143 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+include(${PROJECT_SOURCE_DIR}/third_party/silabs/cmake/utility.cmake)
+include(${PROJECT_SOURCE_DIR}/third_party/silabs/cmake/includes.cmake)
+include(${PROJECT_SOURCE_DIR}/third_party/silabs/cmake/imported_libs.cmake)
+
+
+# ==============================================================================
+# Common sources and includes
+# ==============================================================================
+set(EFR32_COMMON_SOURCES
+    src/alarm.c
+    src/diag.c
+    src/entropy.c
+    src/fem-control.c
+    src/flash.c
+    src/ieee802154mac.h
+    src/logging.c
+    src/mbedtls_config.h
+    src/memory.c
+    src/misc.c
+    src/openthread-core-efr32-config-check.h
+    src/openthread-core-efr32-config.h
+    src/platform-band.h
+    src/platform-efr32.h
+    src/radio.c
+    src/rail_config.h
+    src/startup-gcc.c
+    src/system.c
+    src/uart.c
+)
+
+set(EFR32_INCLUDES
+    ${CMAKE_CURRENT_SOURCE_DIR}/src
+    ${CMAKE_CURRENT_SOURCE_DIR}/${EFR32_PLATFORM}/crypto
+    ${CMAKE_CURRENT_SOURCE_DIR}/${EFR32_PLATFORM}/${BOARD_LOWERCASE}
+    ${PROJECT_SOURCE_DIR}/third_party/silabs/rail_config
+    ${SILABS_GSDK_INCLUDES}
+    ${PROJECT_SOURCE_DIR}/examples/platforms
+    ${PROJECT_SOURCE_DIR}/src/core
+)
+
+set(EFR32_CFLAGS
+    -Wno-sign-compare
+    -Wno-unused-parameter
+    -Wno-missing-field-initializers
+)
+
+# ==============================================================================
+# mbedtls
+# ==============================================================================
+if(NOT OT_EXTERNAL_MBEDTLS)
+    message(FATAL_ERROR "OT_EXTERNAL_MBEDTLS is not set. Please include the define when running cmake\n\t-DOT_EXTERNAL_MBEDTLS=silabs-mbedtls")
+else()
+    list(APPEND OT_PLATFORM_DEFINES "MBEDTLS_CONFIG_FILE=\"mbedtls_config.h\"")
+endif()
+set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE)
+
+if(PLATFORM_LOWERCASE MATCHES "^efr32mg2\\d*")
+    list(APPEND OT_PUBLIC_INCLUDES
+        "${SILABS_GSDK_DIR}/util/third_party/crypto/sl_component/se_manager/inc"
+        "${SILABS_GSDK_DIR}/util/third_party/crypto/sl_component/se_manager/src"
+    )
+    set(OT_PUBLIC_INCLUDES ${OT_PUBLIC_INCLUDES} PARENT_SCOPE)
+endif()
+
+# ==============================================================================
+# General variables
+# ==============================================================================
+
+# ==============================================================================
+# Filter out CFLAGS
+# ==============================================================================
+set(ignored_flags
+    "-pedantic-errors"  # Needed for RAIL lib
+    "-Wshadow"          # Needed for RAIL lib
+)
+
+foreach(flag IN LISTS ignored_flags)
+    string(REPLACE "${flag}" "" OT_CFLAGS "${OT_CFLAGS}")
+endforeach()
+list(APPEND EFR32_CFLAGS ${OT_CFLAGS})
+set(OT_CFLAGS ${OT_CFLAGS} PARENT_SCOPE)
+
+# Use default config file if one isn't specified
+if(NOT OT_CONFIG)
+    set(OT_CONFIG "${CMAKE_CURRENT_SOURCE_DIR}/src/openthread-core-efr32-config.h")
+    set(OT_CONFIG ${OT_CONFIG} PARENT_SCOPE)
+endif()
+
+list(APPEND OT_PLATFORM_DEFINES
+    "OPENTHREAD_PROJECT_CORE_CONFIG_FILE=\"${OT_CONFIG}\""
+    "OPENTHREAD_CORE_CONFIG_PLATFORM_CHECK_FILE=\"openthread-core-efr32-config-check.h\""
+)
+set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE)
+
+# ==============================================================================
+# Platform targets
+# ==============================================================================
+set(EFR32_COMMON_3RD_LIBS
+    jlinkrtt
+    ${OT_MBEDTLS}
+)
+
+include(${PLATFORM_LOWERCASE}/${PLATFORM_LOWERCASE}.cmake)
+
+list(APPEND OT_PUBLIC_INCLUDES ${EFR32_INCLUDES})
+set(OT_PUBLIC_INCLUDES ${OT_PUBLIC_INCLUDES} PARENT_SCOPE)
+
+# ==============================================================================
+# sleepy-demo
+# ==============================================================================
+option(EFR32_APP_SLEEPY_DEMO "enable sleepy-demo app" ON)
+
+if(EFR32_APP_SLEEPY_DEMO)
+    add_subdirectory(sleepy-demo)
+endif()
diff --git a/examples/platforms/efr32/Makefile.platform.am b/examples/platforms/efr32/Makefile.platform.am
index 1d53802..b1a4afb 100644
--- a/examples/platforms/efr32/Makefile.platform.am
+++ b/examples/platforms/efr32/Makefile.platform.am
@@ -55,7 +55,7 @@
 LDADD_COMMON                                                                  += \
     $(top_builddir)/examples/platforms/efr32/libopenthread-$(PLATFORM_LOWERCASE).a   \
     $(top_builddir)/third_party/silabs/libsilabs-$(PLATFORM_LOWERCASE)-sdk.a         \
-    $(top_srcdir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/radio/rail_lib/autogen/librail_release/$(LIBRAIL) \
+    $(top_srcdir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/radio/rail_lib/autogen/librail_release/$(LIBRAIL) \
     $(NULL)
 
 LDFLAGS_COMMON                                                                       += \
diff --git a/examples/platforms/efr32/arm-none-eabi.cmake b/examples/platforms/efr32/arm-none-eabi.cmake
new file mode 100644
index 0000000..9f2e385
--- /dev/null
+++ b/examples/platforms/efr32/arm-none-eabi.cmake
@@ -0,0 +1,51 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+set(CMAKE_SYSTEM_NAME              Generic)
+set(CMAKE_SYSTEM_PROCESSOR         ARM)
+
+set(CMAKE_C_COMPILER               arm-none-eabi-gcc)
+set(CMAKE_CXX_COMPILER             arm-none-eabi-g++)
+set(CMAKE_ASM_COMPILER             arm-none-eabi-as)
+set(CMAKE_RANLIB                   arm-none-eabi-ranlib)
+
+execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE COMPILER_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+set(COMMON_C_FLAGS                 "-mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=softfp -mthumb -fdata-sections -ffunction-sections")
+
+set(CMAKE_C_FLAGS_INIT             "${COMMON_C_FLAGS} -std=c99")
+set(CMAKE_CXX_FLAGS_INIT           "${COMMON_C_FLAGS} -fno-exceptions -fno-rtti")
+set(CMAKE_EXE_LINKER_FLAGS_INIT    "${COMMON_C_FLAGS} -specs=nano.specs -specs=nosys.specs")
+
+set(CMAKE_C_FLAGS_DEBUG            "-Og -g")
+set(CMAKE_CXX_FLAGS_DEBUG          "-Og -g")
+set(CMAKE_ASM_FLAGS_DEBUG          "-g")
+
+set(CMAKE_C_FLAGS_RELEASE          "-Os")
+set(CMAKE_CXX_FLAGS_RELEASE        "-Os")
+set(CMAKE_ASM_FLAGS_RELEASE        "")
diff --git a/examples/platforms/efr32/efr32_platform_defs.am b/examples/platforms/efr32/efr32_platform_defs.am
index 68d7da6..f69d240 100644
--- a/examples/platforms/efr32/efr32_platform_defs.am
+++ b/examples/platforms/efr32/efr32_platform_defs.am
@@ -30,7 +30,7 @@
 # ==============================================================================
 # General variables
 # ==============================================================================
-SDK_SRC_DIR = $(top_srcdir)/third_party/silabs/gecko_sdk_suite/v3.0
+SDK_SRC_DIR = $(top_srcdir)/third_party/silabs/gecko_sdk_suite/v3.1
 
 EFR32_PLATFORM = $(subst /,,$(exec_prefix))
 # NOTE: This is a work-around that sets EFR32_PLATFORM to the correct platform name.
@@ -67,6 +67,8 @@
 # ==============================================================================
 SILABS_GSDK_CPPFLAGS                                                          = \
     -I$(SDK_SRC_DIR)                                                            \
+    -I$(SDK_SRC_DIR)/hardware/board/config/$(BOARD_LOWERCASE)_brd4001a          \
+    -I$(SDK_SRC_DIR)/hardware/board/config/$(BOARD_LOWERCASE)                   \
     -I$(SDK_SRC_DIR)/hardware/kit/$(PLATFORM_UPPERCASE)_$(BOARD_UPPERCASE)/config \
     -I$(SDK_SRC_DIR)/hardware/kit/common/bsp                                    \
     -I$(SDK_SRC_DIR)/hardware/kit/common/drivers                                \
@@ -87,7 +89,6 @@
     -I$(SDK_SRC_DIR)/platform/emlib/inc                                         \
     -I$(SDK_SRC_DIR)/platform/halconfig/inc/hal-config                          \
     -I$(SDK_SRC_DIR)/platform/radio/rail_lib/chip/efr32                         \
-    -I$(SDK_SRC_DIR)/platform/radio/rail_lib/chip/efr32/rf/common/cortex        \
     -I$(SDK_SRC_DIR)/platform/radio/rail_lib/common                             \
     -I$(SDK_SRC_DIR)/platform/radio/rail_lib/hal                                \
     -I$(SDK_SRC_DIR)/platform/radio/rail_lib/hal/efr32                          \
@@ -98,9 +99,6 @@
     -I$(SDK_SRC_DIR)/platform/service/sleeptimer/config                         \
     -I$(SDK_SRC_DIR)/platform/service/sleeptimer/inc                            \
     -I$(SDK_SRC_DIR)/util/plugin/plugin-common/fem-control                      \
-    -I$(SDK_SRC_DIR)/util/third_party/mbedtls/configs                           \
-    -I$(SDK_SRC_DIR)/util/third_party/mbedtls/include                           \
-    -I$(SDK_SRC_DIR)/util/third_party/mbedtls/sl_crypto/include                 \
     $(NULL)
 
 
@@ -117,6 +115,8 @@
     -I$(SDK_SRC_DIR)/platform/emdrv/nvm3/config/s2                              \
     -I$(SDK_SRC_DIR)/platform/radio/rail_lib/chip/efr32/efr32xg2x               \
     -I$(SDK_SRC_DIR)/platform/radio/rail_lib/plugin/pa-conversions/efr32xg21/config \
+    -I$(SDK_SRC_DIR)/util/third_party/crypto/sl_component/se_manager/inc        \
+    -I$(SDK_SRC_DIR)/util/third_party/crypto/sl_component/se_manager/src        \
     $(NULL)
 
 
@@ -130,6 +130,7 @@
     src/fem-control.c                                                           \
     src/flash.c                                                                 \
     src/logging.c                                                               \
+    src/memory.c                                                                \
     src/misc.c                                                                  \
     src/openthread-core-efr32-config-check.h                                    \
     src/openthread-core-efr32-config.h                                          \
diff --git a/examples/platforms/efr32/efr32mg1/Makefile.am b/examples/platforms/efr32/efr32mg1/Makefile.am
index 873a884..fdf9e1e 100644
--- a/examples/platforms/efr32/efr32mg1/Makefile.am
+++ b/examples/platforms/efr32/efr32mg1/Makefile.am
@@ -33,7 +33,6 @@
 
 libopenthread_efr32mg1_a_CPPFLAGS                                             = \
     -DPLATFORM_HEADER=\"platform/base/hal/micro/cortexm3/compiler/gcc.h\"       \
-    -DNVIC_CONFIG=\"platform/base/hal/micro/cortexm3/efm32/nvic-config.h\"      \
     -Wno-sign-compare                                                           \
     -I$(top_srcdir)/examples/platforms                                          \
     -I$(top_srcdir)/examples/platforms/efr32/$(PLATFORM_LOWERCASE)/$(BOARD_LOWERCASE) \
diff --git a/examples/platforms/efr32/efr32mg1/Makefile.platform.am b/examples/platforms/efr32/efr32mg1/Makefile.platform.am
index c3977f3..22912f6 100644
--- a/examples/platforms/efr32/efr32mg1/Makefile.platform.am
+++ b/examples/platforms/efr32/efr32mg1/Makefile.platform.am
@@ -31,5 +31,5 @@
 #
 
 LDADD_COMMON                                                                  += \
-    $(top_srcdir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/emdrv/nvm3/lib/libnvm3_CM4_gcc.a \
+    $(top_srcdir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/emdrv/nvm3/lib/libnvm3_CM4_gcc.a \
     $(NULL)
diff --git a/examples/platforms/efr32/efr32mg1/README.md b/examples/platforms/efr32/efr32mg1/README.md
index 0aa543e..2473f2c 100644
--- a/examples/platforms/efr32/efr32mg1/README.md
+++ b/examples/platforms/efr32/efr32mg1/README.md
@@ -28,8 +28,8 @@
 
 2. Install Flex (Gecko) SDK including RAIL Library from Simplicity Studio.
    - Connect EFR32MG1P Wireless Starter Kit to Simplicity Studio.
-   - Find Flex SDK v3.0 in the Software Update page and click Install.
-   - Flex SDK v3.0 will be installed in the path:
+   - Find Flex SDK v3.1 in the Software Update page and click Install.
+   - Flex SDK v3.1 will be installed in the path:
      - Mac
        - `/Applications/Simplicity\ Studio.app/Contents/Eclipse/developer/sdks/gecko_sdk_suite`
      - Windows
@@ -66,7 +66,35 @@
 $ ./bootstrap
 ```
 
-For EFR32MG1™ Mighty Gecko Wireless Starter Kit:
+For EFR32MG1™ Mighty Gecko Wireless Starter Kit, this can be done using both the CMake and autotools build systems
+
+**CMake (preferred)**
+
+```bash
+$ ./script/cmake-build efr32mg1 -DBOARD=brd4151a
+...
+-- Configuring done
+-- Generating done
+-- Build files have been written to: <path-to-openthread>/build/efr32mg1
++ [[ -n ot-rcp ]]
++ ninja ot-rcp
+[160/160] Linking CXX executable examples/apps/ncp/ot-rcp
++ cd <path-to-openthread>
+```
+
+After a successful build, the `elf` files are found in `<path-to-openthread>/build/efr32mg1/examples`.
+
+```bash
+# For linux
+$ find build/efr32mg1/examples -type f -executable
+build/efr32mg1/examples/apps/ncp/ot-rcp
+
+# For BSD/Darwin/mac systems
+$ find build/efr32mg1/examples -type f -perm +111
+build/efr32mg1/examples/apps/ncp/ot-rcp
+```
+
+**autotools (soon to be depracated)**
 
 ```bash
 $ make -f examples/Makefile-efr32mg1 BOARD=BRD4151A
@@ -234,7 +262,7 @@
 
 For a list of all available commands, visit [OpenThread CLI Reference README.md][cli].
 
-[cli]: https://github.com/openthread/openthread/blob/master/src/cli/README.md
+[cli]: https://github.com/openthread/openthread/blob/main/src/cli/README.md
 
 ## Verification
 
@@ -244,4 +272,4 @@
 
 The EFR32 example has been verified with following Flex SDK/RAIL Library version:
 
-- Flex SDK version 3.0.x
+- Flex SDK v3.1.x
diff --git a/examples/platforms/efr32/efr32mg1/arm-none-eabi.cmake b/examples/platforms/efr32/efr32mg1/arm-none-eabi.cmake
new file mode 100644
index 0000000..9f2e385
--- /dev/null
+++ b/examples/platforms/efr32/efr32mg1/arm-none-eabi.cmake
@@ -0,0 +1,51 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+set(CMAKE_SYSTEM_NAME              Generic)
+set(CMAKE_SYSTEM_PROCESSOR         ARM)
+
+set(CMAKE_C_COMPILER               arm-none-eabi-gcc)
+set(CMAKE_CXX_COMPILER             arm-none-eabi-g++)
+set(CMAKE_ASM_COMPILER             arm-none-eabi-as)
+set(CMAKE_RANLIB                   arm-none-eabi-ranlib)
+
+execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE COMPILER_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+set(COMMON_C_FLAGS                 "-mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=softfp -mthumb -fdata-sections -ffunction-sections")
+
+set(CMAKE_C_FLAGS_INIT             "${COMMON_C_FLAGS} -std=c99")
+set(CMAKE_CXX_FLAGS_INIT           "${COMMON_C_FLAGS} -fno-exceptions -fno-rtti")
+set(CMAKE_EXE_LINKER_FLAGS_INIT    "${COMMON_C_FLAGS} -specs=nano.specs -specs=nosys.specs")
+
+set(CMAKE_C_FLAGS_DEBUG            "-Og -g")
+set(CMAKE_CXX_FLAGS_DEBUG          "-Og -g")
+set(CMAKE_ASM_FLAGS_DEBUG          "-g")
+
+set(CMAKE_C_FLAGS_RELEASE          "-Os")
+set(CMAKE_CXX_FLAGS_RELEASE        "-Os")
+set(CMAKE_ASM_FLAGS_RELEASE        "")
diff --git a/examples/platforms/efr32/efr32mg1/crypto/mbedtls_config_autogen.h b/examples/platforms/efr32/efr32mg1/crypto/mbedtls_config_autogen.h
index 0104fdf..5f839fd 100644
--- a/examples/platforms/efr32/efr32mg1/crypto/mbedtls_config_autogen.h
+++ b/examples/platforms/efr32/efr32mg1/crypto/mbedtls_config_autogen.h
@@ -24,10 +24,10 @@
 #define MBEDTLS_ECP_C
 #define MBEDTLS_ECP_DP_SECP256R1_ENABLED
 #define MBEDTLS_ECDH_C
+#define MBEDTLS_ECDH_LEGACY_CONTEXT
 #define MBEDTLS_ECDSA_C
 #define MBEDTLS_ENTROPY_ADC_C
-#define MBEDTLS_ENTROPY_RAIL_C
-#define MBEDTLS_ENTROPY_HARDWARE_ALT_RAIL
+#define MBEDTLS_ENTROPY_HARDWARE_ALT
 #define MBEDTLS_MD_C
 #define MBEDTLS_ECJPAKE_C
 #define MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED
@@ -37,14 +37,12 @@
 #define MBEDTLS_ENTROPY_FORCE_SHA256
 #define MBEDTLS_ENTROPY_MAX_SOURCES 2
 #define MBEDTLS_NO_PLATFORM_ENTROPY
-#define MBEDTLS_ENTROPY_HARDWARE_ALT
 #define MBEDTLS_CTR_DRBG_C
-#define MBEDTLS_SHA1_C
 #define MBEDTLS_SHA256_C
-#define MBEDTLS_SHA512_C
 #define MBEDTLS_SSL_TLS_C
 #define MBEDTLS_SSL_CLI_C
 #define MBEDTLS_SSL_PROTO_TLS1_2
+#define MBEDTLS_SSL_KEEP_PEER_CERTIFICATE
 #define MBEDTLS_SSL_SRV_C
 #define MBEDTLS_X509_USE_C
 #define MBEDTLS_X509_CRT_PARSE_C
@@ -53,13 +51,17 @@
 #define MBEDTLS_OID_C
 #define MBEDTLS_PK_C
 #define MBEDTLS_PK_PARSE_C
+#define MBEDTLS_PSA_CRYPTO_DRIVERS
 
 #include "config-device-acceleration.h"
 
+#if !defined(TEST_SUITE_MEMORY_BUFFER_ALLOC)
 #include "sl_malloc.h"
 
 #define MBEDTLS_PLATFORM_FREE_MACRO sl_free
 #define MBEDTLS_PLATFORM_CALLOC_MACRO sl_calloc
+#endif
+
 #define MBEDTLS_PLATFORM_MEMORY
 #define MBEDTLS_PLATFORM_C
 
diff --git a/examples/platforms/efr32/efr32mg1/efr32mg1.cmake b/examples/platforms/efr32/efr32mg1/efr32mg1.cmake
new file mode 100644
index 0000000..39e5da5
--- /dev/null
+++ b/examples/platforms/efr32/efr32mg1/efr32mg1.cmake
@@ -0,0 +1,95 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+# ==============================================================================
+# Verify board is supported for platform
+# ==============================================================================
+if(BOARD_LOWERCASE STREQUAL "brd4151a")
+    set(MCU "EFR32MG1P232F256GM48")
+else()
+    message(FATAL_ERROR "
+    BOARD=${BOARD} not supported.
+
+    Please provide a value for BOARD variable e.g BOARD=brd4151a.
+    Currently supported:
+    - brd4151a
+    ")
+endif()
+
+list(APPEND OT_PLATFORM_DEFINES "${MCU}")
+set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE)
+
+# ==============================================================================
+# Platform library
+# ==============================================================================
+set(OT_PLATFORM_LIB "openthread-efr32mg1")
+set(OT_PLATFORM_LIB ${OT_PLATFORM_LIB} PARENT_SCOPE)
+
+set(LD_FILE "${CMAKE_CURRENT_SOURCE_DIR}/efr32mg1/efr32mg1.ld")
+
+add_library(openthread-efr32mg1
+    ${EFR32_COMMON_SOURCES}
+    $<TARGET_OBJECTS:openthread-platform-utils>
+)
+
+set_target_properties(openthread-efr32mg1
+    PROPERTIES
+        C_STANDARD 99
+        CXX_STANDARD 11
+)
+
+target_link_libraries(openthread-efr32mg1
+    PUBLIC
+        ${EFR32_COMMON_3RD_LIBS}
+        silabs-libnvm3_CM4_gcc
+        silabs-efr32mg1-sdk
+        -T${LD_FILE}
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+    PRIVATE
+        ot-config
+)
+
+target_compile_definitions(openthread-efr32mg1
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_options(openthread-efr32mg1
+    PRIVATE
+        ${EFR32_CFLAGS}
+)
+
+target_include_directories(openthread-efr32mg1
+    PUBLIC
+        ${EFR32_INCLUDES}
+    PRIVATE
+        ${SILABS_EFR32MG1X_INCLUDES}
+        ${OT_PUBLIC_INCLUDES}
+)
+
diff --git a/examples/platforms/efr32/efr32mg12/Makefile.am b/examples/platforms/efr32/efr32mg12/Makefile.am
index 73cefb5..0235236 100644
--- a/examples/platforms/efr32/efr32mg12/Makefile.am
+++ b/examples/platforms/efr32/efr32mg12/Makefile.am
@@ -33,7 +33,6 @@
 
 libopenthread_efr32mg12_a_CPPFLAGS                                            = \
     -DPLATFORM_HEADER=\"platform/base/hal/micro/cortexm3/compiler/gcc.h\"       \
-    -DNVIC_CONFIG=\"platform/base/hal/micro/cortexm3/efm32/nvic-config.h\"      \
     -Wno-sign-compare                                                           \
     -I$(top_srcdir)/examples/platforms                                          \
     -I$(top_srcdir)/examples/platforms/efr32/$(PLATFORM_LOWERCASE)/$(BOARD_LOWERCASE) \
diff --git a/examples/platforms/efr32/efr32mg12/Makefile.platform.am b/examples/platforms/efr32/efr32mg12/Makefile.platform.am
index 3b4799a..df29d3f 100644
--- a/examples/platforms/efr32/efr32mg12/Makefile.platform.am
+++ b/examples/platforms/efr32/efr32mg12/Makefile.platform.am
@@ -31,5 +31,5 @@
 #
 
 LDADD_COMMON                                                                  += \
-    $(top_srcdir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/emdrv/nvm3/lib/libnvm3_CM4_gcc.a \
+    $(top_srcdir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/emdrv/nvm3/lib/libnvm3_CM4_gcc.a \
     $(NULL)
diff --git a/examples/platforms/efr32/efr32mg12/README.md b/examples/platforms/efr32/efr32mg12/README.md
index 1facf1f..713e0dc 100644
--- a/examples/platforms/efr32/efr32mg12/README.md
+++ b/examples/platforms/efr32/efr32mg12/README.md
@@ -33,8 +33,8 @@
 
 2. Install Flex (Gecko) SDK including RAIL Library from Simplicity Studio.
    - Connect EFR32MG12P Wireless Starter Kit to Simplicity Studio.
-   - Find Flex SDK v3.0 in the Software Update page and click Install.
-   - Flex SDK v3.0 will be installed in the path:
+   - Find Flex SDK v3.1 in the Software Update page and click Install.
+   - Flex SDK v3.1 will be installed in the path:
      - Mac
        - `/Applications/Simplicity\ Studio.app/Contents/Eclipse/developer/sdks/gecko_sdk_suite`
      - Windows
@@ -71,7 +71,47 @@
 $ ./bootstrap
 ```
 
-For EFR32MG12™ Mighty Gecko Wireless Starter Kit:
+For EFR32MG12™ Mighty Gecko Wireless Starter Kit, this can be done using both the CMake and autotools build systems
+
+**CMake (preferred)**
+
+```bash
+$ ./script/cmake-build efr32mg12 -DBOARD=brd4161a
+...
+-- Configuring done
+-- Generating done
+-- Build files have been written to: <path-to-openthread>/build/efr32mg12
++ [[ -n ot-rcp ot-cli-ftd ot-cli-mtd ot-ncp-ftd ot-ncp-mtd sleepy-demo-ftd sleepy-demo-mtd ]]
++ ninja ot-rcp ot-cli-ftd ot-cli-mtd ot-ncp-ftd ot-ncp-mtd sleepy-demo-ftd sleepy-demo-mtd
+[572/572] Linking CXX executable examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/sleepy-demo-ftd
++ cd <path-to-openthread>
+```
+
+After a successful build, the `elf` files are found in `<path-to-openthread>/build/efr32mg12/examples`.
+
+```bash
+# For linux
+$ find build/efr32mg12/examples -type f -executable
+build/efr32mg12/examples/apps/cli/ot-cli-mtd
+build/efr32mg12/examples/apps/cli/ot-cli-ftd
+build/efr32mg12/examples/apps/ncp/ot-ncp-ftd
+build/efr32mg12/examples/apps/ncp/ot-ncp-mtd
+build/efr32mg12/examples/apps/ncp/ot-rcp
+build/efr32mg12/examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/sleepy-demo-ftd
+build/efr32mg12/examples/platforms/efr32/sleepy-demo/sleepy-demo-mtd/sleepy-demo-mtd
+
+# For BSD/Darwin/mac systems
+$ find build/efr32mg12/examples -type f -perm +111
+build/efr32mg12/examples/apps/cli/ot-cli-mtd
+build/efr32mg12/examples/apps/cli/ot-cli-ftd
+build/efr32mg12/examples/apps/ncp/ot-ncp-ftd
+build/efr32mg12/examples/apps/ncp/ot-ncp-mtd
+build/efr32mg12/examples/apps/ncp/ot-rcp
+build/efr32mg12/examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/sleepy-demo-ftd
+build/efr32mg12/examples/platforms/efr32/sleepy-demo/sleepy-demo-mtd/sleepy-demo-mtd
+```
+
+**autotools (soon to be depracated)**
 
 ```bash
 $ make -f examples/Makefile-efr32mg12 BOARD=BRD4161A
@@ -247,7 +287,7 @@
 
 For a list of all available commands, visit [OpenThread CLI Reference README.md][cli].
 
-[cli]: https://github.com/openthread/openthread/blob/master/src/cli/README.md
+[cli]: https://github.com/openthread/openthread/blob/main/src/cli/README.md
 
 ## Verification
 
@@ -257,4 +297,4 @@
 
 The EFR32 example has been verified with following Flex SDK/RAIL Library version:
 
-- Flex SDK version 3.0.x
+- Flex SDK v3.1.x
diff --git a/examples/platforms/efr32/efr32mg12/arm-none-eabi.cmake b/examples/platforms/efr32/efr32mg12/arm-none-eabi.cmake
new file mode 100644
index 0000000..9f2e385
--- /dev/null
+++ b/examples/platforms/efr32/efr32mg12/arm-none-eabi.cmake
@@ -0,0 +1,51 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+set(CMAKE_SYSTEM_NAME              Generic)
+set(CMAKE_SYSTEM_PROCESSOR         ARM)
+
+set(CMAKE_C_COMPILER               arm-none-eabi-gcc)
+set(CMAKE_CXX_COMPILER             arm-none-eabi-g++)
+set(CMAKE_ASM_COMPILER             arm-none-eabi-as)
+set(CMAKE_RANLIB                   arm-none-eabi-ranlib)
+
+execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE COMPILER_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+set(COMMON_C_FLAGS                 "-mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=softfp -mthumb -fdata-sections -ffunction-sections")
+
+set(CMAKE_C_FLAGS_INIT             "${COMMON_C_FLAGS} -std=c99")
+set(CMAKE_CXX_FLAGS_INIT           "${COMMON_C_FLAGS} -fno-exceptions -fno-rtti")
+set(CMAKE_EXE_LINKER_FLAGS_INIT    "${COMMON_C_FLAGS} -specs=nano.specs -specs=nosys.specs")
+
+set(CMAKE_C_FLAGS_DEBUG            "-Og -g")
+set(CMAKE_CXX_FLAGS_DEBUG          "-Og -g")
+set(CMAKE_ASM_FLAGS_DEBUG          "-g")
+
+set(CMAKE_C_FLAGS_RELEASE          "-Os")
+set(CMAKE_CXX_FLAGS_RELEASE        "-Os")
+set(CMAKE_ASM_FLAGS_RELEASE        "")
diff --git a/examples/platforms/efr32/efr32mg12/crypto/mbedtls_config_autogen.h b/examples/platforms/efr32/efr32mg12/crypto/mbedtls_config_autogen.h
index 42240cc..5f839fd 100644
--- a/examples/platforms/efr32/efr32mg12/crypto/mbedtls_config_autogen.h
+++ b/examples/platforms/efr32/efr32mg12/crypto/mbedtls_config_autogen.h
@@ -24,9 +24,10 @@
 #define MBEDTLS_ECP_C
 #define MBEDTLS_ECP_DP_SECP256R1_ENABLED
 #define MBEDTLS_ECDH_C
+#define MBEDTLS_ECDH_LEGACY_CONTEXT
 #define MBEDTLS_ECDSA_C
 #define MBEDTLS_ENTROPY_ADC_C
-#define MBEDTLS_ENTROPY_RAIL_C
+#define MBEDTLS_ENTROPY_HARDWARE_ALT
 #define MBEDTLS_MD_C
 #define MBEDTLS_ECJPAKE_C
 #define MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED
@@ -36,14 +37,12 @@
 #define MBEDTLS_ENTROPY_FORCE_SHA256
 #define MBEDTLS_ENTROPY_MAX_SOURCES 2
 #define MBEDTLS_NO_PLATFORM_ENTROPY
-#define MBEDTLS_ENTROPY_HARDWARE_ALT
 #define MBEDTLS_CTR_DRBG_C
-#define MBEDTLS_SHA1_C
 #define MBEDTLS_SHA256_C
-#define MBEDTLS_SHA512_C
 #define MBEDTLS_SSL_TLS_C
 #define MBEDTLS_SSL_CLI_C
 #define MBEDTLS_SSL_PROTO_TLS1_2
+#define MBEDTLS_SSL_KEEP_PEER_CERTIFICATE
 #define MBEDTLS_SSL_SRV_C
 #define MBEDTLS_X509_USE_C
 #define MBEDTLS_X509_CRT_PARSE_C
@@ -52,17 +51,17 @@
 #define MBEDTLS_OID_C
 #define MBEDTLS_PK_C
 #define MBEDTLS_PK_PARSE_C
+#define MBEDTLS_PSA_CRYPTO_DRIVERS
 
 #include "config-device-acceleration.h"
 
-#if defined(MBEDTLS_TRNG_PRESENT)
-#define MBEDTLS_TRNG_C
-#endif
-
+#if !defined(TEST_SUITE_MEMORY_BUFFER_ALLOC)
 #include "sl_malloc.h"
 
 #define MBEDTLS_PLATFORM_FREE_MACRO sl_free
 #define MBEDTLS_PLATFORM_CALLOC_MACRO sl_calloc
+#endif
+
 #define MBEDTLS_PLATFORM_MEMORY
 #define MBEDTLS_PLATFORM_C
 
diff --git a/examples/platforms/efr32/efr32mg12/efr32mg12.cmake b/examples/platforms/efr32/efr32mg12/efr32mg12.cmake
new file mode 100644
index 0000000..ad73e5f
--- /dev/null
+++ b/examples/platforms/efr32/efr32mg12/efr32mg12.cmake
@@ -0,0 +1,104 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+# ==============================================================================
+# Verify board is supported for platform
+# ==============================================================================
+if(BOARD_LOWERCASE STREQUAL "brd4304a")
+    set(MCU "EFR32MG12P432F1024GM48")
+elseif(BOARD_LOWERCASE STREQUAL "brd4161a")
+    set(MCU "EFR32MG12P432F1024GL125")
+elseif(BOARD_LOWERCASE STREQUAL "brd4166a")
+    set(MCU "EFR32MG12P332F1024GL125")
+elseif(BOARD_LOWERCASE STREQUAL "brd4170a")
+    set(MCU "EFR32MG12P433F1024GM68")
+else()
+    message(FATAL_ERROR "
+    BOARD=${BOARD} not supported.
+
+    Please provide a value for BOARD variable e.g BOARD=brd4161a.
+    Currently supported:
+    - brd4161a
+    - brd4166a
+    - brd4170a
+    - brd4304a
+    ")
+endif()
+
+list(APPEND OT_PLATFORM_DEFINES "${MCU}")
+set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE)
+
+# ==============================================================================
+# Platform library
+# ==============================================================================
+set(OT_PLATFORM_LIB "openthread-efr32mg12")
+set(OT_PLATFORM_LIB ${OT_PLATFORM_LIB} PARENT_SCOPE)
+
+set(LD_FILE "${CMAKE_CURRENT_SOURCE_DIR}/efr32mg12/efr32mg12.ld")
+
+add_library(openthread-efr32mg12
+    ${EFR32_COMMON_SOURCES}
+    $<TARGET_OBJECTS:openthread-platform-utils>
+)
+
+set_target_properties(openthread-efr32mg12
+    PROPERTIES
+        C_STANDARD 99
+        CXX_STANDARD 11
+)
+
+target_link_libraries(openthread-efr32mg12
+    PUBLIC
+        ${EFR32_COMMON_3RD_LIBS}
+        silabs-libnvm3_CM4_gcc
+        silabs-efr32mg12-sdk
+        -T${LD_FILE}
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+    PRIVATE
+        ot-config
+)
+
+target_compile_definitions(openthread-efr32mg12
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_options(openthread-efr32mg12
+    PRIVATE
+        ${EFR32_CFLAGS}
+)
+
+target_include_directories(openthread-efr32mg12
+    PUBLIC
+        ${EFR32_INCLUDES}
+    PRIVATE
+        ${SILABS_EFR32MG1X_INCLUDES}
+        ${OT_PUBLIC_INCLUDES}
+)
+
diff --git a/examples/platforms/efr32/efr32mg13/Makefile.am b/examples/platforms/efr32/efr32mg13/Makefile.am
index b50f029..cea711d 100644
--- a/examples/platforms/efr32/efr32mg13/Makefile.am
+++ b/examples/platforms/efr32/efr32mg13/Makefile.am
@@ -33,7 +33,6 @@
 
 libopenthread_efr32mg13_a_CPPFLAGS                                            = \
     -DPLATFORM_HEADER=\"platform/base/hal/micro/cortexm3/compiler/gcc.h\"       \
-    -DNVIC_CONFIG=\"platform/base/hal/micro/cortexm3/efm32/nvic-config.h\"      \
     -Wno-sign-compare                                                           \
     -I$(top_srcdir)/examples/platforms                                          \
     -I$(top_srcdir)/examples/platforms/efr32/$(PLATFORM_LOWERCASE)/$(BOARD_LOWERCASE) \
diff --git a/examples/platforms/efr32/efr32mg13/Makefile.platform.am b/examples/platforms/efr32/efr32mg13/Makefile.platform.am
index 47b48a4..f45ce08 100644
--- a/examples/platforms/efr32/efr32mg13/Makefile.platform.am
+++ b/examples/platforms/efr32/efr32mg13/Makefile.platform.am
@@ -31,5 +31,5 @@
 #
 
 LDADD_COMMON                                                                  += \
-    $(top_srcdir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/emdrv/nvm3/lib/libnvm3_CM4_gcc.a \
+    $(top_srcdir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/emdrv/nvm3/lib/libnvm3_CM4_gcc.a \
     $(NULL)
diff --git a/examples/platforms/efr32/efr32mg13/README.md b/examples/platforms/efr32/efr32mg13/README.md
index d38af3f..98a8c3c 100644
--- a/examples/platforms/efr32/efr32mg13/README.md
+++ b/examples/platforms/efr32/efr32mg13/README.md
@@ -32,8 +32,8 @@
 
 2. Install Flex (Gecko) SDK including RAIL Library from Simplicity Studio.
    - Connect EFR32MG13P Wireless Starter Kit to Simplicity Studio.
-   - Find Flex SDK v3.0 in the Software Update page and click Install.
-   - Flex SDK v3.0 will be installed in the path:
+   - Find Flex SDK v3.1 in the Software Update page and click Install.
+   - Flex SDK v3.1 will be installed in the path:
      - Mac
        - `/Applications/Simplicity\ Studio.app/Contents/Eclipse/developer/sdks/gecko_sdk_suite`
      - Windows
@@ -70,7 +70,47 @@
 $ ./bootstrap
 ```
 
-For EFR32MG13™ Mighty Gecko Wireless Starter Kit:
+For EFR32MG13™ Mighty Gecko Wireless Starter Kit, this can be done using both the CMake and autotools build systems
+
+**CMake (preferred)**
+
+```bash
+$ ./script/cmake-build efr32mg13 -DBOARD=brd4168a
+...
+-- Configuring done
+-- Generating done
+-- Build files have been written to: <path-to-openthread>/build/efr32mg13
++ [[ -n ot-rcp ot-cli-ftd ot-cli-mtd ot-ncp-ftd ot-ncp-mtd sleepy-demo-ftd sleepy-demo-mtd ]]
++ ninja ot-rcp ot-cli-ftd ot-cli-mtd ot-ncp-ftd ot-ncp-mtd sleepy-demo-ftd sleepy-demo-mtd
+[573/573] Linking CXX executable examples/apps/ncp/ot-ncp-ftd
++ cd <path-to-openthread>
+```
+
+After a successful build, the `elf` files are found in `<path-to-openthread>/build/efr32mg13/examples`.
+
+```bash
+# For linux
+$ find build/efr32mg13/examples -type f -executable
+build/efr32mg13/examples/apps/cli/ot-cli-mtd
+build/efr32mg13/examples/apps/cli/ot-cli-ftd
+build/efr32mg13/examples/apps/ncp/ot-ncp-ftd
+build/efr32mg13/examples/apps/ncp/ot-ncp-mtd
+build/efr32mg13/examples/apps/ncp/ot-rcp
+build/efr32mg13/examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/sleepy-demo-ftd
+build/efr32mg13/examples/platforms/efr32/sleepy-demo/sleepy-demo-mtd/sleepy-demo-mtd
+
+# For BSD/Darwin/mac systems
+$ find build/efr32mg13/examples -type f -perm +111
+build/efr32mg13/examples/apps/cli/ot-cli-mtd
+build/efr32mg13/examples/apps/cli/ot-cli-ftd
+build/efr32mg13/examples/apps/ncp/ot-ncp-ftd
+build/efr32mg13/examples/apps/ncp/ot-ncp-mtd
+build/efr32mg13/examples/apps/ncp/ot-rcp
+build/efr32mg13/examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/sleepy-demo-ftd
+build/efr32mg13/examples/platforms/efr32/sleepy-demo/sleepy-demo-mtd/sleepy-demo-mtd
+```
+
+**autotools (soon to be depracated)**
 
 ```bash
 $ make -f examples/Makefile-efr32mg13 BOARD=BRD4168A
@@ -240,7 +280,7 @@
 
 For a list of all available commands, visit [OpenThread CLI Reference README.md][cli].
 
-[cli]: https://github.com/openthread/openthread/blob/master/src/cli/README.md
+[cli]: https://github.com/openthread/openthread/blob/main/src/cli/README.md
 
 ## Verification
 
@@ -250,4 +290,4 @@
 
 The EFR32 example has been verified with following Flex SDK/RAIL Library version:
 
-- Flex SDK version 3.0.x
+- Flex SDK v3.1.x
diff --git a/examples/platforms/efr32/efr32mg13/arm-none-eabi.cmake b/examples/platforms/efr32/efr32mg13/arm-none-eabi.cmake
new file mode 100644
index 0000000..9f2e385
--- /dev/null
+++ b/examples/platforms/efr32/efr32mg13/arm-none-eabi.cmake
@@ -0,0 +1,51 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+set(CMAKE_SYSTEM_NAME              Generic)
+set(CMAKE_SYSTEM_PROCESSOR         ARM)
+
+set(CMAKE_C_COMPILER               arm-none-eabi-gcc)
+set(CMAKE_CXX_COMPILER             arm-none-eabi-g++)
+set(CMAKE_ASM_COMPILER             arm-none-eabi-as)
+set(CMAKE_RANLIB                   arm-none-eabi-ranlib)
+
+execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE COMPILER_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+set(COMMON_C_FLAGS                 "-mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=softfp -mthumb -fdata-sections -ffunction-sections")
+
+set(CMAKE_C_FLAGS_INIT             "${COMMON_C_FLAGS} -std=c99")
+set(CMAKE_CXX_FLAGS_INIT           "${COMMON_C_FLAGS} -fno-exceptions -fno-rtti")
+set(CMAKE_EXE_LINKER_FLAGS_INIT    "${COMMON_C_FLAGS} -specs=nano.specs -specs=nosys.specs")
+
+set(CMAKE_C_FLAGS_DEBUG            "-Og -g")
+set(CMAKE_CXX_FLAGS_DEBUG          "-Og -g")
+set(CMAKE_ASM_FLAGS_DEBUG          "-g")
+
+set(CMAKE_C_FLAGS_RELEASE          "-Os")
+set(CMAKE_CXX_FLAGS_RELEASE        "-Os")
+set(CMAKE_ASM_FLAGS_RELEASE        "")
diff --git a/examples/platforms/efr32/efr32mg13/crypto/mbedtls_config_autogen.h b/examples/platforms/efr32/efr32mg13/crypto/mbedtls_config_autogen.h
index c0a738c..5f839fd 100644
--- a/examples/platforms/efr32/efr32mg13/crypto/mbedtls_config_autogen.h
+++ b/examples/platforms/efr32/efr32mg13/crypto/mbedtls_config_autogen.h
@@ -24,10 +24,10 @@
 #define MBEDTLS_ECP_C
 #define MBEDTLS_ECP_DP_SECP256R1_ENABLED
 #define MBEDTLS_ECDH_C
+#define MBEDTLS_ECDH_LEGACY_CONTEXT
 #define MBEDTLS_ECDSA_C
 #define MBEDTLS_ENTROPY_ADC_C
-#define MBEDTLS_ENTROPY_RAIL_C
-#define MBEDTLS_ENTROPY_HARDWARE_ALT_RAIL
+#define MBEDTLS_ENTROPY_HARDWARE_ALT
 #define MBEDTLS_MD_C
 #define MBEDTLS_ECJPAKE_C
 #define MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED
@@ -37,14 +37,12 @@
 #define MBEDTLS_ENTROPY_FORCE_SHA256
 #define MBEDTLS_ENTROPY_MAX_SOURCES 2
 #define MBEDTLS_NO_PLATFORM_ENTROPY
-#define MBEDTLS_ENTROPY_HARDWARE_ALT
 #define MBEDTLS_CTR_DRBG_C
-#define MBEDTLS_SHA1_C
 #define MBEDTLS_SHA256_C
-#define MBEDTLS_SHA512_C
 #define MBEDTLS_SSL_TLS_C
 #define MBEDTLS_SSL_CLI_C
 #define MBEDTLS_SSL_PROTO_TLS1_2
+#define MBEDTLS_SSL_KEEP_PEER_CERTIFICATE
 #define MBEDTLS_SSL_SRV_C
 #define MBEDTLS_X509_USE_C
 #define MBEDTLS_X509_CRT_PARSE_C
@@ -53,17 +51,17 @@
 #define MBEDTLS_OID_C
 #define MBEDTLS_PK_C
 #define MBEDTLS_PK_PARSE_C
+#define MBEDTLS_PSA_CRYPTO_DRIVERS
 
 #include "config-device-acceleration.h"
 
-#if defined(MBEDTLS_TRNG_PRESENT)
-#define MBEDTLS_TRNG_C
-#endif
-
+#if !defined(TEST_SUITE_MEMORY_BUFFER_ALLOC)
 #include "sl_malloc.h"
 
 #define MBEDTLS_PLATFORM_FREE_MACRO sl_free
 #define MBEDTLS_PLATFORM_CALLOC_MACRO sl_calloc
+#endif
+
 #define MBEDTLS_PLATFORM_MEMORY
 #define MBEDTLS_PLATFORM_C
 
diff --git a/examples/platforms/efr32/efr32mg13/efr32mg13.cmake b/examples/platforms/efr32/efr32mg13/efr32mg13.cmake
new file mode 100644
index 0000000..c944901
--- /dev/null
+++ b/examples/platforms/efr32/efr32mg13/efr32mg13.cmake
@@ -0,0 +1,95 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+# ==============================================================================
+# Verify board is supported for platform
+# ==============================================================================
+if(BOARD_LOWERCASE STREQUAL "brd4168a")
+    set(MCU "EFR32MG13P732F512GM48")
+else()
+    message(FATAL_ERROR "
+    BOARD=${BOARD} not supported.
+
+    Please provide a value for BOARD variable e.g BOARD=brd4168a.
+    Currently supported:
+    - brd4168a
+    ")
+endif()
+
+list(APPEND OT_PLATFORM_DEFINES "${MCU}")
+set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE)
+
+# ==============================================================================
+# Platform library
+# ==============================================================================
+set(OT_PLATFORM_LIB "openthread-efr32mg13")
+set(OT_PLATFORM_LIB ${OT_PLATFORM_LIB} PARENT_SCOPE)
+
+set(LD_FILE "${CMAKE_CURRENT_SOURCE_DIR}/efr32mg13/efr32mg13.ld")
+
+add_library(openthread-efr32mg13
+    ${EFR32_COMMON_SOURCES}
+    $<TARGET_OBJECTS:openthread-platform-utils>
+)
+
+set_target_properties(openthread-efr32mg13
+    PROPERTIES
+        C_STANDARD 99
+        CXX_STANDARD 11
+)
+
+target_link_libraries(openthread-efr32mg13
+    PUBLIC
+        ${EFR32_COMMON_3RD_LIBS}
+        silabs-libnvm3_CM4_gcc
+        silabs-efr32mg13-sdk
+        -T${LD_FILE}
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+    PRIVATE
+        ot-config
+)
+
+target_compile_definitions(openthread-efr32mg13
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_options(openthread-efr32mg13
+    PRIVATE
+        ${EFR32_CFLAGS}
+)
+
+target_include_directories(openthread-efr32mg13
+    PUBLIC
+        ${EFR32_INCLUDES}
+    PRIVATE
+        ${SILABS_EFR32MG1X_INCLUDES}
+        ${OT_PUBLIC_INCLUDES}
+)
+
diff --git a/examples/platforms/efr32/efr32mg21/Makefile.am b/examples/platforms/efr32/efr32mg21/Makefile.am
index 4697d3c..3ce9a37 100644
--- a/examples/platforms/efr32/efr32mg21/Makefile.am
+++ b/examples/platforms/efr32/efr32mg21/Makefile.am
@@ -33,7 +33,6 @@
 
 libopenthread_efr32mg21_a_CPPFLAGS                                            = \
     -DPLATFORM_HEADER=\"platform/base/hal/micro/cortexm3/compiler/gcc.h\"       \
-    -DNVIC_CONFIG=\"platform/base/hal/micro/cortexm3/efm32/nvic-config.h\"      \
     -Wno-sign-compare                                                           \
     -I$(top_srcdir)/examples/platforms                                          \
     -I$(top_srcdir)/examples/platforms/efr32/$(PLATFORM_LOWERCASE)/$(BOARD_LOWERCASE) \
diff --git a/examples/platforms/efr32/efr32mg21/Makefile.platform.am b/examples/platforms/efr32/efr32mg21/Makefile.platform.am
index f91e03a..8193b73 100644
--- a/examples/platforms/efr32/efr32mg21/Makefile.platform.am
+++ b/examples/platforms/efr32/efr32mg21/Makefile.platform.am
@@ -31,5 +31,5 @@
 #
 
 LDADD_COMMON                                                                  += \
-    $(top_srcdir)/third_party/silabs/gecko_sdk_suite/v3.0/platform/emdrv/nvm3/lib/libnvm3_CM33_gcc.a \
+    $(top_srcdir)/third_party/silabs/gecko_sdk_suite/v3.1/platform/emdrv/nvm3/lib/libnvm3_CM33_gcc.a \
     $(NULL)
diff --git a/examples/platforms/efr32/efr32mg21/README.md b/examples/platforms/efr32/efr32mg21/README.md
index b3457a2..610ca26 100644
--- a/examples/platforms/efr32/efr32mg21/README.md
+++ b/examples/platforms/efr32/efr32mg21/README.md
@@ -30,8 +30,8 @@
 
 2. Install Flex (Gecko) SDK including RAIL Library from Simplicity Studio.
    - Connect EFR32MG21 Wireless Starter Kit to Simplicity Studio.
-   - Find Flex SDK v3.0 in the Software Update page and click Install.
-   - Flex SDK v3.0 will be installed in the path:
+   - Find Flex SDK v3.1 in the Software Update page and click Install.
+   - Flex SDK v3.1 will be installed in the path:
      - Mac
        - `/Applications/Simplicity\ Studio.app/Contents/Eclipse/developer/sdks/gecko_sdk_suite`
      - Windows
@@ -65,7 +65,47 @@
 $ ./bootstrap
 ```
 
-For EFR32MG21™ Mighty Gecko Wireless Starter Kit:
+For EFR32MG21™ Mighty Gecko Wireless Starter Kit, this can be done using both the CMake and autotools build systems
+
+**CMake (preferred)**
+
+```bash
+$ ./script/cmake-build efr32mg21 -DBOARD=brd4180b
+...
+-- Configuring done
+-- Generating done
+-- Build files have been written to: <path-to-openthread>/build/efr32mg21
++ [[ -n ot-rcp ot-cli-ftd ot-cli-mtd ot-ncp-ftd ot-ncp-mtd sleepy-demo-ftd sleepy-demo-mtd ]]
++ ninja ot-rcp ot-cli-ftd ot-cli-mtd ot-ncp-ftd ot-ncp-mtd sleepy-demo-ftd sleepy-demo-mtd
+[940/940] Linking CXX executable examples/apps/ncp/ot-ncp-ftd
++ cd <path-to-openthread>
+```
+
+After a successful build, the `elf` files are found in `<path-to-openthread>/build/efr32mg21/examples`.
+
+```bash
+# For linux
+$ find build/efr32mg21/examples -type f -executable
+build/efr32mg21/examples/apps/cli/ot-cli-mtd
+build/efr32mg21/examples/apps/cli/ot-cli-ftd
+build/efr32mg21/examples/apps/ncp/ot-ncp-ftd
+build/efr32mg21/examples/apps/ncp/ot-ncp-mtd
+build/efr32mg21/examples/apps/ncp/ot-rcp
+build/efr32mg21/examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/sleepy-demo-ftd
+build/efr32mg21/examples/platforms/efr32/sleepy-demo/sleepy-demo-mtd/sleepy-demo-mtd
+
+# For BSD/Darwin/mac systems
+$ find build/efr32mg21/examples -type f -perm +111
+build/efr32mg21/examples/apps/cli/ot-cli-mtd
+build/efr32mg21/examples/apps/cli/ot-cli-ftd
+build/efr32mg21/examples/apps/ncp/ot-ncp-ftd
+build/efr32mg21/examples/apps/ncp/ot-ncp-mtd
+build/efr32mg21/examples/apps/ncp/ot-rcp
+build/efr32mg21/examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/sleepy-demo-ftd
+build/efr32mg21/examples/platforms/efr32/sleepy-demo/sleepy-demo-mtd/sleepy-demo-mtd
+```
+
+**autotools (soon to be depracated)**
 
 ```bash
 $ make -f examples/Makefile-efr32mg21 BOARD=BRD4180A
@@ -183,7 +223,7 @@
 
 For a list of all available commands, visit [OpenThread CLI Reference README.md][cli].
 
-[cli]: https://github.com/openthread/openthread/blob/master/src/cli/README.md
+[cli]: https://github.com/openthread/openthread/blob/main/src/cli/README.md
 
 ## Verification
 
@@ -193,4 +233,4 @@
 
 The EFR32 example has been verified with following Flex SDK/RAIL Library version:
 
-- Flex SDK version 3.0.x
+- Flex SDK v3.1.x
diff --git a/examples/platforms/efr32/efr32mg21/arm-none-eabi.cmake b/examples/platforms/efr32/efr32mg21/arm-none-eabi.cmake
new file mode 100644
index 0000000..0460316
--- /dev/null
+++ b/examples/platforms/efr32/efr32mg21/arm-none-eabi.cmake
@@ -0,0 +1,51 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+set(CMAKE_SYSTEM_NAME              Generic)
+set(CMAKE_SYSTEM_PROCESSOR         ARM)
+
+set(CMAKE_C_COMPILER               arm-none-eabi-gcc)
+set(CMAKE_CXX_COMPILER             arm-none-eabi-g++)
+set(CMAKE_ASM_COMPILER             arm-none-eabi-as)
+set(CMAKE_RANLIB                   arm-none-eabi-ranlib)
+
+execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE COMPILER_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+set(COMMON_C_FLAGS                 "-mcpu=cortex-m33 -mthumb -fmessage-length=0 -ffunction-sections -fdata-sections -mfpu=fpv5-sp-d16 -mfloat-abi=hard")
+
+set(CMAKE_C_FLAGS_INIT             "${COMMON_C_FLAGS} -std=c99")
+set(CMAKE_CXX_FLAGS_INIT           "${COMMON_C_FLAGS} -fno-exceptions -fno-rtti")
+set(CMAKE_EXE_LINKER_FLAGS_INIT    "${COMMON_C_FLAGS} -specs=nano.specs -specs=nosys.specs")
+
+set(CMAKE_C_FLAGS_DEBUG            "-Og -g")
+set(CMAKE_CXX_FLAGS_DEBUG          "-Og -g")
+set(CMAKE_ASM_FLAGS_DEBUG          "-g")
+
+set(CMAKE_C_FLAGS_RELEASE          "-Os")
+set(CMAKE_CXX_FLAGS_RELEASE        "-Os")
+set(CMAKE_ASM_FLAGS_RELEASE        "")
diff --git a/examples/platforms/efr32/efr32mg21/crypto/mbedtls_config_autogen.h b/examples/platforms/efr32/efr32mg21/crypto/mbedtls_config_autogen.h
index 7f02e60..e55f74a 100644
--- a/examples/platforms/efr32/efr32mg21/crypto/mbedtls_config_autogen.h
+++ b/examples/platforms/efr32/efr32mg21/crypto/mbedtls_config_autogen.h
@@ -24,8 +24,9 @@
 #define MBEDTLS_ECP_C
 #define MBEDTLS_ECP_DP_SECP256R1_ENABLED
 #define MBEDTLS_ECDH_C
+#define MBEDTLS_ECDH_LEGACY_CONTEXT
 #define MBEDTLS_ECDSA_C
-#define MBEDTLS_ENTROPY_RAIL_C
+#define MBEDTLS_ENTROPY_HARDWARE_ALT
 #define MBEDTLS_MD_C
 #define MBEDTLS_ECJPAKE_C
 #define MBEDTLS_KEY_EXCHANGE_ECJPAKE_ENABLED
@@ -35,14 +36,12 @@
 #define MBEDTLS_ENTROPY_FORCE_SHA256
 #define MBEDTLS_ENTROPY_MAX_SOURCES 2
 #define MBEDTLS_NO_PLATFORM_ENTROPY
-#define MBEDTLS_ENTROPY_HARDWARE_ALT
 #define MBEDTLS_CTR_DRBG_C
-#define MBEDTLS_SHA1_C
 #define MBEDTLS_SHA256_C
-#define MBEDTLS_SHA512_C
 #define MBEDTLS_SSL_TLS_C
 #define MBEDTLS_SSL_CLI_C
 #define MBEDTLS_SSL_PROTO_TLS1_2
+#define MBEDTLS_SSL_KEEP_PEER_CERTIFICATE
 #define MBEDTLS_SSL_SRV_C
 #define MBEDTLS_X509_USE_C
 #define MBEDTLS_X509_CRT_PARSE_C
@@ -51,13 +50,18 @@
 #define MBEDTLS_OID_C
 #define MBEDTLS_PK_C
 #define MBEDTLS_PK_PARSE_C
+#define MBEDTLS_PSA_CRYPTO_DRIVERS
+#define MBEDTLS_PSA_CRYPTO_BUILTIN_KEYS
 
 #include "config-device-acceleration.h"
 
+#if !defined(TEST_SUITE_MEMORY_BUFFER_ALLOC)
 #include "sl_malloc.h"
 
 #define MBEDTLS_PLATFORM_FREE_MACRO sl_free
 #define MBEDTLS_PLATFORM_CALLOC_MACRO sl_calloc
+#endif
+
 #define MBEDTLS_PLATFORM_MEMORY
 #define MBEDTLS_PLATFORM_C
 
diff --git a/examples/platforms/efr32/efr32mg21/efr32mg21.cmake b/examples/platforms/efr32/efr32mg21/efr32mg21.cmake
new file mode 100644
index 0000000..eaaffdd
--- /dev/null
+++ b/examples/platforms/efr32/efr32mg21/efr32mg21.cmake
@@ -0,0 +1,98 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+# ==============================================================================
+# Verify board is supported for platform
+# ==============================================================================
+if(BOARD_LOWERCASE STREQUAL "brd4180a")
+    set(MCU "EFR32MG21A020F1024IM32")
+elseif(BOARD_LOWERCASE STREQUAL "brd4180b")
+    set(MCU "EFR32MG21A020F1024IM32")
+else()
+    message(FATAL_ERROR "
+    BOARD=${BOARD} not supported.
+
+    Please provide a value for BOARD variable e.g BOARD=brd4180a.
+    Currently supported:
+    - brd4180a
+    - brd4180b
+    ")
+endif()
+
+list(APPEND OT_PLATFORM_DEFINES "${MCU}")
+set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE)
+
+# ==============================================================================
+# Platform library
+# ==============================================================================
+set(OT_PLATFORM_LIB "openthread-efr32mg21")
+set(OT_PLATFORM_LIB ${OT_PLATFORM_LIB} PARENT_SCOPE)
+
+set(LD_FILE "${CMAKE_CURRENT_SOURCE_DIR}/efr32mg21/efr32mg21.ld")
+
+add_library(openthread-efr32mg21
+    ${EFR32_COMMON_SOURCES}
+    $<TARGET_OBJECTS:openthread-platform-utils>
+)
+
+set_target_properties(openthread-efr32mg21
+    PROPERTIES
+        C_STANDARD 99
+        CXX_STANDARD 11
+)
+
+target_link_libraries(openthread-efr32mg21
+    PUBLIC
+        ${EFR32_COMMON_3RD_LIBS}
+        silabs-libnvm3_CM33_gcc
+        silabs-efr32mg21-sdk
+        -T${LD_FILE}
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+    PRIVATE
+        ot-config
+)
+
+target_compile_definitions(openthread-efr32mg21
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_options(openthread-efr32mg21
+    PRIVATE
+        ${EFR32_CFLAGS}
+)
+
+target_include_directories(openthread-efr32mg21
+    PUBLIC
+        ${EFR32_INCLUDES}
+    PRIVATE
+        ${SILABS_EFR32MG2X_INCLUDES}
+        ${OT_PUBLIC_INCLUDES}
+)
+
diff --git a/tests/scripts/expect/cli-anycast.exp b/examples/platforms/efr32/sleepy-demo/CMakeLists.txt
similarity index 74%
copy from tests/scripts/expect/cli-anycast.exp
copy to examples/platforms/efr32/sleepy-demo/CMakeLists.txt
index 3081a6b..b99a6b6 100644
--- a/tests/scripts/expect/cli-anycast.exp
+++ b/examples/platforms/efr32/sleepy-demo/CMakeLists.txt
@@ -1,6 +1,5 @@
-#!/usr/bin/expect -f
 #
-#  Copyright (c) 2020, The OpenThread Authors.
+#  Copyright (c) 2021, The OpenThread Authors.
 #  All rights reserved.
 #
 #  Redistribution and use in source and binary forms, with or without
@@ -27,30 +26,10 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
-source "tests/scripts/expect/_common.exp"
+if(OT_FTD)
+    add_subdirectory(sleepy-demo-ftd)
+endif()
 
-
-set spawn_id [spawn_node 1]
-
-send "panid 0xface\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-
-wait_for "state" "leader"
-expect "Done"
-
-send "ping fdde:ad00:beef:0:0:ff:fe00:fc00\n"
-expect "Done"
-expect "16 bytes from "
-expect {
-    "fdde:ad00:beef:0:0:ff:fe00:fc00" abort
-
-    -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})} {}
-
-    timeout abort
-}
-
-dispose
+if(OT_MTD)
+    add_subdirectory(sleepy-demo-mtd)
+endif()
diff --git a/tests/scripts/expect/cli-anycast.exp b/examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/CMakeLists.txt
similarity index 74%
copy from tests/scripts/expect/cli-anycast.exp
copy to examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/CMakeLists.txt
index 3081a6b..f8aaf63 100644
--- a/tests/scripts/expect/cli-anycast.exp
+++ b/examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/CMakeLists.txt
@@ -1,6 +1,5 @@
-#!/usr/bin/expect -f
 #
-#  Copyright (c) 2020, The OpenThread Authors.
+#  Copyright (c) 2021, The OpenThread Authors.
 #  All rights reserved.
 #
 #  Redistribution and use in source and binary forms, with or without
@@ -27,30 +26,28 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
-source "tests/scripts/expect/_common.exp"
+add_executable(sleepy-demo-ftd
+    ${PROJECT_SOURCE_DIR}/examples/apps/cli/cli_uart.cpp
+    main.c
+)
+
+target_include_directories(sleepy-demo-ftd PRIVATE ${COMMON_INCLUDES})
+
+target_link_libraries(sleepy-demo-ftd PRIVATE
+    openthread-cli-ftd
+    ${OT_PLATFORM_LIB}
+    openthread-ftd
+    ${OT_PLATFORM_LIB}
+    ${OT_MBEDTLS}
+    ot-config
+)
 
 
-set spawn_id [spawn_node 1]
+target_compile_options(sleepy-demo-ftd
+    PRIVATE
+        ${EFR32_CFLAGS}
+)
 
-send "panid 0xface\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
+install(TARGETS sleepy-demo-ftd
+    DESTINATION bin)
 
-wait_for "state" "leader"
-expect "Done"
-
-send "ping fdde:ad00:beef:0:0:ff:fe00:fc00\n"
-expect "Done"
-expect "16 bytes from "
-expect {
-    "fdde:ad00:beef:0:0:ff:fe00:fc00" abort
-
-    -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})} {}
-
-    timeout abort
-}
-
-dispose
diff --git a/examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/Makefile.am b/examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/Makefile.am
index 141968e..f7b548d 100644
--- a/examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/Makefile.am
+++ b/examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/Makefile.am
@@ -36,14 +36,15 @@
     $(NULL)
 
 CPPFLAGS_COMMON                                                              += \
-    -DNVIC_CONFIG=\"platform/base/hal/micro/cortexm3/efm32/nvic-config.h\"      \
     -DPLATFORM_HEADER=\"platform/base/hal/micro/cortexm3/compiler/gcc.h\"       \
     -Wno-sign-compare                                                           \
+    -I$(top_srcdir)/examples/apps/cli                                           \
     -I$(top_srcdir)/examples/platforms                                          \
     -I$(top_srcdir)/examples/platforms/efr32/$(PLATFORM_LOWERCASE)/$(BOARD_LOWERCASE) \
     -I$(top_srcdir)/examples/platforms/efr32/$(PLATFORM_LOWERCASE)              \
     -I$(top_srcdir)/examples/platforms/efr32/src                                \
     -I$(top_srcdir)/include                                                     \
+    -I$(top_srcdir)/src                                                         \
     -I$(top_srcdir)/src/core                                                    \
     -I$(top_srcdir)/third_party/silabs/rail_config                              \
     $(SILABS_GSDK_CPPFLAGS)                                                     \
@@ -61,6 +62,7 @@
     $(NULL)
 
 SOURCES_COMMON                                                               += \
+    ../../../../apps/cli/cli_uart.cpp                                           \
     main.c                                                                      \
     $(NULL)
 
diff --git a/examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/main.c b/examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/main.c
index 2944a83..19560cb 100644
--- a/examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/main.c
+++ b/examples/platforms/efr32/sleepy-demo/sleepy-demo-ftd/main.c
@@ -71,6 +71,14 @@
 void applicationTick(void);
 void sFtdReceiveCallback(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
 
+/**
+ * This function initializes the CLI app.
+ *
+ * @param[in]  aInstance  The OpenThread instance structure.
+ *
+ */
+extern void otAppCliInit(otInstance *aInstance);
+
 // Variables
 static otInstance *        instance;
 static otUdpSocket         sFtdSocket;
@@ -93,7 +101,7 @@
     instance = otInstanceInitSingle();
     assert(instance);
 
-    otCliUartInit(instance);
+    otAppCliInit(instance);
     otCliOutputFormat("sleepy-demo-ftd started\r\n");
 
     setNetworkConfiguration(instance);
diff --git a/tests/scripts/expect/cli-anycast.exp b/examples/platforms/efr32/sleepy-demo/sleepy-demo-mtd/CMakeLists.txt
similarity index 74%
copy from tests/scripts/expect/cli-anycast.exp
copy to examples/platforms/efr32/sleepy-demo/sleepy-demo-mtd/CMakeLists.txt
index 3081a6b..6eec209 100644
--- a/tests/scripts/expect/cli-anycast.exp
+++ b/examples/platforms/efr32/sleepy-demo/sleepy-demo-mtd/CMakeLists.txt
@@ -1,6 +1,5 @@
-#!/usr/bin/expect -f
 #
-#  Copyright (c) 2020, The OpenThread Authors.
+#  Copyright (c) 2021, The OpenThread Authors.
 #  All rights reserved.
 #
 #  Redistribution and use in source and binary forms, with or without
@@ -27,30 +26,21 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
-source "tests/scripts/expect/_common.exp"
+add_executable(sleepy-demo-mtd
+    ${PROJECT_SOURCE_DIR}/examples/apps/cli/cli_uart.cpp
+    main.c
+)
 
+target_include_directories(sleepy-demo-mtd PRIVATE ${COMMON_INCLUDES})
 
-set spawn_id [spawn_node 1]
+target_link_libraries(sleepy-demo-mtd PRIVATE
+    openthread-cli-mtd
+    ${OT_PLATFORM_LIB}
+    openthread-mtd
+    ${OT_PLATFORM_LIB}
+    ${OT_MBEDTLS}
+    ot-config
+)
 
-send "panid 0xface\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-
-wait_for "state" "leader"
-expect "Done"
-
-send "ping fdde:ad00:beef:0:0:ff:fe00:fc00\n"
-expect "Done"
-expect "16 bytes from "
-expect {
-    "fdde:ad00:beef:0:0:ff:fe00:fc00" abort
-
-    -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})} {}
-
-    timeout abort
-}
-
-dispose
+install(TARGETS sleepy-demo-mtd
+    DESTINATION bin)
diff --git a/examples/platforms/efr32/sleepy-demo/sleepy-demo-mtd/Makefile.am b/examples/platforms/efr32/sleepy-demo/sleepy-demo-mtd/Makefile.am
index 6adedec..9d855bc 100644
--- a/examples/platforms/efr32/sleepy-demo/sleepy-demo-mtd/Makefile.am
+++ b/examples/platforms/efr32/sleepy-demo/sleepy-demo-mtd/Makefile.am
@@ -36,14 +36,15 @@
     $(NULL)
 
 CPPFLAGS_COMMON                                                              += \
-    -DNVIC_CONFIG=\"platform/base/hal/micro/cortexm3/efm32/nvic-config.h\"      \
     -DPLATFORM_HEADER=\"platform/base/hal/micro/cortexm3/compiler/gcc.h\"       \
     -Wno-sign-compare                                                           \
+    -I$(top_srcdir)/examples/apps/cli                                           \
     -I$(top_srcdir)/examples/platforms                                          \
     -I$(top_srcdir)/examples/platforms/efr32/$(PLATFORM_LOWERCASE)/$(BOARD_LOWERCASE) \
     -I$(top_srcdir)/examples/platforms/efr32/$(PLATFORM_LOWERCASE)              \
     -I$(top_srcdir)/examples/platforms/efr32/src                                \
     -I$(top_srcdir)/include                                                     \
+    -I$(top_srcdir)/src                                                         \
     -I$(top_srcdir)/src/core                                                    \
     -I$(top_srcdir)/third_party/silabs/rail_config                              \
     $(SILABS_GSDK_CPPFLAGS)                                                     \
@@ -61,6 +62,7 @@
     $(NULL)
 
 SOURCES_COMMON                                                               += \
+    ../../../../apps/cli/cli_uart.cpp                                           \
     main.c                                                                      \
     $(NULL)
 
diff --git a/examples/platforms/efr32/sleepy-demo/sleepy-demo-mtd/main.c b/examples/platforms/efr32/sleepy-demo/sleepy-demo-mtd/main.c
index 17560fe..23318f7 100644
--- a/examples/platforms/efr32/sleepy-demo/sleepy-demo-mtd/main.c
+++ b/examples/platforms/efr32/sleepy-demo/sleepy-demo-mtd/main.c
@@ -76,6 +76,14 @@
 void applicationTick(void);
 void mtdReceiveCallback(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
 
+/**
+ * This function initializes the CLI app.
+ *
+ * @param[in]  aInstance  The OpenThread instance structure.
+ *
+ */
+extern void otAppCliInit(otInstance *aInstance);
+
 // Variables
 static otInstance *        instance;
 static otUdpSocket         sMtdSocket;
@@ -97,7 +105,7 @@
     instance = otInstanceInitSingle();
     assert(instance);
 
-    otCliUartInit(instance);
+    otAppCliInit(instance);
 
     otLinkSetPollPeriod(instance, SLEEPY_POLL_PERIOD_MS);
     setNetworkConfiguration(instance);
@@ -211,7 +219,7 @@
             config.mDeviceType   = 0;
             config.mNetworkData  = 0;
             otThreadSetLinkMode(instance, config);
-            sAllowDeepSleep = true;
+            sAllowDeepSleep = false;
             break;
 
         case OT_DEVICE_ROLE_DETACHED:
diff --git a/examples/platforms/efr32/src/alarm.c b/examples/platforms/efr32/src/alarm.c
index 6caa1ef..81a0015 100644
--- a/examples/platforms/efr32/src/alarm.c
+++ b/examples/platforms/efr32/src/alarm.c
@@ -38,6 +38,7 @@
 
 #include "openthread-system.h"
 #include <openthread/config.h>
+#include <openthread/platform/alarm-micro.h>
 #include <openthread/platform/alarm-milli.h>
 #include <openthread/platform/diag.h>
 #include "common/logging.hpp"
@@ -145,3 +146,23 @@
         }
     }
 }
+
+uint32_t otPlatAlarmMicroGetNow(void)
+{
+    // TODO microsecond support
+    return otPlatAlarmMilliGetNow();
+}
+
+void otPlatAlarmMicroStartAt(otInstance *aInstance, uint32_t aT0, uint32_t aDt)
+{
+    // TODO microsecond support
+    otPlatAlarmMilliStartAt(aInstance, aT0, aDt);
+}
+
+void otPlatAlarmMicroStop(otInstance *aInstance)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+
+    sl_sleeptimer_stop_timer(&sl_handle);
+    sIsRunning = false;
+}
diff --git a/examples/platforms/efr32/src/entropy.c b/examples/platforms/efr32/src/entropy.c
index 3a755e1..b9c629c 100644
--- a/examples/platforms/efr32/src/entropy.c
+++ b/examples/platforms/efr32/src/entropy.c
@@ -51,7 +51,6 @@
         // Non-zero return values for mbedtls_hardware_poll() signify an error has occurred
         otEXPECT_ACTION(0 == mbedtls_hardware_poll(NULL, &aOutput[outputLen], remaining, &partialLen),
                         error = OT_ERROR_FAILED);
-        otEXPECT_ACTION(partialLen > 0, error = OT_ERROR_FAILED);
     }
 
 exit:
diff --git a/examples/platforms/efr32/src/ieee802154mac.h b/examples/platforms/efr32/src/ieee802154mac.h
new file mode 100644
index 0000000..178dfee
--- /dev/null
+++ b/examples/platforms/efr32/src/ieee802154mac.h
@@ -0,0 +1,194 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file holds required 802.15.4 definitions
+ *
+ */
+
+#ifndef __IEEE802154IEEE802154_H__
+#define __IEEE802154IEEE802154_H__
+
+#define IEEE802154_MIN_LENGTH \
+    4 // Technically, a version 2 packet / ACK
+      // can be 4 bytes with seq# suppression
+#define IEEE802154_MAX_LENGTH 127
+#define IEEE802154_ACK_LENGTH 5
+
+// FCF + DSN + dest PANID + dest addr + src PANID + src addr (without security header)
+#define IEEE802154_MAX_MHR_LENGTH (2 + 1 + 2 + 8 + 2 + 8)
+
+#define IEEE802154_DSN_OFFSET 2
+#define IEEE802154_FCF_OFFSET 0
+
+//------------------------------------------------------------------------
+// 802.15.4 Frame Control Field definitions for Beacon, Ack, Data, Command
+
+#define IEEE802154_FRAME_TYPE_MASK ((uint16_t)0x0007U)              // Bits 0..2
+#define IEEE802154_FRAME_TYPE_BEACON ((uint16_t)0x0000U)            // Beacon
+#define IEEE802154_FRAME_TYPE_DATA ((uint16_t)0x0001U)              // Data
+#define IEEE802154_FRAME_TYPE_ACK ((uint16_t)0x0002U)               // ACK
+#define IEEE802154_FRAME_TYPE_COMMAND ((uint16_t)0x0003U)           // Command
+#define IEEE802154_FRAME_TYPE_CONTROL IEEE802154_FRAME_TYPE_COMMAND // (synonym)
+#define IEEE802154_FRAME_TYPE_RESERVED_MASK ((uint16_t)0x0004U)     // Versions 0/1
+// 802.15.4E-2012 introduced MultiPurpose with different Frame Control Field
+// layout described in the MultiPurpose section below.
+#define IEEE802154_FRAME_TYPE_MULTIPURPOSE ((uint16_t)0x0005U) // MultiPurpose
+
+#define IEEE802154_FRAME_FLAG_SECURITY_ENABLED ((uint16_t)0x0008U) // Bit 3
+#define IEEE802154_FRAME_FLAG_FRAME_PENDING ((uint16_t)0x0010U)    // Bit 4
+#define IEEE802154_FRAME_FLAG_ACK_REQUIRED ((uint16_t)0x0020U)     // Bit 5
+#define IEEE802154_FRAME_FLAG_INTRA_PAN ((uint16_t)0x0040U)        // Bit 6
+// 802.15.4-2006 renamed the Intra-Pan flag PanId-Compression
+#define IEEE802154_FRAME_FLAG_PANID_COMPRESSION IEEE802154_FRAME_FLAG_INTRA_PAN
+#define IEEE802154_FRAME_FLAG_RESERVED ((uint16_t)0x0080U) // Bit 7 reserved
+// Use the reserved flag internally to check whether frame pending bit was set in outgoing ACK
+#define IEEE802154_FRAME_PENDING_SET_IN_OUTGOING_ACK IEEE802154_FRAME_FLAG_RESERVED
+// 802.15.4E-2012 introduced these flags for Frame Version 2 frames
+// which are reserved bit positions in earlier Frame Version frames:
+#define IEEE802154_FRAME_FLAG_SEQ_SUPPRESSION ((uint16_t)0x0100U) // Bit 8
+#define IEEE802154_FRAME_FLAG_IE_LIST_PRESENT ((uint16_t)0x0200U) // Bit 9
+
+#define IEEE802154_FRAME_DESTINATION_MODE_MASK ((uint16_t)0x0C00U)     // Bits 10..11
+#define IEEE802154_FRAME_DESTINATION_MODE_NONE ((uint16_t)0x0000U)     // Mode 0
+#define IEEE802154_FRAME_DESTINATION_MODE_RESERVED ((uint16_t)0x0400U) // Mode 1
+#define IEEE802154_FRAME_DESTINATION_MODE_SHORT ((uint16_t)0x0800U)    // Mode 2
+#define IEEE802154_FRAME_DESTINATION_MODE_LONG ((uint16_t)0x0C00U)     // Mode 3
+// 802.15.4e-2012 only (not adopted into 802.15.4-2015)
+#define IEEE802154_FRAME_DESTINATION_MODE_BYTE IEEE802154_FRAME_DESTINATION_MODE_RESERVED
+
+#define IEEE802154_FRAME_VERSION_MASK ((uint16_t)0x3000U) // Bits 12..13
+#define IEEE802154_FRAME_VERSION_2003 ((uint16_t)0x0000U) // Version 0
+#define IEEE802154_FRAME_VERSION_2006 ((uint16_t)0x1000U) // Version 1
+// In 802.15.4-2015, Version 2 is just called "IEEE STD 802.15.4"
+// which can be rather confusing. It was introduced in 802.15.4E-2012.
+#define IEEE802154_FRAME_VERSION_2012 ((uint16_t)0x2000U)     // Version 2
+#define IEEE802154_FRAME_VERSION_2015 ((uint16_t)0x2000U)     // Version 2
+#define IEEE802154_FRAME_VERSION_RESERVED ((uint16_t)0x3000U) // Version 3
+
+#define IEEE802154_FRAME_SOURCE_MODE_MASK ((uint16_t)0xC000U)     // Bits 14..15
+#define IEEE802154_FRAME_SOURCE_MODE_NONE ((uint16_t)0x0000U)     // Mode 0
+#define IEEE802154_FRAME_SOURCE_MODE_RESERVED ((uint16_t)0x4000U) // Mode 1
+#define IEEE802154_FRAME_SOURCE_MODE_SHORT ((uint16_t)0x8000U)    // Mode 2
+#define IEEE802154_FRAME_SOURCE_MODE_LONG ((uint16_t)0xC000U)     // Mode 3
+// 802.15.4e-2012 only (not adopted into 802.15.4-2015)
+#define IEEE802154_FRAME_SOURCE_MODE_BYTE IEEE802154_FRAME_SOURCE_MODE_RESERVED
+
+//------------------------------------------------------------------------
+// 802.15.4E-2012 Frame Control Field definitions for MultiPurpose
+
+#define IEEE802154_MP_FRAME_TYPE_MASK IEEE802154_FRAME_TYPE_MASK // Bits 0..2
+#define IEEE802154_MP_FRAME_TYPE_MULTIPURPOSE IEEE802154_FRAME_TYPE_MULTIPURPOSE
+
+#define IEEE802154_MP_FRAME_FLAG_LONG_FCF ((uint16_t)0x0008U) // Bit 3
+
+#define IEEE802154_MP_FRAME_DESTINATION_MODE_MASK ((uint16_t)0x0030U)     // Bits 4..5
+#define IEEE802154_MP_FRAME_DESTINATION_MODE_NONE ((uint16_t)0x0000U)     // Mode 0
+#define IEEE802154_MP_FRAME_DESTINATION_MODE_RESERVED ((uint16_t)0x0010U) // Mode 1
+#define IEEE802154_MP_FRAME_DESTINATION_MODE_SHORT ((uint16_t)0x0020U)    // Mode 2
+#define IEEE802154_MP_FRAME_DESTINATION_MODE_LONG ((uint16_t)0x0030U)     // Mode 3
+// 802.15.4e-2012 only (not adopted into 802.15.4-2015)
+#define IEEE802154_MP_FRAME_DESTINATION_MODE_BYTE IEEE802154_MP_FRAME_DESTINATION_MODE_RESERVED
+
+#define IEEE802154_MP_FRAME_SOURCE_MODE_MASK ((uint16_t)0x00C0U)     // Bits 6..7
+#define IEEE802154_MP_FRAME_SOURCE_MODE_NONE ((uint16_t)0x0000U)     // Mode 0
+#define IEEE802154_MP_FRAME_SOURCE_MODE_RESERVED ((uint16_t)0x0040U) // Mode 1
+#define IEEE802154_MP_FRAME_SOURCE_MODE_SHORT ((uint16_t)0x0080U)    // Mode 2
+#define IEEE802154_MP_FRAME_SOURCE_MODE_LONG ((uint16_t)0x00C0U)     // Mode 3
+// 802.15.4e-2012 only (not adopted into 802.15.4-2015)
+#define IEEE802154_MP_FRAME_SOURCE_MODE_BYTE IEEE802154_MP_FRAME_SOURCE_MODE_RESERVED
+
+#define IEEE802154_MP_FRAME_FLAG_PANID_PRESENT ((uint16_t)0x0100U)    // Bit 8
+#define IEEE802154_MP_FRAME_FLAG_SECURITY_ENABLED ((uint16_t)0x0200U) // Bit 9
+#define IEEE802154_MP_FRAME_FLAG_SEQ_SUPPRESSION ((uint16_t)0x0400U)  // Bit 10
+#define IEEE802154_MP_FRAME_FLAG_FRAME_PENDING ((uint16_t)0x0800U)    // Bit 11
+
+#define IEEE802154_MP_FRAME_VERSION_MASK IEEE802154_FRAME_VERSION_MASK // Bits 12..13
+#define IEEE802154_MP_FRAME_VERSION_2012 ((uint16_t)0x0000U)           // Zeroed out
+#define IEEE802154_MP_FRAME_VERSION_2015 ((uint16_t)0x0000U)           // Zeroed out
+// All other MultiPurpose Frame Versions are reserved
+
+#define IEEE802154_MP_FRAME_FLAG_ACK_REQUIRED ((uint16_t)0x4000U)    // Bit 14
+#define IEEE802154_MP_FRAME_FLAG_IE_LIST_PRESENT ((uint16_t)0x8000U) // Bit 15
+
+//------------------------------------------------------------------------
+// Information Elements fields
+
+#define IEEE802154_KEYID_MODE_0 ((uint8_t)0x0000U)
+#define IEEE802154_KEYID_MODE_0_SIZE 0
+
+#define IEEE802154_KEYID_MODE_1 ((uint8_t)0x0008U)
+#define IEEE802154_KEYID_MODE_1_SIZE 0
+
+#define IEEE802154_KEYID_MODE_2 ((uint8_t)0x0010U)
+#define IEEE802154_KEYID_MODE_2_SIZE 4
+
+#define IEEE802154_KEYID_MODE_3 ((uint8_t)0x0018U)
+#define IEEE802154_KEYID_MODE_3_SIZE 8
+
+#define IEEE802154_KEYID_MODE_MASK ((uint8_t)0x0018U)
+
+//------------------------------------------------------------------------
+// Information Elements fields
+
+// There are Header IEs and Payload IEs.  Header IEs are authenticated
+// if MAC Security is enabled.  Payload IEs are both authenticated and
+// encrypted if MAC security is enabled.
+
+// Header and Payload IEs have slightly different formats and different
+// contents based on the 802.15.4 spec.
+
+// Both are actually a list of IEs that continues until a termination
+// IE is seen.
+
+#define IEEE802154_FRAME_HEADER_INFO_ELEMENT_LENGTH_MASK 0x007F // bits 0-6
+#define IEEE802154_FRAME_HEADER_INFO_ELEMENT_ID_MASK 0x7F80     // bits 7-14
+#define IEEE802154_FRAME_HEADER_INFO_ELEMENT_TYPE_MASK 0x8000   // bit  15
+
+#define IEEE802154_FRAME_HEADER_INFO_ELEMENT_ID_SHIFT 7
+
+#define IEEE802154_FRAME_PAYLOAD_INFO_ELEMENT_LENGTH_MASK 0x07FF   // bits 0 -10
+#define IEEE802154_FRAME_PAYLOAD_INFO_ELEMENT_GROUP_ID_MASK 0x7800 // bits 11-14
+#define IEEE802154_FRAME_PAYLOAD_INFO_ELEMENT_TYPE_MASK 0x8000     // bit  15
+
+#define IEEE802154_FRAME_PAYLOAD_INFO_ELEMENT_ID_SHIFT 11
+
+// This "type" field indicates header vs. payload IE.  However there is
+// also a Header IE List terminator which would imply the IE list
+// that follows is only payload IEs.
+#define IEEE802154_FRAME_INFO_ELEMENT_TYPE_MASK 0x8000
+
+// Header Termination ID 1 is used when there are Payload IEs that follow.
+// Header Termination ID 2 is used when there are no Payload IEs and the
+//   next field is the MAC payload.
+#define IEEE802154_FRAME_HEADER_TERMINATION_ID_1 0x7E
+#define IEEE802154_FRAME_HEADER_TERMINATION_ID_2 0x7F
+#define IEEE802154_FRAME_PAYLOAD_TERMINATION_ID 0x0F
+
+#endif //__IEEE802154IEEE802154_H__
diff --git a/examples/platforms/efr32/src/mbedtls_config.h b/examples/platforms/efr32/src/mbedtls_config.h
index c56ab3b..c9035a6 100644
--- a/examples/platforms/efr32/src/mbedtls_config.h
+++ b/examples/platforms/efr32/src/mbedtls_config.h
@@ -14,7 +14,11 @@
 // <i> Default: 768
 // <i> The size configured here determines the size of each of the two
 // <i> internal I/O buffers used in mbedTLS when sending and receiving data.
-#define MBEDTLS_SSL_MAX_CONTENT_LEN 768
+#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE
+#define MBEDTLS_SSL_MAX_CONTENT_LEN 900 /**< Maximum fragment length in bytes */
+#else
+#define MBEDTLS_SSL_MAX_CONTENT_LEN 768 /**< Maximum fragment length in bytes */
+#endif
 
 // <q SL_MBEDTLS_SSL_MAX_FRAGMENT_LENGTH> Enable support for RFC 6066 max_fragment_length extension in SSL.
 // <i> Default: 1
diff --git a/examples/platforms/efr32/src/memory.c b/examples/platforms/efr32/src/memory.c
new file mode 100644
index 0000000..1f48994
--- /dev/null
+++ b/examples/platforms/efr32/src/memory.c
@@ -0,0 +1,46 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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.
+ */
+
+#include "openthread-core-efr32-config.h"
+#include "platform-efr32.h"
+
+#if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
+#include <openthread/platform/memory.h>
+
+#include "sl_malloc.h"
+
+void *otPlatCAlloc(size_t aNum, size_t aSize)
+{
+    return sl_calloc(aNum, aSize);
+}
+
+void otPlatFree(void *aPtr)
+{
+    sl_free(aPtr);
+}
+#endif
diff --git a/examples/platforms/efr32/src/openthread-core-efr32-config.h b/examples/platforms/efr32/src/openthread-core-efr32-config.h
index d1cbde6..3608210 100644
--- a/examples/platforms/efr32/src/openthread-core-efr32-config.h
+++ b/examples/platforms/efr32/src/openthread-core-efr32-config.h
@@ -33,6 +33,9 @@
  */
 
 #include "board_config.h"
+
+// IMPORTANT: Do not remove this include. Apps will build without error, but
+// they will not boot up properly
 #include "em_msc.h"
 
 #ifndef OPENTHREAD_CORE_EFR32_CONFIG_H_
@@ -101,7 +104,17 @@
  * Define to 1 if you want to enable software transmission security logic.
  *
  */
-#define OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE 0
+#define OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE \
+    (OPENTHREAD_RADIO && (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2))
+
+/**
+ * @def OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_TIMING_ENABLE
+ *
+ * Define to 1 to enable software transmission target time logic.
+ *
+ */
+#define OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_TIMING_ENABLE \
+    (OPENTHREAD_RADIO && (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2))
 
 /**
  * @def OPENTHREAD_CONFIG_MAC_SOFTWARE_ENERGY_SCAN_ENABLE
@@ -112,6 +125,15 @@
 #define OPENTHREAD_CONFIG_MAC_SOFTWARE_ENERGY_SCAN_ENABLE 0
 
 /**
+ * @def OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE
+ *
+ * Define to 1 if you want to support microsecond timer in platform.
+ *
+ */
+#define OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE \
+    (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2))
+
+/**
  * @def OPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE
  *
  * Define to 1 to enable otPlatFlash* APIs to support non-volatile storage.
@@ -122,11 +144,40 @@
 #define OPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE 0
 
 /**
- * @def OPENTHREAD_CONFIG_NCP_UART_ENABLE
+ * @def OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
  *
- * Define to 1 to enable NCP UART support.
+ * Define to 1 to enable NCP HDLC support.
  *
  */
-#define OPENTHREAD_CONFIG_NCP_UART_ENABLE 1
+#define OPENTHREAD_CONFIG_NCP_HDLC_ENABLE 1
+
+/**
+ * @def OPENTHREAD_CONFIG_MIN_SLEEP_DURATION_MS
+ *
+ * Minimum duration in ms below which the platform will not
+ * enter a deep sleep (EM2) mode.
+ *
+ */
+#define OPENTHREAD_CONFIG_MIN_SLEEP_DURATION_MS 5
+
+/**
+ * @def OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
+ *
+ * Enable the external heap.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
+#define OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE 0
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_EFR32_UART_TX_FLUSH_TIMEOUT_MS
+ *
+ * Maximum time to wait for a flush to complete in otPlatUartFlush().
+ *
+ * Value is in milliseconds
+ *
+ */
+#define OPENTHREAD_CONFIG_EFR32_UART_TX_FLUSH_TIMEOUT_MS 500
 
 #endif // OPENTHREAD_CORE_EFR32_CONFIG_H_
diff --git a/examples/platforms/efr32/src/radio.c b/examples/platforms/efr32/src/radio.c
index 44e8380..a03079b 100644
--- a/examples/platforms/efr32/src/radio.c
+++ b/examples/platforms/efr32/src/radio.c
@@ -32,55 +32,87 @@
  *
  */
 
-#include <openthread-core-config.h>
-#include <openthread/config.h>
-
 #include <assert.h>
 
 #include "openthread-system.h"
 #include <openthread/config.h>
+#include <openthread/link.h>
+#include <openthread/platform/alarm-micro.h>
 #include <openthread/platform/alarm-milli.h>
 #include <openthread/platform/diag.h>
 #include <openthread/platform/radio.h>
 
 #include "common/logging.hpp"
 #include "utils/code_utils.h"
+#include "utils/mac_frame.h"
 
 #include "utils/soft_source_match_table.h"
 
 #include "antenna.h"
 #include "board_config.h"
-#include "em_cmu.h"
 #include "em_core.h"
 #include "em_system.h"
-#include "hal-config.h"
+#include "ieee802154mac.h"
+#include "openthread-core-efr32-config.h"
 #include "pa_conversions_efr32.h"
 #include "platform-band.h"
 #include "rail.h"
 #include "rail_config.h"
 #include "rail_ieee802154.h"
 
-#define IEEE802154_MIN_LENGTH (5u)
-#define IEEE802154_MAX_LENGTH (127u)
-#define IEEE802154_ACK_LENGTH (5u)
+#ifdef SL_COMPONENT_CATALOG_PRESENT
+#include "sl_component_catalog.h"
+#endif // SL_COMPONENT_CATALOG_PRESENT
 
-// FCF + DSN + dest PANID + dest addr + src PANID + src addr (without security header)
-#define IEEE802154_MAX_MHR_LENGTH (2u + 1u + 2u + 8u + 2u + 8u)
+#ifdef SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT
+#include "sl_rail_util_ant_div.h"
+#include "sl_rail_util_ant_div_config.h"
+#endif // SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT
 
-#define IEEE802154_FRAME_TYPE_MASK (0x07u)
-#define IEEE802154_FRAME_TYPE_ACK (0x02u)
-#define IEEE802154_FRAME_TYPE_MAC_COMMAND (0x03u)
-#define IEEE802154_ACK_REQUEST (1u << 5)
-#define IEEE802154_DSN_OFFSET (2u)
-#define IEEE802154_FCF_OFFSET (0u)
+#ifdef SL_CATALOG_RAIL_UTIL_COEX_PRESENT
+#include "coexistence-802154.h"
+#endif // SL_CATALOG_RAIL_UTIL_COEX_PRESENT
 
-#define EFR32_RECEIVE_SENSITIVITY (-100)    // dBm
-#define EFR32_RSSI_AVERAGING_TIME (16u)     // us
-#define EFR32_RSSI_AVERAGING_TIMEOUT (300u) // us
+#ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT
+#include "sl_rail_util_ieee802154_stack_event.h"
+#endif // SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT
 
-#define EFR32_SCHEDULER_SAMPLE_RSSI_PRIORITY (10u) // High priority
-#define EFR32_SCHEDULER_TX_PRIORITY (10u)          // High priority
-#define EFR32_SCHEDULER_RX_PRIORITY (20u)          // Low priority
+#ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_PHY_SELECT_PRESENT
+#include "sl_rail_util_ieee802154_phy_select.h"
+#endif // #ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_PHY_SELECT_PRESENT
+//------------------------------------------------------------------------------
+// Enums, macros and static variables
+
+#define LOW_BYTE(n) ((uint8_t)((n)&0xFF))
+#define HIGH_BYTE(n) ((uint8_t)(LOW_BYTE((n) >> 8)))
+
+#define EFR32_RECEIVE_SENSITIVITY -100   // dBm
+#define EFR32_RSSI_AVERAGING_TIME 16     // us
+#define EFR32_RSSI_AVERAGING_TIMEOUT 300 // us
+
+// Internal flags
+#define FLAG_RADIO_INIT_DONE 0x0001
+#define FLAG_ONGOING_TX_DATA 0x0002
+#define FLAG_ONGOING_TX_ACK 0x0004
+#define FLAG_WAITING_FOR_ACK 0x0008
+#define FLAG_SYMBOL_TIMER_RUNNING 0x0010 // Not used
+#define FLAG_CURRENT_TX_USE_CSMA 0x0020
+#define FLAG_DATA_POLL_FRAME_PENDING_SET 0x0040
+#define FLAG_CALIBRATION_NEEDED 0x0080 // Not used
+#define FLAG_IDLE_PENDING 0x0100       // Not used
+
+#define TX_COMPLETE_RESULT_SUCCESS 0x00 // Not used
+#define TX_COMPLETE_RESULT_CCA_FAIL 0x01
+#define TX_COMPLETE_RESULT_OTHER_FAIL 0x02
+#define TX_COMPLETE_RESULT_NONE 0xFF // Not used
+
+#define TX_WAITING_FOR_ACK 0x00
+#define TX_NO_ACK 0x01
+
+#define ONGOING_TX_FLAGS (FLAG_ONGOING_TX_DATA | FLAG_ONGOING_TX_ACK)
+
+#define QUARTER_DBM_IN_DBM 4
+#define US_IN_MS 1000
 
 enum
 {
@@ -91,6 +123,7 @@
 #endif
 };
 
+// Energy Scan
 typedef enum
 {
     ENERGY_SCAN_STATUS_IDLE,
@@ -104,87 +137,366 @@
     ENERGY_SCAN_MODE_ASYNC
 } energyScanMode;
 
-RAIL_Handle_t gRailHandle;
+static volatile energyScanStatus sEnergyScanStatus;
+static volatile int8_t           sEnergyScanResultDbm;
+static energyScanMode            sEnergyScanMode;
 
-static volatile bool sTransmitBusy = false;
-static bool          sPromiscuous  = false;
-static otRadioState  sState        = OT_RADIO_STATE_DISABLED;
+static bool sIsSrcMatchEnabled = false;
 
-enum
-{
-    ACKED_WITH_FP_MATCH_LENGTH = 1 + IEEE802154_MAX_MHR_LENGTH, // PHR and MHR
-    ACKED_WITH_FP_SLOTS = 32, // maximum number of Data Request packets in the RX FIFO. Length should be a power of 2.
-};
-
-typedef struct efr32AckedWithFP
-{
-    uint8_t mLength;
-    uint8_t mPacket[ACKED_WITH_FP_MATCH_LENGTH];
-} efr32AckedWithFP;
-static bool              sIsSrcMatchEnabled = false;
-static efr32AckedWithFP  sAckedWithFPFifo[ACKED_WITH_FP_SLOTS];
-static uint32_t          sAckedWithFPReadIndex;
-static volatile uint32_t sAckedWithFPWriteIndex;
-
+// Receive
 static uint8_t      sReceivePsdu[IEEE802154_MAX_LENGTH];
+static uint8_t      sReceiveAckPsdu[IEEE802154_ACK_LENGTH];
 static otRadioFrame sReceiveFrame;
+static otRadioFrame sReceiveAckFrame;
 static otError      sReceiveError;
 
+// Transmit
 static otRadioFrame     sTransmitFrame;
 static uint8_t          sTransmitPsdu[IEEE802154_MAX_LENGTH];
 static volatile otError sTransmitError;
+static volatile bool    sTransmitBusy = false;
+static otRadioFrame *   sTxFrame      = NULL;
+
+// Radio
+#define CCA_THRESHOLD_UNINIT 127
+#define CCA_THRESHOLD_DEFAULT -75 // dBm  - default for 2.4GHz 802.15.4
+
+static bool         sPromiscuous = false;
+static otRadioState sState       = OT_RADIO_STATE_DISABLED;
 
 static efr32CommonConfig sCommonConfig;
-static efr32BandConfig   sBandConfigs[EFR32_NUM_BAND_CONFIGS];
+static efr32BandConfig   sBandConfig[EFR32_NUM_BAND_CONFIGS];
+static efr32BandConfig * sCurrentBandConfig = NULL;
+
+static int8_t sCcaThresholdDbm = CCA_THRESHOLD_DEFAULT;
 
 #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
 static efr32RadioCounters sRailDebugCounters;
 #endif
 
-static volatile energyScanStatus sEnergyScanStatus;
-static volatile int8_t           sEnergyScanResultDbm;
-static energyScanMode            sEnergyScanMode;
+// RAIL
+RAIL_Handle_t emPhyRailHandle;
+#define gRailHandle emPhyRailHandle
 
-#define QUARTER_DBM_IN_DBM 4
-#define US_IN_MS 1000
+static const RAIL_IEEE802154_Config_t sRailIeee802154Config = {
+    NULL, // addresses
+    {
+        // ackConfig
+        true, // ackConfig.enable
+        672,  // ackConfig.ackTimeout
+        {
+            // ackConfig.rxTransitions
+            RAIL_RF_STATE_RX, // ackConfig.rxTransitions.success
+            RAIL_RF_STATE_RX, // ackConfig.rxTransitions.error
+        },
+        {
+            // ackConfig.txTransitions
+            RAIL_RF_STATE_RX, // ackConfig.txTransitions.success
+            RAIL_RF_STATE_RX, // ackConfig.txTransitions.error
+        },
+    },
+    {
+        // timings
+        100,      // timings.idleToRx
+        192 - 10, // timings.txToRx
+        100,      // timings.idleToTx
+        192,      // timings.rxToTx
+        0,        // timings.rxSearchTimeout
+        0,        // timings.txToRxSearchTimeout
+    },
+    RAIL_IEEE802154_ACCEPT_STANDARD_FRAMES, // framesMask
+    false,                                  // promiscuousMode
+    false,                                  // isPanCoordinator
+    false,                                  // defaultFramePendingInOutgoingAcks
+};
+
+// Misc
+static volatile uint16_t miscInternalFlags = 0;
+static bool              emPendingData     = false;
+
+#ifdef SL_CATALOG_RAIL_UTIL_COEX_PRESENT
+enum
+{
+    RHO_INACTIVE = 0,
+    RHO_EXT_ACTIVE,
+    RHO_INT_ACTIVE, // Not used
+    RHO_BOTH_ACTIVE,
+};
+
+static uint8_t rhoActive = RHO_INACTIVE;
+static bool    ptaGntEventReported;
+static bool    sRadioCoexEnabled = true;
+
+#if SL_OPENTHREAD_COEX_COUNTER_ENABLE
+static uint32_t sCoexCounters[SL_RAIL_UTIL_COEX_EVENT_COUNT] = {0};
+#endif // SL_OPENTHREAD_COEX_COUNTER_ENABLE
+
+#endif // SL_CATALOG_RAIL_UTIL_COEX_PRESENT
+
+#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+// Transmit Security
+static uint32_t        sMacFrameCounter;
+static uint8_t         sKeyId;
+static struct otMacKey sPrevKey;
+static struct otMacKey sCurrKey;
+static struct otMacKey sNextKey;
+
+#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
+// IE support
+static otExtAddress  sExtAddress;
+static otRadioIeInfo sTransmitIeInfo;
+#endif
+
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+// Enhanced ACKs, CSL
+static bool     sAckedWithSecEnhAck;
+static uint32_t sAckFrameCounter;
+static uint8_t  sAckKeyId;
+
+static uint8_t sAckIeData[OT_ACK_IE_MAX_SIZE];
+static uint8_t sAckIeDataLength = 0;
+
+static uint32_t      sCslPeriod;
+static uint32_t      sCslSampleTime;
+static const uint8_t sCslIeHeader[OT_IE_HEADER_SIZE] = {CSL_IE_HEADER_BYTES_LO, CSL_IE_HEADER_BYTES_HI};
+
+static void processSecurityForEnhancedAck(uint8_t *aAckFrame)
+{
+    otRadioFrame     ackFrame;
+    struct otMacKey *key = NULL;
+    uint8_t          keyId;
+
+    sAckedWithSecEnhAck = false;
+    otEXPECT(aAckFrame[1] & IEEE802154_FRAME_FLAG_SECURITY_ENABLED);
+
+    memset(&ackFrame, 0, sizeof(ackFrame));
+    ackFrame.mPsdu   = &aAckFrame[1];
+    ackFrame.mLength = aAckFrame[0];
+
+    keyId = otMacFrameGetKeyId(&ackFrame);
+
+    otEXPECT(otMacFrameIsKeyIdMode1(&ackFrame) && keyId != 0);
+
+    if (keyId == sKeyId)
+    {
+        key = &sCurrKey;
+    }
+    else if (keyId == sKeyId - 1)
+    {
+        key = &sPrevKey;
+    }
+    else if (keyId == sKeyId + 1)
+    {
+        key = &sNextKey;
+    }
+    else
+    {
+        otEXPECT(false);
+    }
+
+    sAckFrameCounter    = sMacFrameCounter;
+    sAckKeyId           = keyId;
+    sAckedWithSecEnhAck = true;
+
+    ackFrame.mInfo.mTxInfo.mAesKey = key;
+
+    otMacFrameSetKeyId(&ackFrame, keyId);
+    otMacFrameSetFrameCounter(&ackFrame, sMacFrameCounter++);
+
+    // Perform AES-CCM encryption on the frame which is going to be sent.
+    otMacFrameProcessTransmitAesCcm(&ackFrame, &sExtAddress);
+
+exit:
+    return;
+}
+
+static uint16_t getCslPhase()
+{
+    uint32_t curTime       = otPlatAlarmMicroGetNow();
+    uint32_t cslPeriodInUs = sCslPeriod * OT_US_PER_TEN_SYMBOLS;
+    uint32_t diff = ((sCslSampleTime % cslPeriodInUs) - (curTime % cslPeriodInUs) + cslPeriodInUs) % cslPeriodInUs;
+
+    return (uint16_t)(diff / OT_US_PER_TEN_SYMBOLS);
+}
+
+static void updateIeData(void)
+{
+    // The CSL IE Content field:
+    //  ___________________________________________________
+    // |   Octets: 2  |   Octets: 2  |     Octets: 0/2     |
+    // |______________|______________|_____________________|
+    // |   CSL Phase  |   CSL Period |   Rendezvous time   |
+    // |______________|______________|_____________________|
+    //
+    // Note: The rendezvous time is included right when sending the packet,
+    // (in txCurrentPacket), before updating the 802.15.4 header with CSL IEs.
+    // The tx frame is modified at the right offset (see mInfo.mTxInfo.mIeInfo->mTimeIeOffset)
+
+    int8_t offset = 0;
+    if (sCslPeriod > 0)
+    {
+        uint8_t *finger = sAckIeData;
+        memcpy(finger, sCslIeHeader, OT_IE_HEADER_SIZE);
+        finger += OT_IE_HEADER_SIZE;
+
+        uint16_t cslPhase = getCslPhase();
+        *finger++         = HIGH_BYTE(cslPhase);
+        *finger++         = LOW_BYTE(cslPhase);
+
+        *finger++ = HIGH_BYTE((uint16_t)sCslPeriod);
+        *finger++ = LOW_BYTE((uint16_t)sCslPeriod);
+
+        offset = finger - sAckIeData;
+    }
+
+    sAckIeDataLength = offset;
+}
+#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+#endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+
+//------------------------------------------------------------------------------
+// Forward Declarations
 
 static void RAILCb_Generic(RAIL_Handle_t aRailHandle, RAIL_Events_t aEvents);
 
-static const RAIL_IEEE802154_Config_t sRailIeee802154Config = {
-    .addresses = NULL,
-    .ackConfig =
-        {
-            .enable     = true,
-            .ackTimeout = 672,
-            .rxTransitions =
-                {
-                    .success = RAIL_RF_STATE_RX,
-                    .error   = RAIL_RF_STATE_RX,
-                },
-            .txTransitions =
-                {
-                    .success = RAIL_RF_STATE_RX,
-                    .error   = RAIL_RF_STATE_RX,
-                },
-        },
-    .timings =
-        {
-            .idleToRx            = 100,
-            .txToRx              = 192 - 10,
-            .idleToTx            = 100,
-            .rxToTx              = 192,
-            .rxSearchTimeout     = 0,
-            .txToRxSearchTimeout = 0,
-        },
-    .framesMask                        = RAIL_IEEE802154_ACCEPT_STANDARD_FRAMES,
-    .promiscuousMode                   = false,
-    .isPanCoordinator                  = false,
-    .defaultFramePendingInOutgoingAcks = false,
-};
+static void efr32PhyStackInit(void);
 
-static int8_t sCcaThresholdDbm = -75; // default -75dBm energy detect threshold
+#ifdef SL_CATALOG_RAIL_UTIL_COEX_PRESENT
+static void efr32CoexInit(void);
+// Try to transmit the current outgoing frame subject to MAC-level PTA
+static void tryTxCurrentPacket(void);
+#else
+// Transmit the current outgoing frame.
+void txCurrentPacket(void);
+#define tryTxCurrentPacket txCurrentPacket
+#endif // SL_CATALOG_RAIL_UTIL_COEX_PRESENT
 
-static efr32BandConfig *sCurrentBandConfig = NULL;
+static void txFailedCallback(bool isAck, uint8_t status);
+
+static bool validatePacketDetails(RAIL_RxPacketHandle_t   packetHandle,
+                                  RAIL_RxPacketDetails_t *pPacketDetails,
+                                  RAIL_RxPacketInfo_t *   pPacketInfo,
+                                  uint16_t *              packetLength);
+static bool validatePacketTimestamp(RAIL_RxPacketDetails_t *pPacketDetails, uint16_t packetLength);
+static void updateRxFrameDetails(RAIL_RxPacketDetails_t *pPacketDetails, bool framePendingSetInOutgoingAck);
+
+//------------------------------------------------------------------------------
+// Helper Functions
+
+#ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT
+
+static bool phyStackEventIsEnabled(void)
+{
+    bool result = false;
+
+#if (defined(SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT) && SL_RAIL_UTIL_ANT_DIV_RX_RUNTIME_PHY_SELECT)
+    result = true;
+#endif // SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT
+
+#ifdef SL_CATALOG_RAIL_UTIL_COEX_PRESENT
+    result |= (sl_rail_util_coex_is_enabled() && sRadioCoexEnabled);
+#endif // SL_CATALOG_RAIL_UTIL_COEX_PRESENT
+
+    return result;
+}
+
+static RAIL_Events_t currentEventConfig = RAIL_EVENTS_NONE;
+static void          updateEvents(RAIL_Events_t mask, RAIL_Events_t values)
+{
+    RAIL_Status_t status;
+    RAIL_Events_t newEventConfig = (currentEventConfig & ~mask) | (values & mask);
+    if (newEventConfig != currentEventConfig)
+    {
+        currentEventConfig = newEventConfig;
+        status             = RAIL_ConfigEvents(gRailHandle, mask, values);
+        assert(status == RAIL_STATUS_NO_ERROR);
+    }
+}
+
+static sl_rail_util_ieee802154_stack_event_t handlePhyStackEvent(sl_rail_util_ieee802154_stack_event_t stackEvent,
+                                                                 uint32_t                              supplement)
+{
+    return (phyStackEventIsEnabled() ? sl_rail_util_ieee802154_on_event(stackEvent, supplement) : 0);
+}
+#else
+static void updateEvents(RAIL_Events_t mask, RAIL_Events_t values)
+{
+    RAIL_Status_t status;
+    status = RAIL_ConfigEvents(gRailHandle, mask, values);
+    assert(status == RAIL_STATUS_NO_ERROR);
+}
+
+#define handlePhyStackEvent(event, supplement) 0
+#endif // SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT
+
+// Set or clear the passed flag.
+static inline void setInternalFlag(uint16_t flag, bool val)
+{
+    CORE_DECLARE_IRQ_STATE;
+    CORE_ENTER_ATOMIC();
+    miscInternalFlags = (val ? (miscInternalFlags | flag) : (miscInternalFlags & ~flag));
+    CORE_EXIT_ATOMIC();
+}
+// Returns true if the passed flag is set, false otherwise.
+static inline bool getInternalFlag(uint16_t flag)
+{
+    return ((miscInternalFlags & flag) != 0);
+}
+
+static inline bool txWaitingForAck(void)
+{
+    return (sTransmitBusy == true && ((sTransmitFrame.mPsdu[0] & IEEE802154_FRAME_FLAG_ACK_REQUIRED) != 0));
+}
+
+static bool txIsDataRequest(void)
+{
+    uint16_t fcf = sTransmitFrame.mPsdu[IEEE802154_FCF_OFFSET] | (sTransmitFrame.mPsdu[IEEE802154_FCF_OFFSET + 1] << 8);
+
+    return (sTransmitBusy == true && (fcf & IEEE802154_FRAME_TYPE_MASK) == IEEE802154_FRAME_TYPE_COMMAND);
+}
+
+#ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT
+static inline bool isReceivingFrame(void)
+{
+    return (RAIL_GetRadioState(gRailHandle) & RAIL_RF_STATE_RX_ACTIVE) == RAIL_RF_STATE_RX_ACTIVE;
+}
+#endif
+
+static void radioSetIdle(void)
+{
+    if (RAIL_GetRadioState(gRailHandle) != RAIL_RF_STATE_IDLE)
+    {
+        RAIL_Idle(gRailHandle, RAIL_IDLE, true);
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_IDLED, 0U);
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_IDLED, 0U);
+    }
+    RAIL_YieldRadio(gRailHandle);
+    sState = OT_RADIO_STATE_SLEEP;
+}
+
+static otError radioSetRx(uint8_t aChannel)
+{
+    otError       error = OT_ERROR_NONE;
+    RAIL_Status_t status;
+
+    RAIL_SchedulerInfo_t bgRxSchedulerInfo = {
+        .priority = RADIO_SCHEDULER_BACKGROUND_RX_PRIORITY,
+        // sliptime/transaction time is not used for bg rx
+    };
+
+    status = RAIL_StartRx(gRailHandle, aChannel, &bgRxSchedulerInfo);
+    otEXPECT_ACTION(status == RAIL_STATUS_NO_ERROR, error = OT_ERROR_FAILED);
+
+    (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_LISTEN, 0U);
+    sState = OT_RADIO_STATE_RECEIVE;
+
+    otLogInfoPlat("State=OT_RADIO_STATE_RECEIVE", NULL);
+exit:
+    return error;
+}
+
+//------------------------------------------------------------------------------
+// Radio Initialization
 
 static RAIL_Handle_t efr32RailInit(efr32CommonConfig *aCommonConfig)
 {
@@ -194,29 +506,32 @@
     handle = RAIL_Init(&aCommonConfig->mRailConfig, NULL);
     assert(handle != NULL);
 
+#if defined(SL_CATALOG_POWER_MANAGER_PRESENT)
+    status = RAIL_InitPowerManager();
+    assert(status == RAIL_STATUS_NO_ERROR);
+#endif // SL_CATALOG_POWER_MANAGER_PRESENT
+
     status = RAIL_ConfigCal(handle, RAIL_CAL_ALL);
     assert(status == RAIL_STATUS_NO_ERROR);
 
     status = RAIL_IEEE802154_Init(handle, &sRailIeee802154Config);
     assert(status == RAIL_STATUS_NO_ERROR);
 
-    status = RAIL_ConfigEvents(handle, RAIL_EVENTS_ALL,
-                               RAIL_EVENT_RX_ACK_TIMEOUT |                      //
-                                   RAIL_EVENTS_TX_COMPLETION |                  //
-                                   RAIL_EVENT_RX_PACKET_RECEIVED |              //
-                                   RAIL_EVENT_RSSI_AVERAGE_DONE |               //
-                                   RAIL_EVENT_IEEE802154_DATA_REQUEST_COMMAND | //
-                                   RAIL_EVENT_CAL_NEEDED |                      //
-#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT || RADIO_CONFIG_DMP_SUPPORT
-                                   RAIL_EVENT_CONFIG_SCHEDULED |   //
-                                   RAIL_EVENT_CONFIG_UNSCHEDULED | //
-#endif
-                                   RAIL_EVENT_SCHEDULER_STATUS //
-    );
-    assert(status == RAIL_STATUS_NO_ERROR);
+#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+    // Enhanced Frame Pending
+    // status = RAIL_IEEE802154_EnableEarlyFramePending(handle, true);
+    // assert(status == RAIL_STATUS_NO_ERROR);
 
-    uint16_t actualLength = RAIL_SetTxFifo(handle, aCommonConfig->mRailTxFifo, 0, sizeof(aCommonConfig->mRailTxFifo));
-    assert(actualLength == sizeof(aCommonConfig->mRailTxFifo));
+    // status = RAIL_IEEE802154_EnableDataFramePending(handle, true);
+    // assert(status == RAIL_STATUS_NO_ERROR);
+
+    // Enhanced ACKs (only on platforms that support it, so error checking is disabled)
+    RAIL_IEEE802154_ConfigEOptions(handle, (RAIL_IEEE802154_E_OPTION_GB868 | RAIL_IEEE802154_E_OPTION_ENH_ACK),
+                                   (RAIL_IEEE802154_E_OPTION_GB868 | RAIL_IEEE802154_E_OPTION_ENH_ACK));
+#endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+
+    uint16_t actualLenth = RAIL_SetTxFifo(handle, aCommonConfig->mRailTxFifo, 0, sizeof(aCommonConfig->mRailTxFifo));
+    assert(actualLenth == sizeof(aCommonConfig->mRailTxFifo));
 
     return handle;
 }
@@ -237,7 +552,11 @@
     else
 #endif // RADIO_CONFIG_915MHZ_OQPSK_SUPPORT
     {
+#ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_PHY_SELECT_PRESENT
+        status = sl_rail_util_plugin_config_2p4ghz_radio(gRailHandle);
+#else
         status = RAIL_IEEE802154_Config2p4GHzRadio(gRailHandle);
+#endif // SL_CATALOG_RAIL_UTIL_IEEE802154_PHY_SELECT_PRESENT
         assert(status == RAIL_STATUS_NO_ERROR);
     }
 
@@ -260,9 +579,9 @@
 
     for (uint8_t i = 0; i < EFR32_NUM_BAND_CONFIGS; i++)
     {
-        if ((sBandConfigs[i].mChannelMin <= aChannel) && (aChannel <= sBandConfigs[i].mChannelMax))
+        if ((sBandConfig[i].mChannelMin <= aChannel) && (aChannel <= sBandConfig[i].mChannelMax))
         {
-            config = &sBandConfigs[i];
+            config = &sBandConfig[i];
             break;
         }
     }
@@ -283,17 +602,17 @@
     uint8_t index = 0;
 
 #if RADIO_CONFIG_2P4GHZ_OQPSK_SUPPORT
-    sBandConfigs[index].mChannelConfig = NULL;
-    sBandConfigs[index].mChannelMin    = OT_RADIO_2P4GHZ_OQPSK_CHANNEL_MIN;
-    sBandConfigs[index].mChannelMax    = OT_RADIO_2P4GHZ_OQPSK_CHANNEL_MAX;
+    sBandConfig[index].mChannelConfig = NULL;
+    sBandConfig[index].mChannelMin    = OT_RADIO_2P4GHZ_OQPSK_CHANNEL_MIN;
+    sBandConfig[index].mChannelMax    = OT_RADIO_2P4GHZ_OQPSK_CHANNEL_MAX;
 
     index++;
 #endif
 
 #if RADIO_CONFIG_915MHZ_OQPSK_SUPPORT
-    sBandConfigs[index].mChannelConfig = channelConfigs[0];
-    sBandConfigs[index].mChannelMin    = OT_RADIO_915MHZ_OQPSK_CHANNEL_MIN;
-    sBandConfigs[index].mChannelMax    = OT_RADIO_915MHZ_OQPSK_CHANNEL_MAX;
+    sBandConfig[index].mChannelConfig = channelConfigs[0]; // TO DO: channel config??
+    sBandConfig[index].mChannelMin    = OT_RADIO_915MHZ_OQPSK_CHANNEL_MIN;
+    sBandConfig[index].mChannelMax    = OT_RADIO_915MHZ_OQPSK_CHANNEL_MAX;
 #endif
 
 #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
@@ -302,11 +621,24 @@
 
     gRailHandle = efr32RailInit(&sCommonConfig);
     assert(gRailHandle != NULL);
-    efr32RailConfigLoad(&(sBandConfigs[0]));
+
+    updateEvents(RAIL_EVENTS_ALL,
+                 (0 | RAIL_EVENT_RX_ACK_TIMEOUT | RAIL_EVENT_RX_PACKET_RECEIVED | RAIL_EVENTS_TXACK_COMPLETION |
+                  RAIL_EVENTS_TX_COMPLETION | RAIL_EVENT_RSSI_AVERAGE_DONE | RAIL_EVENT_IEEE802154_DATA_REQUEST_COMMAND
+#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT || RADIO_CONFIG_DMP_SUPPORT
+                  | RAIL_EVENT_CONFIG_SCHEDULED | RAIL_EVENT_CONFIG_UNSCHEDULED | RAIL_EVENT_SCHEDULER_STATUS
+#endif
+                  | RAIL_EVENT_CAL_NEEDED));
+
+    efr32RailConfigLoad(&(sBandConfig[0]));
 }
 
 void efr32RadioInit(void)
 {
+    if (getInternalFlag(FLAG_RADIO_INIT_DONE))
+    {
+        return;
+    }
     RAIL_Status_t status;
 
     // check if RAIL_TX_FIFO_SIZE is power of two..
@@ -316,24 +648,33 @@
     assert((RAIL_TX_FIFO_SIZE >= 64) || (RAIL_TX_FIFO_SIZE <= 4096));
 
     efr32ConfigInit(RAILCb_Generic);
+    setInternalFlag(FLAG_RADIO_INIT_DONE, true);
 
     status = RAIL_ConfigSleep(gRailHandle, RAIL_SLEEP_CONFIG_TIMERSYNC_ENABLED);
     assert(status == RAIL_STATUS_NO_ERROR);
 
-    sReceiveFrame.mLength  = 0;
-    sReceiveFrame.mPsdu    = sReceivePsdu;
-    sTransmitFrame.mLength = 0;
-    sTransmitFrame.mPsdu   = sTransmitPsdu;
+    sReceiveFrame.mLength    = 0;
+    sReceiveFrame.mPsdu      = sReceivePsdu;
+    sReceiveAckFrame.mLength = 0;
+    sReceiveAckFrame.mPsdu   = sReceiveAckPsdu;
+    sTransmitFrame.mLength   = 0;
+    sTransmitFrame.mPsdu     = sTransmitPsdu;
+
+#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
+    sTransmitFrame.mInfo.mTxInfo.mIeInfo = &sTransmitIeInfo;
+#endif
+#endif
 
     sCurrentBandConfig = efr32RadioGetBandConfig(OPENTHREAD_CONFIG_DEFAULT_CHANNEL);
     assert(sCurrentBandConfig != NULL);
 
-    memset(sAckedWithFPFifo, 0, sizeof(sAckedWithFPFifo));
-    sAckedWithFPWriteIndex = 0;
-    sAckedWithFPReadIndex  = 0;
-
     efr32RadioSetTxPower(OPENTHREAD_CONFIG_DEFAULT_TRANSMIT_POWER);
 
+    assert(RAIL_ConfigRxOptions(gRailHandle, RAIL_RX_OPTION_TRACK_ABORTED_FRAMES,
+                                RAIL_RX_OPTION_TRACK_ABORTED_FRAMES) == RAIL_STATUS_NO_ERROR);
+    efr32PhyStackInit();
+
     sEnergyScanStatus = ENERGY_SCAN_STATUS_IDLE;
     sTransmitError    = OT_ERROR_NONE;
     sTransmitBusy     = false;
@@ -352,6 +693,15 @@
     sCurrentBandConfig = NULL;
 }
 
+//------------------------------------------------------------------------------
+// Energy Scan support
+
+static void energyScanComplete(int8_t scanResultDbm)
+{
+    sEnergyScanResultDbm = scanResultDbm;
+    sEnergyScanStatus    = ENERGY_SCAN_STATUS_COMPLETED;
+}
+
 static otError efr32StartEnergyScan(energyScanMode aMode, uint16_t aChannel, RAIL_Time_t aAveragingTimeUs)
 {
     RAIL_Status_t    status = RAIL_STATUS_NO_ERROR;
@@ -382,9 +732,16 @@
     otEXPECT_ACTION(status == RAIL_STATUS_NO_ERROR, error = OT_ERROR_FAILED);
 
 exit:
+    if (status != RAIL_STATUS_NO_ERROR)
+    {
+        energyScanComplete(OT_RADIO_RSSI_INVALID);
+    }
     return error;
 }
 
+//------------------------------------------------------------------------------
+// Stack support
+
 void otPlatRadioGetIeeeEui64(otInstance *aInstance, uint8_t *aIeeeEui64)
 {
     OT_UNUSED_VARIABLE(aInstance);
@@ -418,6 +775,14 @@
 void otPlatRadioSetExtendedAddress(otInstance *aInstance, const otExtAddress *aAddress)
 {
     OT_UNUSED_VARIABLE(aInstance);
+#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
+    for (size_t i = 0; i < sizeof(*aAddress); i++)
+    {
+        sExtAddress.m8[i] = aAddress->m8[sizeof(*aAddress) - 1 - i];
+    }
+#endif
+#endif
 
     RAIL_Status_t status;
 
@@ -472,18 +837,13 @@
 otError otPlatRadioSleep(otInstance *aInstance)
 {
     OT_UNUSED_VARIABLE(aInstance);
-
     otError error = OT_ERROR_NONE;
 
     otEXPECT_ACTION((sState != OT_RADIO_STATE_TRANSMIT) && (sState != OT_RADIO_STATE_DISABLED),
                     error = OT_ERROR_INVALID_STATE);
 
     otLogInfoPlat("State=OT_RADIO_STATE_SLEEP", NULL);
-
-    RAIL_Idle(gRailHandle, RAIL_IDLE, true);
-    RAIL_YieldRadio(gRailHandle);
-    sState = OT_RADIO_STATE_SLEEP;
-
+    radioSetIdle();
 exit:
     return error;
 }
@@ -507,17 +867,11 @@
         sCurrentBandConfig = config;
     }
 
-    RAIL_SchedulerInfo_t bgRxSchedulerInfo = {
-        .priority = RADIO_SCHEDULER_BACKGROUND_RX_PRIORITY,
-        // sliptime/transaction time is not used for bg rx
-    };
-
-    status = RAIL_StartRx(gRailHandle, aChannel, &bgRxSchedulerInfo);
+    status = radioSetRx(aChannel);
     otEXPECT_ACTION(status == RAIL_STATUS_NO_ERROR, error = OT_ERROR_FAILED);
 
-    otLogInfoPlat("State=OT_RADIO_STATE_RECEIVE", NULL);
-    sState                 = OT_RADIO_STATE_RECEIVE;
-    sReceiveFrame.mChannel = aChannel;
+    sReceiveFrame.mChannel    = aChannel;
+    sReceiveAckFrame.mChannel = aChannel;
 
 exit:
     return error;
@@ -525,29 +879,14 @@
 
 otError otPlatRadioTransmit(otInstance *aInstance, otRadioFrame *aFrame)
 {
-    otError           error      = OT_ERROR_NONE;
-    RAIL_CsmaConfig_t csmaConfig = RAIL_CSMA_CONFIG_802_15_4_2003_2p4_GHz_OQPSK_CSMA;
-    RAIL_TxOptions_t  txOptions  = RAIL_TX_OPTIONS_DEFAULT;
-    efr32BandConfig * config;
-    RAIL_Status_t     status;
-    uint8_t           frameLength;
-
-#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
-    sRailDebugCounters.mRailPlatTxTriggered++;
-#endif
-
-    assert(sTransmitBusy == false);
+    otError          error = OT_ERROR_NONE;
+    efr32BandConfig *config;
 
     otEXPECT_ACTION((sState != OT_RADIO_STATE_DISABLED) && (sState != OT_RADIO_STATE_TRANSMIT),
                     error = OT_ERROR_INVALID_STATE);
 
     config = efr32RadioGetBandConfig(aFrame->mChannel);
     otEXPECT_ACTION(config != NULL, error = OT_ERROR_INVALID_ARGS);
-
-    sState         = OT_RADIO_STATE_TRANSMIT;
-    sTransmitError = OT_ERROR_NONE;
-    sTransmitBusy  = true;
-
     if (sCurrentBandConfig != config)
     {
         RAIL_Idle(gRailHandle, RAIL_IDLE, true);
@@ -555,9 +894,115 @@
         sCurrentBandConfig = config;
     }
 
-    frameLength = (uint8_t)aFrame->mLength;
+    assert(sTransmitBusy == false);
+    sState         = OT_RADIO_STATE_TRANSMIT;
+    sTransmitError = OT_ERROR_NONE;
+    sTransmitBusy  = true;
+    sTxFrame       = aFrame;
+
+    setInternalFlag(FLAG_CURRENT_TX_USE_CSMA, aFrame->mInfo.mTxInfo.mCsmaCaEnabled);
+
+    CORE_DECLARE_IRQ_STATE;
+    CORE_ENTER_ATOMIC();
+    setInternalFlag(FLAG_ONGOING_TX_DATA, true);
+    tryTxCurrentPacket();
+    CORE_EXIT_ATOMIC();
+
+    if (sTransmitError == OT_ERROR_NONE)
+    {
+        otPlatRadioTxStarted(aInstance, aFrame);
+    }
+exit:
+    return error;
+}
+
+void txCurrentPacket(void)
+{
+    assert(getInternalFlag(FLAG_ONGOING_TX_DATA));
+    assert(sTxFrame != NULL);
+
+    RAIL_CsmaConfig_t csmaConfig = RAIL_CSMA_CONFIG_802_15_4_2003_2p4_GHz_OQPSK_CSMA;
+    RAIL_TxOptions_t  txOptions  = RAIL_TX_OPTIONS_DEFAULT;
+    RAIL_Status_t     status;
+    uint8_t           frameLength;
+    bool              ackRequested;
+
+#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
+    sRailDebugCounters.mRailPlatTxTriggered++;
+#endif
+
+    // signalling this event earlier, as this event can assert REQ (expecially for a
+    // non-CSMA transmit) giving the Coex master a little more time to grant or deny.
+    if (getInternalFlag(FLAG_CURRENT_TX_USE_CSMA))
+    {
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_PENDED_PHY, (uint32_t) true);
+    }
+    else
+    {
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_PENDED_PHY, (uint32_t) false);
+    }
+
+    frameLength = (uint8_t)sTxFrame->mLength;
+
+#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+    // Update IE data in the 802.15.4 header with the newest CSL period / phase
+    if (sCslPeriod > 0)
+    {
+        otMacFrameSetCslIe(sTxFrame, (uint16_t)sCslPeriod, getCslPhase());
+    }
+#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+
+#if OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE
+    bool processSecurity = false;
+#endif
+
+#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
+    // Seek the time sync offset and update the rendezvous time
+    if (sTxFrame->mInfo.mTxInfo.mIeInfo->mTimeIeOffset != 0)
+    {
+        uint8_t *timeIe = sTxFrame->mPsdu + sTxFrame->mInfo.mTxInfo.mIeInfo->mTimeIeOffset;
+        uint64_t time   = otPlatTimeGet() + sTxFrame->mInfo.mTxInfo.mIeInfo->mNetworkTimeOffset;
+
+        *timeIe = sTxFrame->mInfo.mTxInfo.mIeInfo->mTimeSyncSeq;
+
+        *(++timeIe) = (uint8_t)(time & 0xff);
+        for (uint8_t i = 1; i < sizeof(uint64_t); i++)
+        {
+            time        = time >> 8;
+            *(++timeIe) = (uint8_t)(time & 0xff);
+        }
+
+#if OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE
+        processSecurity = true;
+#endif
+    }
+#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
+
+#if OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE
+    if (otMacFrameIsSecurityEnabled(sTxFrame) && otMacFrameIsKeyIdMode1(sTxFrame) &&
+        !sTxFrame->mInfo.mTxInfo.mIsSecurityProcessed)
+    {
+        sTxFrame->mInfo.mTxInfo.mAesKey = &sCurrKey;
+
+        if (!sTxFrame->mInfo.mTxInfo.mIsARetx)
+        {
+            otMacFrameSetKeyId(sTxFrame, sKeyId);
+            otMacFrameSetFrameCounter(sTxFrame, sMacFrameCounter++);
+        }
+
+        processSecurity = true;
+    }
+
+    if (processSecurity)
+    {
+        otMacFrameProcessTransmitAesCcm(sTxFrame, &sExtAddress);
+    }
+#endif // OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE
+#endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
+
     RAIL_WriteTxFifo(gRailHandle, &frameLength, sizeof frameLength, true);
-    RAIL_WriteTxFifo(gRailHandle, aFrame->mPsdu, frameLength - 2, false);
+    RAIL_WriteTxFifo(gRailHandle, sTxFrame->mPsdu, frameLength - 2, false);
 
     RAIL_SchedulerInfo_t txSchedulerInfo = {
         .priority        = RADIO_SCHEDULER_TX_PRIORITY,
@@ -565,7 +1010,8 @@
         .transactionTime = 0, // will be calculated later if DMP is used
     };
 
-    if (aFrame->mPsdu[0] & IEEE802154_ACK_REQUEST)
+    ackRequested = (sTxFrame->mPsdu[0] & IEEE802154_FRAME_FLAG_ACK_REQUIRED);
+    if (ackRequested)
     {
         txOptions |= RAIL_TX_OPTION_WAIT_FOR_ACK;
 
@@ -582,6 +1028,18 @@
 #endif
     }
 
+#ifdef SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT
+    // Update Tx options to use currently-selected antenna.
+    // If antenna diverisity on Tx is disabled, leave both options 0
+    // so Tx antenna tracks Rx antenna.
+    if (sl_rail_util_ant_div_get_antenna_mode() != SL_RAIL_UTIL_ANT_DIV_DISABLED)
+    {
+        txOptions |= ((sl_rail_util_ant_div_get_antenna_selected() == SL_RAIL_UTIL_ANTENNA_SELECT_ANTENNA1)
+                          ? RAIL_TX_OPTION_ANTENNA0
+                          : RAIL_TX_OPTION_ANTENNA1);
+    }
+#endif // SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT
+
 #if RADIO_CONFIG_DMP_SUPPORT
     // time needed for the frame itself
     // 4B preamble, 1B SFD, 1B PHR is not counted in frameLength
@@ -595,42 +1053,40 @@
     }
 #endif
 
-    if (aFrame->mInfo.mTxInfo.mCsmaCaEnabled)
+    if (getInternalFlag(FLAG_CURRENT_TX_USE_CSMA))
     {
 #if RADIO_CONFIG_DMP_SUPPORT
         // time needed for CSMA/CA
         txSchedulerInfo.transactionTime += RADIO_TIMING_CSMA_OVERHEAD_US;
 #endif
-        csmaConfig.csmaTries    = aFrame->mInfo.mTxInfo.mMaxCsmaBackoffs;
+        csmaConfig.csmaTries    = sTxFrame->mInfo.mTxInfo.mMaxCsmaBackoffs;
         csmaConfig.ccaThreshold = sCcaThresholdDbm;
-
-        status = RAIL_StartCcaCsmaTx(gRailHandle, aFrame->mChannel, txOptions, &csmaConfig, &txSchedulerInfo);
+        status = RAIL_StartCcaCsmaTx(gRailHandle, sTxFrame->mChannel, txOptions, &csmaConfig, &txSchedulerInfo);
     }
     else
     {
-        status = RAIL_StartTx(gRailHandle, aFrame->mChannel, txOptions, &txSchedulerInfo);
+        status = RAIL_StartTx(gRailHandle, sTxFrame->mChannel, txOptions, &txSchedulerInfo);
+        if (status == RAIL_STATUS_NO_ERROR)
+        {
+            (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_STARTED, 0U);
+        }
     }
-
     if (status == RAIL_STATUS_NO_ERROR)
     {
 #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
         sRailDebugCounters.mRailTxStarted++;
 #endif
-        otPlatRadioTxStarted(aInstance, aFrame);
     }
     else
     {
 #if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
         sRailDebugCounters.mRailTxStartFailed++;
 #endif
-        // Tx started at an invalid time or an invalid paramter has been passed.
-        sTransmitError = OT_ERROR_CHANNEL_ACCESS_FAILURE;
-        sTransmitBusy  = false;
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_BLOCKED, (uint32_t)ackRequested);
+        txFailedCallback(false, TX_COMPLETE_RESULT_OTHER_FAIL);
+
         otSysEventSignalPending();
     }
-
-exit:
-    return error;
 }
 
 otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *aInstance)
@@ -672,7 +1128,18 @@
 {
     OT_UNUSED_VARIABLE(aInstance);
 
-    return OT_RADIO_CAPS_ACK_TIMEOUT | OT_RADIO_CAPS_CSMA_BACKOFF | OT_RADIO_CAPS_ENERGY_SCAN;
+    otRadioCaps capabilities = (OT_RADIO_CAPS_ACK_TIMEOUT | OT_RADIO_CAPS_CSMA_BACKOFF | OT_RADIO_CAPS_ENERGY_SCAN |
+                                OT_RADIO_CAPS_SLEEP_TO_TX);
+
+#if OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE
+    capabilities |= OT_RADIO_CAPS_TRANSMIT_SEC;
+#endif
+
+#if OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_TIMING_ENABLE
+    capabilities |= OT_RADIO_CAPS_TRANSMIT_TIMING;
+#endif
+
+    return capabilities;
 }
 
 bool otPlatRadioGetPromiscuous(otInstance *aInstance)
@@ -702,420 +1169,6 @@
     sIsSrcMatchEnabled = aEnable;
 }
 
-static bool sAckedWithFPFifoIsFull(void)
-{
-    return (uint32_t)(sAckedWithFPWriteIndex - sAckedWithFPReadIndex) == otARRAY_LENGTH(sAckedWithFPFifo);
-}
-
-static bool sAckedWithFPFifoIsEmpty(void)
-{
-    return (uint32_t)(sAckedWithFPWriteIndex - sAckedWithFPReadIndex) == 0;
-}
-
-static efr32AckedWithFP *sAckedWithFPFifoGetWriteSlot(void)
-{
-    uint32_t idx = sAckedWithFPWriteIndex & (otARRAY_LENGTH(sAckedWithFPFifo) - 1);
-    return &sAckedWithFPFifo[idx];
-}
-
-static const efr32AckedWithFP *sAckedWithFPFifoGetReadSlot(void)
-{
-    uint32_t idx = sAckedWithFPReadIndex & (otARRAY_LENGTH(sAckedWithFPFifo) - 1);
-    return &sAckedWithFPFifo[idx];
-}
-
-static void insertIeee802154DataRequestCommand(RAIL_Handle_t aRailHandle)
-{
-    assert(!sAckedWithFPFifoIsFull());
-    efr32AckedWithFP *const slot = sAckedWithFPFifoGetWriteSlot();
-
-    RAIL_RxPacketInfo_t packetInfo;
-
-    RAIL_GetRxIncomingPacketInfo(aRailHandle, &packetInfo);
-    assert(packetInfo.packetBytes >= 4); // PHR + FCF + DSN
-
-    if (packetInfo.packetBytes > sizeof(slot->mPacket))
-    {
-        packetInfo.packetBytes = sizeof(slot->mPacket);
-        if (packetInfo.firstPortionBytes >= sizeof(slot->mPacket))
-        {
-            packetInfo.firstPortionBytes = sizeof(slot->mPacket);
-            packetInfo.lastPortionData   = NULL;
-        }
-    }
-    slot->mLength = packetInfo.packetBytes;
-    RAIL_CopyRxPacket(slot->mPacket, &packetInfo);
-
-    ++sAckedWithFPWriteIndex;
-}
-
-static bool wasAckedWithFramePending(const uint8_t *aPsdu, uint8_t aPsduLength)
-{
-    bool     ackedWithFramePending = false;
-    uint16_t fcf                   = aPsdu[IEEE802154_FCF_OFFSET] | (aPsdu[IEEE802154_FCF_OFFSET + 1] << 8);
-
-    otEXPECT((fcf & IEEE802154_FRAME_TYPE_MASK) == IEEE802154_FRAME_TYPE_MAC_COMMAND);
-
-    while (!(ackedWithFramePending || sAckedWithFPFifoIsEmpty()))
-    {
-        const efr32AckedWithFP *const slot = sAckedWithFPFifoGetReadSlot();
-        if ((slot->mPacket[0] == aPsduLength) && (memcmp(slot->mPacket + 1, aPsdu, slot->mLength - 1) == 0))
-        {
-            ackedWithFramePending = true;
-        }
-        ++sAckedWithFPReadIndex;
-    }
-
-exit:
-    return ackedWithFramePending;
-}
-
-static void processNextRxPacket(otInstance *aInstance)
-{
-    RAIL_RxPacketHandle_t  packetHandle = RAIL_RX_PACKET_HANDLE_INVALID;
-    RAIL_RxPacketInfo_t    packetInfo;
-    RAIL_RxPacketDetails_t packetDetails;
-    RAIL_Status_t          status;
-    uint16_t               length;
-    bool                   framePending = false;
-
-    packetHandle = RAIL_GetRxPacketInfo(gRailHandle, RAIL_RX_PACKET_HANDLE_OLDEST, &packetInfo);
-
-    otEXPECT_ACTION(packetHandle != RAIL_RX_PACKET_HANDLE_INVALID &&
-                        packetInfo.packetStatus == RAIL_RX_PACKET_READY_SUCCESS,
-                    packetHandle = RAIL_RX_PACKET_HANDLE_INVALID);
-
-    status = RAIL_GetRxPacketDetailsAlt(gRailHandle, packetHandle, &packetDetails);
-    otEXPECT(status == RAIL_STATUS_NO_ERROR);
-
-    length = packetInfo.packetBytes + 1;
-
-    // check the length in recv packet info structure; RAIL should take care of this.
-    otEXPECT(length == packetInfo.firstPortionData[0]);
-
-    // check the length validity of recv packet; RAIL should take care of this.
-    otEXPECT(length >= IEEE802154_MIN_LENGTH && length <= IEEE802154_MAX_LENGTH);
-
-    otLogInfoPlat("Received data:%d", length);
-
-    // skip length byte
-    otEXPECT(packetInfo.firstPortionBytes > 0);
-    packetInfo.firstPortionData++;
-    packetInfo.firstPortionBytes--;
-    packetInfo.packetBytes--;
-
-    // read packet
-    RAIL_CopyRxPacket(sReceiveFrame.mPsdu, &packetInfo);
-
-    status = RAIL_ReleaseRxPacket(gRailHandle, packetHandle);
-    if (status == RAIL_STATUS_NO_ERROR)
-    {
-        packetHandle = RAIL_RX_PACKET_HANDLE_INVALID;
-    }
-
-    sReceiveFrame.mLength = length;
-
-    sReceiveFrame.mInfo.mRxInfo.mRssi = packetDetails.rssi;
-    sReceiveFrame.mInfo.mRxInfo.mLqi  = packetDetails.lqi;
-
-    // Get the timestamp when the SFD was received
-    assert(packetDetails.timeReceived.timePosition != RAIL_PACKET_TIME_INVALID);
-    packetDetails.timeReceived.totalPacketBytes = length + 1;
-
-    status = RAIL_GetRxTimeSyncWordEndAlt(gRailHandle, &packetDetails);
-    assert(status == RAIL_STATUS_NO_ERROR);
-    sReceiveFrame.mInfo.mRxInfo.mTimestamp = packetDetails.timeReceived.packetTime;
-
-    if (packetDetails.isAck)
-    {
-        otEXPECT((length == IEEE802154_ACK_LENGTH) &&
-                 (sReceiveFrame.mPsdu[0] & IEEE802154_FRAME_TYPE_MASK) == IEEE802154_FRAME_TYPE_ACK);
-
-        sReceiveFrame.mInfo.mRxInfo.mAckedWithFramePending = false;
-
-        RAIL_YieldRadio(gRailHandle);
-        sTransmitBusy = false;
-
-        if (sReceiveFrame.mPsdu[IEEE802154_DSN_OFFSET] == sTransmitFrame.mPsdu[IEEE802154_DSN_OFFSET])
-        {
-            sTransmitError = OT_ERROR_NONE;
-        }
-        else
-        {
-            sTransmitError = OT_ERROR_NO_ACK;
-        }
-    }
-    else
-    {
-        // signal MAC layer for each received frame if promiscuous is enabled
-        // otherwise only signal MAC layer for non-ACK frame
-        otEXPECT(sPromiscuous || (length != IEEE802154_ACK_LENGTH));
-
-        sReceiveError = OT_ERROR_NONE;
-
-        // Set this flag only when the packet is really acknowledged with frame pending set.
-        framePending = wasAckedWithFramePending(sReceiveFrame.mPsdu, sReceiveFrame.mLength);
-        sReceiveFrame.mInfo.mRxInfo.mAckedWithFramePending = framePending;
-
-#if OPENTHREAD_CONFIG_DIAG_ENABLE
-
-        if (otPlatDiagModeGet())
-        {
-            otPlatDiagRadioReceiveDone(aInstance, &sReceiveFrame, sReceiveError);
-        }
-        else
-#endif
-        {
-            otLogInfoPlat("Received %d bytes", sReceiveFrame.mLength);
-            otPlatRadioReceiveDone(aInstance, &sReceiveFrame, sReceiveError);
-#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
-            sRailDebugCounters.mRailPlatRadioReceiveDoneCbCount++;
-#endif
-        }
-        if (!framePending)
-        {
-            RAIL_YieldRadio(gRailHandle);
-        }
-    }
-    otSysEventSignalPending();
-
-exit:
-
-    if (packetHandle != RAIL_RX_PACKET_HANDLE_INVALID)
-    {
-        RAIL_ReleaseRxPacket(gRailHandle, packetHandle);
-    }
-}
-
-static void ieee802154DataRequestCommand(RAIL_Handle_t aRailHandle)
-{
-    RAIL_Status_t status = RAIL_STATUS_NO_ERROR;
-
-    if (sIsSrcMatchEnabled)
-    {
-        RAIL_IEEE802154_Address_t sourceAddress;
-
-        status = RAIL_IEEE802154_GetAddress(aRailHandle, &sourceAddress);
-        otEXPECT(status == RAIL_STATUS_NO_ERROR);
-
-        if ((sourceAddress.length == RAIL_IEEE802154_LongAddress &&
-             utilsSoftSrcMatchExtFindEntry((otExtAddress *)sourceAddress.longAddress) >= 0) ||
-            (sourceAddress.length == RAIL_IEEE802154_ShortAddress &&
-             utilsSoftSrcMatchShortFindEntry(sourceAddress.shortAddress) >= 0))
-        {
-            status = RAIL_IEEE802154_SetFramePending(aRailHandle);
-            otEXPECT(status == RAIL_STATUS_NO_ERROR);
-            insertIeee802154DataRequestCommand(aRailHandle);
-        }
-    }
-    else
-    {
-        status = RAIL_IEEE802154_SetFramePending(aRailHandle);
-        otEXPECT(status == RAIL_STATUS_NO_ERROR);
-        insertIeee802154DataRequestCommand(aRailHandle);
-    }
-
-exit:
-    if (status == RAIL_STATUS_INVALID_STATE)
-    {
-        otLogWarnPlat("Too late to modify outgoing FP");
-    }
-    else
-    {
-        assert(status == RAIL_STATUS_NO_ERROR);
-    }
-}
-
-static void RAILCb_Generic(RAIL_Handle_t aRailHandle, RAIL_Events_t aEvents)
-{
-#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
-    if (aEvents & RAIL_EVENT_CONFIG_SCHEDULED)
-    {
-        sRailDebugCounters.mRailEventConfigScheduled++;
-    }
-    if (aEvents & RAIL_EVENT_CONFIG_UNSCHEDULED)
-    {
-        sRailDebugCounters.mRailEventConfigUnScheduled++;
-    }
-#endif
-    if (aEvents & RAIL_EVENT_IEEE802154_DATA_REQUEST_COMMAND)
-    {
-        ieee802154DataRequestCommand(aRailHandle);
-    }
-
-    if (aEvents & RAIL_EVENTS_TX_COMPLETION)
-    {
-        if (aEvents & RAIL_EVENT_TX_PACKET_SENT)
-        {
-            if ((sTransmitFrame.mPsdu[0] & IEEE802154_ACK_REQUEST) == 0)
-            {
-                RAIL_YieldRadio(aRailHandle);
-                sTransmitError = OT_ERROR_NONE;
-                sTransmitBusy  = false;
-            }
-#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
-            sRailDebugCounters.mRailEventPacketSent++;
-#endif
-        }
-        else if (aEvents & RAIL_EVENT_TX_CHANNEL_BUSY)
-        {
-            RAIL_YieldRadio(aRailHandle);
-            sTransmitError = OT_ERROR_CHANNEL_ACCESS_FAILURE;
-            sTransmitBusy  = false;
-#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
-            sRailDebugCounters.mRailEventChannelBusy++;
-#endif
-        }
-        else
-        {
-            RAIL_YieldRadio(aRailHandle);
-            sTransmitError = OT_ERROR_ABORT;
-            sTransmitBusy  = false;
-#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
-            sRailDebugCounters.mRailEventTxAbort++;
-#endif
-        }
-    }
-
-    if (aEvents & RAIL_EVENT_RX_ACK_TIMEOUT)
-    {
-        RAIL_YieldRadio(aRailHandle);
-        sTransmitError = OT_ERROR_NO_ACK;
-        sTransmitBusy  = false;
-#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
-        sRailDebugCounters.mRailEventNoAck++;
-#endif
-    }
-
-    if (aEvents & RAIL_EVENT_RX_PACKET_RECEIVED)
-    {
-        RAIL_HoldRxPacket(aRailHandle);
-#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
-        sRailDebugCounters.mRailEventPacketReceived++;
-#endif
-    }
-
-    if (aEvents & RAIL_EVENT_CAL_NEEDED)
-    {
-        RAIL_Status_t status;
-
-        status = RAIL_Calibrate(aRailHandle, NULL, RAIL_CAL_ALL_PENDING);
-        assert(status == RAIL_STATUS_NO_ERROR);
-
-#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
-        sRailDebugCounters.mRailEventCalNeeded++;
-#endif
-    }
-
-    if (aEvents & RAIL_EVENT_RSSI_AVERAGE_DONE)
-    {
-        const int16_t energyScanResultQuarterDbm = RAIL_GetAverageRssi(aRailHandle);
-        RAIL_YieldRadio(aRailHandle);
-
-        sEnergyScanStatus = ENERGY_SCAN_STATUS_COMPLETED;
-
-        if (energyScanResultQuarterDbm == RAIL_RSSI_INVALID)
-        {
-            sEnergyScanResultDbm = OT_RADIO_RSSI_INVALID;
-        }
-        else
-        {
-            sEnergyScanResultDbm = energyScanResultQuarterDbm / QUARTER_DBM_IN_DBM;
-        }
-
-#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
-        sRailDebugCounters.mRailPlatRadioEnergyScanDoneCbCount++;
-#endif
-    }
-    if (aEvents & RAIL_EVENT_SCHEDULER_STATUS)
-    {
-        RAIL_SchedulerStatus_t status = RAIL_GetSchedulerStatus(aRailHandle);
-
-        assert(status != RAIL_SCHEDULER_STATUS_INTERNAL_ERROR);
-
-        if (status == RAIL_SCHEDULER_STATUS_CCA_CSMA_TX_FAIL || status == RAIL_SCHEDULER_STATUS_SINGLE_TX_FAIL ||
-            status == RAIL_SCHEDULER_STATUS_SCHEDULED_TX_FAIL ||
-            (status == RAIL_SCHEDULER_STATUS_SCHEDULE_FAIL && sTransmitBusy) ||
-            (status == RAIL_SCHEDULER_STATUS_EVENT_INTERRUPTED && sTransmitBusy))
-        {
-            sTransmitError = OT_ERROR_ABORT;
-            sTransmitBusy  = false;
-#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
-            sRailDebugCounters.mRailEventSchedulerStatusError++;
-#endif
-        }
-        else if (status == RAIL_SCHEDULER_STATUS_AVERAGE_RSSI_FAIL)
-        {
-            sEnergyScanStatus    = ENERGY_SCAN_STATUS_COMPLETED;
-            sEnergyScanResultDbm = OT_RADIO_RSSI_INVALID;
-        }
-#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
-        else if (sTransmitBusy)
-        {
-            sRailDebugCounters.mRailEventsSchedulerStatusLastStatus = status;
-            sRailDebugCounters.mRailEventsSchedulerStatusTransmitBusy++;
-        }
-#endif
-    }
-
-    otSysEventSignalPending();
-}
-
-otError otPlatRadioEnergyScan(otInstance *aInstance, uint8_t aScanChannel, uint16_t aScanDuration)
-{
-    OT_UNUSED_VARIABLE(aInstance);
-
-    return efr32StartEnergyScan(ENERGY_SCAN_MODE_ASYNC, aScanChannel, (RAIL_Time_t)aScanDuration * US_IN_MS);
-}
-
-void efr32RadioProcess(otInstance *aInstance)
-{
-    // We should process the received packet first. Adding it at the end of this function,
-    // will delay the stack notification until the next call to efr32RadioProcess()
-    processNextRxPacket(aInstance);
-
-    if (sState == OT_RADIO_STATE_TRANSMIT && sTransmitBusy == false)
-    {
-        if (sTransmitError != OT_ERROR_NONE)
-        {
-            otLogDebgPlat("Transmit failed ErrorCode=%d", sTransmitError);
-        }
-
-        sState = OT_RADIO_STATE_RECEIVE;
-#if OPENTHREAD_CONFIG_DIAG_ENABLE
-        if (otPlatDiagModeGet())
-        {
-            otPlatDiagRadioTransmitDone(aInstance, &sTransmitFrame, sTransmitError);
-        }
-        else
-#endif
-            if (((sTransmitFrame.mPsdu[0] & IEEE802154_ACK_REQUEST) == 0) || (sTransmitError != OT_ERROR_NONE))
-        {
-            otPlatRadioTxDone(aInstance, &sTransmitFrame, NULL, sTransmitError);
-        }
-        else
-        {
-            otPlatRadioTxDone(aInstance, &sTransmitFrame, &sReceiveFrame, sTransmitError);
-        }
-
-#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
-        sRailDebugCounters.mRailPlatRadioTxDoneCbCount++;
-#endif
-
-        otSysEventSignalPending();
-    }
-    else if (sEnergyScanMode == ENERGY_SCAN_MODE_ASYNC && sEnergyScanStatus == ENERGY_SCAN_STATUS_COMPLETED)
-    {
-        sEnergyScanStatus = ENERGY_SCAN_STATUS_IDLE;
-        otPlatRadioEnergyScanDone(aInstance, sEnergyScanResultDbm);
-        otSysEventSignalPending();
-
-#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
-        sRailDebugCounters.mRailEventEnergyScanCompleted++;
-#endif
-    }
-}
-
 otError otPlatRadioGetTransmitPower(otInstance *aInstance, int8_t *aPower)
 {
     OT_UNUSED_VARIABLE(aInstance);
@@ -1123,7 +1176,6 @@
     otError error = OT_ERROR_NONE;
 
     otEXPECT_ACTION(aPower != NULL, error = OT_ERROR_INVALID_ARGS);
-
     // RAIL_GetTxPowerDbm() returns power in deci-dBm (0.1dBm)
     // Divide by 10 because aPower is supposed be in units dBm
     *aPower = RAIL_GetTxPowerDbm(gRailHandle) / 10;
@@ -1175,6 +1227,1474 @@
     return EFR32_RECEIVE_SENSITIVITY;
 }
 
+otError otPlatRadioEnergyScan(otInstance *aInstance, uint8_t aScanChannel, uint16_t aScanDuration)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+
+    return efr32StartEnergyScan(ENERGY_SCAN_MODE_ASYNC, aScanChannel, (RAIL_Time_t)aScanDuration * US_IN_MS);
+}
+
+#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+//------------------------------------------------------------------------------
+// Radio Config: Thread 1.2 transmit security support
+
+void otPlatRadioSetMacKey(otInstance *    aInstance,
+                          uint8_t         aKeyIdMode,
+                          uint8_t         aKeyId,
+                          const otMacKey *aPrevKey,
+                          const otMacKey *aCurrKey,
+                          const otMacKey *aNextKey)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+    OT_UNUSED_VARIABLE(aKeyIdMode);
+
+    assert(aPrevKey != NULL && aCurrKey != NULL && aNextKey != NULL);
+
+    CORE_DECLARE_IRQ_STATE;
+    CORE_ENTER_ATOMIC();
+
+    sKeyId = aKeyId;
+    memcpy(sPrevKey.m8, aPrevKey->m8, OT_MAC_KEY_SIZE);
+    memcpy(sCurrKey.m8, aCurrKey->m8, OT_MAC_KEY_SIZE);
+    memcpy(sNextKey.m8, aNextKey->m8, OT_MAC_KEY_SIZE);
+
+    CORE_EXIT_ATOMIC();
+}
+
+void otPlatRadioSetMacFrameCounter(otInstance *aInstance, uint32_t aMacFrameCounter)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+
+    CORE_DECLARE_IRQ_STATE;
+    CORE_ENTER_ATOMIC();
+
+    sMacFrameCounter = aMacFrameCounter;
+
+    CORE_EXIT_ATOMIC();
+}
+
+//------------------------------------------------------------------------------
+// Radio Config: Enhanced Acks, CSL
+
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+otError otPlatRadioEnableCsl(otInstance *aInstance, uint32_t aCslPeriod, const otExtAddress *aExtAddr)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+    OT_UNUSED_VARIABLE(aExtAddr);
+
+    sCslPeriod = aCslPeriod;
+    updateIeData();
+
+    return OT_ERROR_NONE;
+}
+
+void otPlatRadioUpdateCslSampleTime(otInstance *aInstance, uint32_t aCslSampleTime)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+
+    sCslSampleTime = aCslSampleTime;
+}
+#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+
+//------------------------------------------------------------------------------
+// Radio Config: Link Metrics
+
+#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
+otError otPlatRadioConfigureEnhAckProbing(otInstance *         aInstance,
+                                          otLinkMetrics        aLinkMetrics,
+                                          const otShortAddress aShortAddress,
+                                          const otExtAddress * aExtAddress)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+
+    otError error = otLinkMetricsConfigureEnhAckProbing(aShortAddress, aExtAddress, aLinkMetrics);
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+    if (error == OT_ERROR_NONE)
+    {
+        updateIeData();
+    }
+#endif
+
+    return error;
+}
+#endif
+#endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+
+#if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE
+otError otPlatRadioSetCoexEnabled(otInstance *aInstance, bool aEnabled)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+
+    if (aEnabled && !sl_rail_util_coex_is_enabled())
+    {
+        otLogInfoPlat("Coexistence GPIO configurations not set");
+        return OT_ERROR_FAILED;
+    }
+    sRadioCoexEnabled = aEnabled;
+    return OT_ERROR_NONE;
+}
+
+bool otPlatRadioIsCoexEnabled(otInstance *aInstance)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+    return (sRadioCoexEnabled && sl_rail_util_coex_is_enabled());
+}
+
+otError otPlatRadioGetCoexMetrics(otInstance *aInstance, otRadioCoexMetrics *aCoexMetrics)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+    otError error = OT_ERROR_NONE;
+
+    otEXPECT_ACTION(aCoexMetrics != NULL, error = OT_ERROR_INVALID_ARGS);
+
+    memset(aCoexMetrics, 0, sizeof(otRadioCoexMetrics));
+    // TO DO:
+    // Tracking coex metrics with detailed granularity currently
+    // not implemented.
+    // memcpy(aCoexMetrics, &sCoexMetrics, sizeof(otRadioCoexMetrics));
+exit:
+    return error;
+}
+#endif // OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE
+
+#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+//------------------------------------------------------------------------------
+// Radio implementation: Enhanced ACKs, CSL
+
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+static uint8_t getKeySourceLength(uint8_t keyIdMode)
+{
+    uint8_t len = 0;
+
+    switch (keyIdMode)
+    {
+    case IEEE802154_KEYID_MODE_0:
+        len = IEEE802154_KEYID_MODE_0_SIZE;
+        break;
+
+    case IEEE802154_KEYID_MODE_1:
+        len = IEEE802154_KEYID_MODE_1_SIZE;
+        break;
+
+    case IEEE802154_KEYID_MODE_2:
+        len = IEEE802154_KEYID_MODE_2_SIZE;
+        break;
+
+    case IEEE802154_KEYID_MODE_3:
+        len = IEEE802154_KEYID_MODE_3_SIZE;
+        break;
+    }
+
+    return len;
+}
+
+static bool writeIeee802154EnhancedAck(RAIL_Handle_t aRailHandle, const uint8_t *aIeData, uint8_t aIeLength)
+{
+    // This table is derived from 802.15.4-2015 Section 7.2.1.5 PAN ID
+    // Compression field and Table 7-2 for both 2003/2006 and 2015
+    // frame versions.  It is indexed by 6 bits of the MacFCF:
+    //   SrcAdrMode FrameVer<msbit> DstAdrMode PanIdCompression
+    // and each address' length is encoded in a nibble:
+    //    15:12  11:8     7:4     3:0
+    //   SrcAdr  SrcPan  DstAdr  DstPan
+    // Illegal combinations are indicated by 0xFFFFU.
+
+#define ADDRSIZE_DST_PAN_SHIFT 0
+#define ADDRSIZE_DST_PAN_MASK (0x0FU << ADDRSIZE_DST_PAN_SHIFT)
+#define ADDRSIZE_DST_ADR_SHIFT 4
+#define ADDRSIZE_DST_ADR_MASK (0x0FU << ADDRSIZE_DST_ADR_SHIFT)
+#define ADDRSIZE_SRC_PAN_SHIFT 8
+#define ADDRSIZE_SRC_PAN_MASK (0x0FU << ADDRSIZE_SRC_PAN_SHIFT)
+#define ADDRSIZE_SRC_ADR_SHIFT 12
+#define ADDRSIZE_SRC_ADR_MASK (0x0FU << ADDRSIZE_SRC_ADR_SHIFT)
+
+    static const uint16_t ieee802154Table7p2[64] = {
+        0x0000U, 0x0000U, 0xFFFFU, 0xFFFFU, 0x0022U, 0x0022U, 0x0082U, 0x0082U, 0x0000U, 0x0002U, 0xFFFFU,
+        0xFFFFU, 0x0022U, 0x0020U, 0x0082U, 0x0080U, 0xFFFFU, 0xFFFFU, 0xFFFFU, 0xFFFFU, 0xFFFFU, 0xFFFFU,
+        0xFFFFU, 0xFFFFU, 0xFFFFU, 0xFFFFU, 0xFFFFU, 0xFFFFU, 0xFFFFU, 0xFFFFU, 0xFFFFU, 0xFFFFU, 0x2200U,
+        0x2200U, 0xFFFFU, 0xFFFFU, 0x2222U, 0x2022U, 0x2282U, 0x2082U, 0x2200U, 0x2000U, 0xFFFFU, 0xFFFFU,
+        0x2222U, 0x2022U, 0x2282U, 0x2082U, 0x8200U, 0x8200U, 0xFFFFU, 0xFFFFU, 0x8222U, 0x8022U, 0x8282U,
+        0x8082U, 0x8200U, 0x8000U, 0xFFFFU, 0xFFFFU, 0x8222U, 0x8022U, 0x8082U, 0x8080U,
+    };
+
+    // For an Enhanced ACK, we need to generate that ourselves;
+    // RAIL will generate an Immediate ACK for us, though we can
+    // tell it to go out with its FramePending bit set.
+    // An 802.15.4 packet from RAIL should look like:
+    // 1/2 |   1/2  | 0/1  |  0/2   | 0/2/8  |  0/2   | 0/2/8  |   14
+    // PHR | MacFCF | Seq# | DstPan | DstAdr | SrcPan | SrcAdr | SecHdr
+
+    // With RAIL_IEEE802154_EnableEarlyFramePending(), RAIL_EVENT_IEEE802154_DATA_REQUEST_COMMAND
+    // is triggered after receiving through the SrcAdr field of the packet,
+    // not the SecHdr which hasn't been received yet.
+
+#define EARLY_FRAME_PENDING_EXPECTED_BYTES (2U + 2U + 1U + 2U + 8U + 2U + 8U)
+#define MAX_SECURED_EXPECTED_RECEIVED_BYTES (EARLY_FRAME_PENDING_EXPECTED_BYTES + 14U)
+#define FINAL_PACKET_LENGTH_WITH_IE (MAX_SECURED_EXPECTED_RECEIVED_BYTES + aIeLength)
+
+    RAIL_RxPacketInfo_t packetInfo;
+    uint8_t             pkt[FINAL_PACKET_LENGTH_WITH_IE];
+
+    // TODO, in the original prototype for this code, this check was made to
+    // determine the PHY header length.
+    // When we add Sub-Ghz support, and we call RAIL_IEEE802154_ConfigGOptions,
+    // we should check for a 2-byte PHR.
+    // #if     ("mac has channel pages" && IEEE802154_GB868_SUPPORTED)
+    //     #define PHRLen ((emRadioChannelPageInUse == 0) ? 1 : 2)
+    // #else
+    //     #define PHRLen 1
+    uint8_t PHRLen = 1;
+
+    uint8_t pktOffset = PHRLen; // No need to parse the PHR byte(s)
+    RAIL_GetRxIncomingPacketInfo(gRailHandle, &packetInfo);
+
+    // Check if packetinfo.packetBytes includes the info we want,
+    // and if not, spin-wait calling RAIL_GetRxIncomingPacketInfo()
+    // until it has arrived
+    uint32_t startMs = otPlatAlarmMilliGetNow();
+    while (packetInfo.packetBytes < MAX_SECURED_EXPECTED_RECEIVED_BYTES)
+    {
+        RAIL_GetRxIncomingPacketInfo(gRailHandle, &packetInfo);
+        if (otPlatAlarmMilliGetNow() - startMs > 100u)
+        {
+            // More than 100 ms. has elapsed.
+            break;
+        }
+    }
+
+    if (packetInfo.packetBytes < (pktOffset + 2U))
+    {
+        return false;
+    }
+
+    // Only extract what we care about
+    if (packetInfo.packetBytes > MAX_SECURED_EXPECTED_RECEIVED_BYTES)
+    {
+        packetInfo.packetBytes = MAX_SECURED_EXPECTED_RECEIVED_BYTES;
+        if (packetInfo.firstPortionBytes >= MAX_SECURED_EXPECTED_RECEIVED_BYTES)
+        {
+            packetInfo.firstPortionBytes = MAX_SECURED_EXPECTED_RECEIVED_BYTES;
+            packetInfo.lastPortionData   = NULL;
+        }
+    }
+
+    RAIL_CopyRxPacket(pkt, &packetInfo);
+    uint16_t macFcf = pkt[pktOffset++];
+
+    if ((macFcf & IEEE802154_FRAME_TYPE_MASK) == IEEE802154_FRAME_TYPE_MULTIPURPOSE)
+    {
+        // Multipurpose frames have an arcane FCF structure
+        if ((macFcf & IEEE802154_MP_FRAME_FLAG_LONG_FCF) != 0U)
+        {
+            macFcf |= (pkt[pktOffset++] << 8);
+        }
+
+        // Map Multipurpose FCF to a 'normal' Version FCF as best we can.
+        macFcf =
+            (IEEE802154_FRAME_TYPE_MULTIPURPOSE |
+             ((macFcf & (IEEE802154_MP_FRAME_FLAG_SECURITY_ENABLED | IEEE802154_MP_FRAME_FLAG_IE_LIST_PRESENT)) >> 6) |
+             ((macFcf & IEEE802154_MP_FRAME_FLAG_FRAME_PENDING) >> 7) |
+             ((macFcf & IEEE802154_MP_FRAME_FLAG_ACK_REQUIRED) >> 9) |
+             ((macFcf & (IEEE802154_MP_FRAME_FLAG_PANID_PRESENT | IEEE802154_MP_FRAME_FLAG_SEQ_SUPPRESSION)) >> 2) |
+             ((macFcf & IEEE802154_MP_FRAME_DESTINATION_MODE_MASK) << 6) | IEEE802154_MP_FRAME_VERSION_2015 |
+             ((macFcf & IEEE802154_MP_FRAME_SOURCE_MODE_MASK) << 8));
+
+        // MultiPurpose's PANID_PRESENT is not equivalent to 2012/5's
+        // PANID_COMPRESSION so we map it best we can by flipping it
+        // in the following address-combination situations:
+        uint16_t addrCombo = (macFcf & (IEEE802154_FRAME_SOURCE_MODE_MASK | IEEE802154_FRAME_DESTINATION_MODE_MASK));
+        if ((addrCombo == (IEEE802154_FRAME_SOURCE_MODE_NONE | IEEE802154_FRAME_DESTINATION_MODE_NONE)) ||
+            (addrCombo == (IEEE802154_FRAME_SOURCE_MODE_SHORT | IEEE802154_FRAME_DESTINATION_MODE_SHORT)) ||
+            (addrCombo == (IEEE802154_FRAME_SOURCE_MODE_SHORT | IEEE802154_FRAME_DESTINATION_MODE_LONG)) ||
+            (addrCombo == (IEEE802154_FRAME_SOURCE_MODE_LONG | IEEE802154_FRAME_DESTINATION_MODE_SHORT)))
+        {
+            // 802.15.4-2015 PANID_COMPRESSION = MP PANID_PRESENT
+        }
+        else
+        {
+            // 802.15.4-2015 PANID_COMPRESSION = !MP PANID_PRESENT
+            macFcf ^= IEEE802154_FRAME_FLAG_PANID_COMPRESSION; // Flip it
+        }
+    }
+    else
+    {
+        macFcf |= (pkt[pktOffset++] << 8);
+    }
+
+    bool enhAck = ((macFcf & IEEE802154_FRAME_VERSION_MASK) == IEEE802154_FRAME_VERSION_2015);
+    if (!enhAck)
+    {
+        return false;
+    }
+
+    // Compress MAC FCF to index into 64-entry address-length table:
+    // SrcAdrMode FrameVer<msbit> DstAdrMode PanIdCompression
+    //
+    // Note: Use IEEE802154_FRAME_VERSION_2012 rather than _MASK so the
+    // low-order bit of the version field isn't used in deriving the index.
+    uint16_t index = (((macFcf & (IEEE802154_FRAME_SOURCE_MODE_MASK | IEEE802154_FRAME_VERSION_2012)) >> 10) |
+                      ((macFcf & IEEE802154_FRAME_DESTINATION_MODE_MASK) >> 9) |
+                      ((macFcf & IEEE802154_FRAME_FLAG_PANID_COMPRESSION) >> 6));
+
+    uint16_t addrSizes = ieee802154Table7p2[index];
+    // Illegal combinations mean illegal packets which we ignore
+    if (addrSizes == 0xFFFFU)
+    {
+        return false;
+    }
+
+    uint8_t seqNo =
+        ((enhAck && ((macFcf & IEEE802154_FRAME_FLAG_SEQ_SUPPRESSION) != 0U)) ? 0U : pkt[pktOffset++]); // Seq#
+
+    // Start writing the enhanced ACK -- we need to construct it since RAIL cannot.
+
+    // First extract addresses from incoming packet since we may
+    // need to reflect them in a different order in the outgoing ACK.
+    // Use byte[0] to hold each one's length.
+    uint8_t dstPan[3] = {
+        0,
+    }; // Initialized only to eliminate false gcc warning
+    dstPan[0] = ((addrSizes & ADDRSIZE_DST_PAN_MASK) >> ADDRSIZE_DST_PAN_SHIFT);
+    if ((dstPan[0] + pktOffset) > packetInfo.packetBytes)
+    {
+        return false;
+    }
+
+    if (dstPan[0] > 0U)
+    {
+        dstPan[1] = pkt[pktOffset++];
+        dstPan[2] = pkt[pktOffset++];
+    }
+
+    uint8_t dstAdr[9];
+    dstAdr[0] = ((addrSizes & ADDRSIZE_DST_ADR_MASK) >> ADDRSIZE_DST_ADR_SHIFT);
+    if ((dstAdr[0] + pktOffset) > packetInfo.packetBytes)
+    {
+        return false;
+    }
+
+    for (uint8_t i = 1U; i <= dstAdr[0]; i++)
+    {
+        dstAdr[i] = pkt[pktOffset++];
+    }
+
+    uint8_t srcPan[3];
+    srcPan[0] = ((addrSizes & ADDRSIZE_SRC_PAN_MASK) >> ADDRSIZE_SRC_PAN_SHIFT);
+    if ((srcPan[0] + pktOffset) > packetInfo.packetBytes)
+    {
+        return false;
+    }
+
+    if (srcPan[0] > 0U)
+    {
+        srcPan[1] = pkt[pktOffset++];
+        srcPan[2] = pkt[pktOffset++];
+    }
+
+    uint8_t srcAdr[9];
+    srcAdr[0] = ((addrSizes & ADDRSIZE_SRC_ADR_MASK) >> ADDRSIZE_SRC_ADR_SHIFT);
+    if ((srcAdr[0] + pktOffset) > packetInfo.packetBytes)
+    {
+        return false;
+    }
+
+    for (uint8_t i = 1U; i <= srcAdr[0]; i++)
+    {
+        srcAdr[i] = pkt[pktOffset++];
+    }
+
+    // Once done with address fields, pick the security control (if present)
+    uint8_t securityHeader[1 + 4 + 8 + 1]; // max len: control + fc + key source + key ID
+    uint8_t securityHeaderLength = 0;
+
+    if (macFcf & IEEE802154_FRAME_FLAG_SECURITY_ENABLED)
+    {
+        uint8_t securityControl = pkt[pktOffset];
+        // Then the key ID
+        uint8_t keySourceLength = getKeySourceLength(securityControl & IEEE802154_KEYID_MODE_MASK);
+        securityHeaderLength += (sizeof(uint8_t)                       /* security control */
+                                 + sizeof(uint32_t)                    /* frame counter */
+                                 + keySourceLength + sizeof(uint8_t)); /* key ID */
+        memcpy(securityHeader, pkt + pktOffset, securityHeaderLength);
+        pktOffset += securityHeaderLength;
+    }
+
+    // Reuse packet[] buffer for outgoing Enhanced ACK.
+    // Phr1 Phr2 FcfL FcfH [Seq#] [DstPan] [DstAdr] [SrcPan] [SrcAdr]
+    // Will fill in PHR later.
+    // MAC Fcf:
+    // - Frame Type = ACK
+    // - Security Enabled, as appropriate
+    // - Frame Pending = 0 or as appropriate
+    // - ACK Request = 0
+    // - PanId compression = incoming packet's
+    // - Seq# suppression = incoming packet's
+    // - IE Present = 0 in this implementation
+    // - DstAdrMode = SrcAdrMode of incoming packet's
+    // - Frame Version = 2 (154E)
+    // - SrcAdrMode = DstAdrMode of incoming packet's (for convenience)
+    uint16_t ackFcf =
+        (IEEE802154_FRAME_TYPE_ACK | (macFcf & IEEE802154_FRAME_FLAG_PANID_COMPRESSION) |
+         (macFcf & IEEE802154_FRAME_FLAG_SEQ_SUPPRESSION) | (macFcf & IEEE802154_FRAME_FLAG_SECURITY_ENABLED) |
+         IEEE802154_FRAME_VERSION_2015 | ((macFcf & IEEE802154_FRAME_SOURCE_MODE_MASK) >> 4) |
+         ((macFcf & IEEE802154_FRAME_DESTINATION_MODE_MASK) << 4));
+
+    // Do frame-pending check now
+    if (sIsSrcMatchEnabled)
+    {
+        bool setFramePending = true;
+        if (srcAdr[0] > 0U)
+        {
+            if (srcAdr[0] == 8U)
+            {
+                setFramePending = (utilsSoftSrcMatchExtFindEntry((otExtAddress *)&srcAdr[1]) >= 0);
+            }
+            else
+            {
+                uint16_t srcAdrShort = srcAdr[1] | (srcAdr[2] << 8);
+                setFramePending      = (utilsSoftSrcMatchShortFindEntry(srcAdrShort) >= 0);
+            }
+        }
+        if (setFramePending)
+        {
+            ackFcf |= IEEE802154_FRAME_FLAG_FRAME_PENDING;
+        }
+    }
+
+    pktOffset        = PHRLen;
+    pkt[pktOffset++] = (uint8_t)ackFcf;
+    pkt[pktOffset++] = (uint8_t)(ackFcf >> 8);
+
+    if ((macFcf & IEEE802154_FRAME_FLAG_SEQ_SUPPRESSION) == 0U)
+    {
+        pkt[pktOffset++] = seqNo;
+    }
+
+    // Determine outgoing ACK's address field sizes
+    index = (((ackFcf & (IEEE802154_FRAME_SOURCE_MODE_MASK | IEEE802154_FRAME_VERSION_2012)) >> 10) |
+             ((ackFcf & IEEE802154_FRAME_DESTINATION_MODE_MASK) >> 9) |
+             ((ackFcf & IEEE802154_FRAME_FLAG_PANID_COMPRESSION) >> 6));
+
+    addrSizes = ieee802154Table7p2[index];
+    if (addrSizes == 0xFFFFU)
+    {
+        // Uh-oh! Enh-ACK would be malformed?!  Something funky happened!
+        // Possibly a latency-induced issue.
+        return false;
+    }
+
+    // DstPan = SrcPan of incoming if avail otherwise DstPan of incoming
+    if ((addrSizes & ADDRSIZE_DST_PAN_MASK) != 0U)
+    {
+        if (srcPan[0] > 0U)
+        {
+            pkt[pktOffset++] = srcPan[1];
+            pkt[pktOffset++] = srcPan[2];
+        }
+        else if (dstPan[0] > 0U)
+        {
+            pkt[pktOffset++] = dstPan[1];
+            pkt[pktOffset++] = dstPan[2];
+        }
+        else
+        {
+            // Uh-oh! Outgoing packet needs a DstPanId but incoming had neither!
+            // Possibly a latency-induced issue.
+            return false;
+        }
+    }
+
+    // DstAdr = SrcAdr of incoming packet -- their sizes should match
+    if ((addrSizes & ADDRSIZE_DST_ADR_MASK) != 0U)
+    {
+        for (uint8_t i = 1U; i <= srcAdr[0]; i++)
+        {
+            pkt[pktOffset++] = srcAdr[i];
+        }
+    }
+
+    // SrcPan = DstPan of incoming if avail otherwise SrcPan of incoming
+    if ((addrSizes & ADDRSIZE_SRC_PAN_MASK) != 0U)
+    {
+        if (dstPan[0] > 0U)
+        {
+            pkt[pktOffset++] = dstPan[1];
+            pkt[pktOffset++] = dstPan[2];
+        }
+        else if (srcPan[0] > 0U)
+        {
+            pkt[pktOffset++] = srcPan[1];
+            pkt[pktOffset++] = srcPan[2];
+        }
+        else
+        {
+            // Uh-oh! Outgoing packet needs a SrcPanId but incoming had neither!
+            // Possibly a latency-induced issue.
+            return false;
+        }
+    }
+
+    // SrcAdr = DstAdr of incoming packet -- their sizes should match
+    if ((addrSizes & ADDRSIZE_SRC_ADR_MASK) != 0U)
+    {
+        for (uint8_t i = 1U; i <= dstAdr[0]; i++)
+        {
+            pkt[pktOffset++] = dstAdr[i];
+        }
+    }
+
+    if (ackFcf & IEEE802154_FRAME_FLAG_SECURITY_ENABLED)
+    {
+        memcpy(pkt + pktOffset, securityHeader, securityHeaderLength);
+        pktOffset += securityHeaderLength;
+    }
+
+    // Now add the IE data
+    memcpy(pkt + pktOffset, aIeData, aIeLength);
+    pktOffset += aIeLength;
+
+    // Fill in PHR now that we know Enh-ACK's length
+    if (PHRLen == 2U) // Not true till SubGhz implementation is in place
+    {
+        pkt[0] = (0x08U /*FCS=2byte*/ | 0x10U /*Whiten=enabled*/);
+        pkt[1] = (uint8_t)(__RBIT(pktOffset - 2U /*PHRLen*/ + 2U /*FCS*/) >> 24);
+    }
+    else
+    {
+        pkt[0] = (pktOffset - 1U /*PHRLen*/ + 2U /*FCS*/);
+    }
+
+    processSecurityForEnhancedAck(pkt);
+
+    return (RAIL_IEEE802154_WriteEnhAck(aRailHandle, pkt, pktOffset) == RAIL_STATUS_NO_ERROR);
+}
+#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+#endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+
+//------------------------------------------------------------------------------
+// RAIL callbacks
+
+static void dataRequestCommandCallback(RAIL_Handle_t aRailHandle)
+{
+    // This callback occurs after the address fields of an incoming
+    // ACK-requesting CMD or DATA frame have been received and we
+    // can do a frame pending check.  We must also figure out what
+    // kind of ACK is being requested -- Immediate or Enhanced.
+
+#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 && OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE)
+    // Check if we need to write an enhanced ACK.
+    if (writeIeee802154EnhancedAck(aRailHandle, sAckIeData, sAckIeDataLength))
+    {
+        return;
+    }
+#endif
+
+    // If not, RAIL will send an immediate ACK, but we need to do FP lookup.
+    RAIL_Status_t status = RAIL_STATUS_NO_ERROR;
+
+    bool framePendingSet = false;
+
+    if (sIsSrcMatchEnabled)
+    {
+        RAIL_IEEE802154_Address_t sourceAddress;
+
+        status = RAIL_IEEE802154_GetAddress(aRailHandle, &sourceAddress);
+        otEXPECT(status == RAIL_STATUS_NO_ERROR);
+
+        if ((sourceAddress.length == RAIL_IEEE802154_LongAddress &&
+             utilsSoftSrcMatchExtFindEntry((otExtAddress *)sourceAddress.longAddress) >= 0) ||
+            (sourceAddress.length == RAIL_IEEE802154_ShortAddress &&
+             utilsSoftSrcMatchShortFindEntry(sourceAddress.shortAddress) >= 0))
+        {
+            status = RAIL_IEEE802154_SetFramePending(aRailHandle);
+            otEXPECT(status == RAIL_STATUS_NO_ERROR);
+            framePendingSet = true;
+        }
+    }
+    else
+    {
+        status = RAIL_IEEE802154_SetFramePending(aRailHandle);
+        otEXPECT(status == RAIL_STATUS_NO_ERROR);
+        framePendingSet = true;
+    }
+
+    if (framePendingSet)
+    {
+        // Store whether frame pending was set in the outgoing ACK in a reserved
+        // bit of the MAC header.
+        RAIL_RxPacketInfo_t packetInfo;
+        RAIL_GetRxIncomingPacketInfo(gRailHandle, &packetInfo);
+
+        // skip length byte
+        otEXPECT(packetInfo.firstPortionBytes > 0);
+        packetInfo.firstPortionData++;
+        packetInfo.firstPortionBytes--;
+        packetInfo.packetBytes--;
+
+        uint8_t *macFcfPointer = ((packetInfo.firstPortionBytes == 0) ? (uint8_t *)packetInfo.lastPortionData
+                                                                      : (uint8_t *)packetInfo.firstPortionData);
+        *macFcfPointer |= IEEE802154_FRAME_PENDING_SET_IN_OUTGOING_ACK;
+    }
+
+exit:
+    if (status == RAIL_STATUS_INVALID_STATE)
+    {
+        otLogWarnPlat("Too late to modify outgoing FP");
+    }
+    else
+    {
+        assert(status == RAIL_STATUS_NO_ERROR);
+    }
+}
+
+static void packetReceivedCallback(RAIL_RxPacketHandle_t packetHandle)
+{
+    RAIL_RxPacketInfo_t    packetInfo;
+    RAIL_RxPacketDetails_t packetDetails;
+    uint16_t               length;
+    bool                   framePendingInAck = false;
+    bool                   rxCorrupted       = false;
+
+    packetHandle = RAIL_GetRxPacketInfo(gRailHandle, packetHandle, &packetInfo);
+    otEXPECT_ACTION(
+        (packetHandle != RAIL_RX_PACKET_HANDLE_INVALID && packetInfo.packetStatus == RAIL_RX_PACKET_READY_SUCCESS),
+        rxCorrupted = true);
+
+    otEXPECT_ACTION(validatePacketDetails(packetHandle, &packetDetails, &packetInfo, &length), rxCorrupted = true);
+
+    // skip length byte
+    otEXPECT_ACTION(packetInfo.firstPortionBytes > 0, rxCorrupted = true);
+    packetInfo.firstPortionData++;
+    packetInfo.firstPortionBytes--;
+    packetInfo.packetBytes--;
+
+    uint8_t macFcf =
+        ((packetInfo.firstPortionBytes == 0) ? packetInfo.lastPortionData[0] : packetInfo.firstPortionData[0]);
+
+    if (packetDetails.isAck)
+    {
+        otEXPECT_ACTION(
+            (length == IEEE802154_ACK_LENGTH && (macFcf & IEEE802154_FRAME_TYPE_MASK) == IEEE802154_FRAME_TYPE_ACK),
+            rxCorrupted = true);
+
+        // read packet
+        RAIL_CopyRxPacket(sReceiveAckFrame.mPsdu, &packetInfo);
+        sReceiveAckFrame.mLength = length;
+
+        // Releasing the ACK frames here, ensures that the main thread (processNextRxPacket)
+        // is not wasting cycles, releasing the ACK frames from the Rx FIFO queue.
+        RAIL_ReleaseRxPacket(gRailHandle, packetHandle);
+
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ENDED, (uint32_t)isReceivingFrame());
+
+        if (txWaitingForAck() &&
+            (sReceiveAckFrame.mPsdu[IEEE802154_DSN_OFFSET] == sTransmitFrame.mPsdu[IEEE802154_DSN_OFFSET]))
+        {
+            otEXPECT_ACTION(validatePacketTimestamp(&packetDetails, length), rxCorrupted = true);
+            updateRxFrameDetails(&packetDetails, false);
+
+            // Processing the ACK frame in ISR context avoids the Tx state to be messed up,
+            // in case the Rx FIFO queue gets wiped out in a DMP situation.
+            sTransmitBusy  = false;
+            sTransmitError = OT_ERROR_NONE;
+            setInternalFlag(FLAG_WAITING_FOR_ACK, false);
+
+            framePendingInAck = ((macFcf & IEEE802154_FRAME_FLAG_FRAME_PENDING) != 0);
+            (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_ACK_RECEIVED, (uint32_t)framePendingInAck);
+
+            if (txIsDataRequest() && framePendingInAck)
+            {
+                emPendingData = true;
+            }
+        }
+        // Yield the radio upon receiving an ACK as long as it is not related to
+        // a data request.
+        if (!txIsDataRequest())
+        {
+            RAIL_YieldRadio(gRailHandle);
+        }
+    }
+    else
+    {
+        otEXPECT_ACTION(sPromiscuous || (length != IEEE802154_ACK_LENGTH), rxCorrupted = true);
+
+        if (macFcf & IEEE802154_FRAME_FLAG_ACK_REQUIRED)
+        {
+            (void)handlePhyStackEvent((RAIL_IsRxAutoAckPaused(gRailHandle)
+                                           ? SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ACK_BLOCKED
+                                           : SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ACKING),
+                                      (uint32_t)isReceivingFrame());
+            setInternalFlag(FLAG_ONGOING_TX_ACK, true);
+        }
+        else
+        {
+            (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ENDED, (uint32_t)isReceivingFrame());
+            // We received a frame that does not require an ACK as result of a data
+            // poll: we yield the radio here.
+            if (emPendingData)
+            {
+                RAIL_YieldRadio(gRailHandle);
+                emPendingData = false;
+            }
+        }
+    }
+exit:
+    if (rxCorrupted)
+    {
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_CORRUPTED, (uint32_t)isReceivingFrame());
+    }
+}
+
+static void packetSentCallback(bool isAck)
+{
+    if (isAck)
+    {
+        // We successfully sent out an ACK.
+        setInternalFlag(FLAG_ONGOING_TX_ACK, false);
+        // We acked the packet we received after a poll: we can yield now.
+        if (emPendingData)
+        {
+            RAIL_YieldRadio(gRailHandle);
+            emPendingData = false;
+        }
+    }
+    else if (getInternalFlag(FLAG_ONGOING_TX_DATA))
+    {
+        setInternalFlag(FLAG_ONGOING_TX_DATA, false);
+
+        if (txWaitingForAck())
+        {
+            setInternalFlag(FLAG_WAITING_FOR_ACK, true);
+            (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_ACK_WAITING, 0U);
+        }
+        else
+        {
+            (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_ENDED, 0U);
+            RAIL_YieldRadio(gRailHandle);
+            sTransmitError = OT_ERROR_NONE;
+            sTransmitBusy  = false;
+        }
+#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
+        sRailDebugCounters.mRailEventPacketSent++;
+#endif
+    }
+}
+
+static void txFailedCallback(bool isAck, uint8_t status)
+{
+    if (isAck)
+    {
+        setInternalFlag(FLAG_ONGOING_TX_ACK, false);
+    }
+    else if (getInternalFlag(FLAG_ONGOING_TX_DATA))
+    {
+        if (status == TX_COMPLETE_RESULT_CCA_FAIL)
+        {
+            sTransmitError = OT_ERROR_CHANNEL_ACCESS_FAILURE;
+            setInternalFlag(FLAG_CURRENT_TX_USE_CSMA, false);
+#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
+            sRailDebugCounters.mRailEventChannelBusy++;
+#endif
+        }
+        else
+        {
+            sTransmitError = OT_ERROR_ABORT;
+#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
+            sRailDebugCounters.mRailEventTxAbort++;
+#endif
+        }
+        setInternalFlag(FLAG_ONGOING_TX_DATA, false);
+        RAIL_YieldRadio(gRailHandle);
+        sTransmitBusy = false;
+    }
+}
+
+static void ackTimeoutCallback(void)
+{
+    assert(txWaitingForAck());
+    assert(getInternalFlag(FLAG_WAITING_FOR_ACK));
+
+    sTransmitError = OT_ERROR_NO_ACK;
+    sTransmitBusy  = false;
+#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
+    sRailDebugCounters.mRailEventNoAck++;
+#endif
+
+#ifdef SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT
+    // If antenna diversity is enabled toggle the selected antenna.
+    sl_rail_util_ant_div_toggle_antenna();
+#endif // SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT
+    // TO DO: Check if we have an OT function that
+    // provides the number of mac retry attempts left
+    (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_ACK_TIMEDOUT, 0);
+
+    setInternalFlag(FLAG_WAITING_FOR_ACK, false);
+    RAIL_YieldRadio(gRailHandle);
+    emPendingData = false;
+}
+
+static void schedulerEventCallback(RAIL_Handle_t aRailHandle)
+{
+    RAIL_SchedulerStatus_t status = RAIL_GetSchedulerStatus(aRailHandle);
+    assert(status != RAIL_SCHEDULER_STATUS_INTERNAL_ERROR);
+
+    if (status == RAIL_SCHEDULER_STATUS_CCA_CSMA_TX_FAIL || status == RAIL_SCHEDULER_STATUS_SINGLE_TX_FAIL ||
+        status == RAIL_SCHEDULER_STATUS_SCHEDULED_TX_FAIL ||
+        (status == RAIL_SCHEDULER_STATUS_SCHEDULE_FAIL && sTransmitBusy) ||
+        (status == RAIL_SCHEDULER_STATUS_EVENT_INTERRUPTED && sTransmitBusy))
+    {
+        if (getInternalFlag(FLAG_ONGOING_TX_ACK))
+        {
+            (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ACK_ABORTED, (uint32_t)isReceivingFrame());
+            txFailedCallback(true, 0xFF);
+        }
+        // We were in the process of TXing a data frame, treat it as a CCA_FAIL.
+        if (getInternalFlag(FLAG_ONGOING_TX_DATA))
+        {
+            (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_BLOCKED, (uint32_t)txWaitingForAck());
+            txFailedCallback(false, TX_COMPLETE_RESULT_CCA_FAIL);
+        }
+
+#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
+        sRailDebugCounters.mRailEventSchedulerStatusError++;
+#endif
+    }
+    else if (status == RAIL_SCHEDULER_STATUS_AVERAGE_RSSI_FAIL ||
+             (status == RAIL_SCHEDULER_STATUS_SCHEDULE_FAIL && sEnergyScanStatus == ENERGY_SCAN_STATUS_IN_PROGRESS))
+    {
+        energyScanComplete(OT_RADIO_RSSI_INVALID);
+    }
+#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
+    else if (sTransmitBusy)
+    {
+        sRailDebugCounters.mRailEventsSchedulerStatusLastStatus = status;
+        sRailDebugCounters.mRailEventsSchedulerStatusTransmitBusy++;
+    }
+#endif
+}
+
+static void configUnscheduledCallback(void)
+{
+    // We are waiting for an ACK: we will never get the ACK we were waiting for.
+    // We want to call ackTimeoutCallback() only if the PACKET_SENT event
+    // already fired (which would clear the FLAG_ONGOING_TX_DATA flag).
+    if (getInternalFlag(FLAG_WAITING_FOR_ACK))
+    {
+        ackTimeoutCallback();
+    }
+
+    // We are about to send an ACK, which it won't happen.
+    if (getInternalFlag(FLAG_ONGOING_TX_ACK))
+    {
+        txFailedCallback(true, 0xFF);
+    }
+}
+
+static void RAILCb_Generic(RAIL_Handle_t aRailHandle, RAIL_Events_t aEvents)
+{
+#ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT
+    if (aEvents & (RAIL_EVENT_RX_SYNC1_DETECT | RAIL_EVENT_RX_SYNC2_DETECT))
+    {
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_STARTED, (uint32_t)isReceivingFrame());
+    }
+#endif // SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT
+
+    if ((aEvents & RAIL_EVENT_IEEE802154_DATA_REQUEST_COMMAND)
+#ifdef SL_CATALOG_RAIL_UTIL_COEX_PRESENT
+        && !RAIL_IsRxAutoAckPaused(aRailHandle)
+#endif // SL_CATALOG_RAIL_UTIL_COEX_PRESENT
+    )
+    {
+        dataRequestCommandCallback(aRailHandle);
+    }
+
+#ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT
+    if (aEvents & RAIL_EVENT_RX_FILTER_PASSED)
+    {
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ACCEPTED, (uint32_t)isReceivingFrame());
+    }
+#endif // SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT
+
+    if (aEvents & RAIL_EVENT_TX_PACKET_SENT)
+    {
+        packetSentCallback(false);
+    }
+    else if (aEvents & RAIL_EVENT_TX_CHANNEL_BUSY)
+    {
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_BLOCKED, (uint32_t)txWaitingForAck());
+        txFailedCallback(false, TX_COMPLETE_RESULT_CCA_FAIL);
+    }
+    else if (aEvents & RAIL_EVENT_TX_BLOCKED)
+    {
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_BLOCKED, (uint32_t)txWaitingForAck());
+        txFailedCallback(false, TX_COMPLETE_RESULT_OTHER_FAIL);
+    }
+    else if (aEvents & (RAIL_EVENT_TX_UNDERFLOW | RAIL_EVENT_TX_ABORTED))
+    {
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_ABORTED, (uint32_t)txWaitingForAck());
+        txFailedCallback(false, TX_COMPLETE_RESULT_OTHER_FAIL);
+    }
+    else
+    {
+        // Pre-completion aEvents are processed in their logical order:
+#ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT
+        if (aEvents & RAIL_EVENT_TX_START_CCA)
+        {
+            // We are starting RXWARM for a CCA check
+            (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_CCA_SOON, 0U);
+        }
+        if (aEvents & RAIL_EVENT_TX_CCA_RETRY)
+        {
+            // We failed a CCA check and need to retry
+            (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_CCA_BUSY, 0U);
+        }
+        if (aEvents & RAIL_EVENT_TX_CHANNEL_CLEAR)
+        {
+            // We're going on-air
+            (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_STARTED, 0U);
+        }
+#endif // SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT
+    }
+
+    if (aEvents & RAIL_EVENT_RX_PACKET_RECEIVED)
+    {
+        packetReceivedCallback(RAIL_HoldRxPacket(aRailHandle));
+#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
+        sRailDebugCounters.mRailEventPacketReceived++;
+#endif
+    }
+
+#ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT
+    if (aEvents & RAIL_EVENT_RX_FRAME_ERROR)
+    {
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_CORRUPTED, (uint32_t)isReceivingFrame());
+    }
+    // The following 3 events cause us to not receive a packet
+    if (aEvents & (RAIL_EVENT_RX_PACKET_ABORTED | RAIL_EVENT_RX_ADDRESS_FILTERED | RAIL_EVENT_RX_FIFO_OVERFLOW))
+    {
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_FILTERED, (uint32_t)isReceivingFrame());
+    }
+#endif // SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT
+
+    if (aEvents & RAIL_EVENT_TXACK_PACKET_SENT)
+    {
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ACK_SENT, (uint32_t)isReceivingFrame());
+        packetSentCallback(true);
+    }
+    if (aEvents & (RAIL_EVENT_TXACK_ABORTED | RAIL_EVENT_TXACK_UNDERFLOW))
+    {
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ACK_ABORTED, (uint32_t)isReceivingFrame());
+        txFailedCallback(true, 0xFF);
+    }
+    if (aEvents & RAIL_EVENT_TXACK_BLOCKED)
+    {
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_ACK_BLOCKED, (uint32_t)isReceivingFrame());
+        txFailedCallback(true, 0xFF);
+    }
+    // Deal with ACK timeout after possible RX completion in case RAIL
+    // notifies us of the ACK and the timeout simultaneously -- we want
+    // the ACK to win over the timeout.
+    if (aEvents & RAIL_EVENT_RX_ACK_TIMEOUT)
+    {
+        if (getInternalFlag(FLAG_WAITING_FOR_ACK))
+        {
+            ackTimeoutCallback();
+        }
+    }
+
+    if (aEvents & RAIL_EVENT_CONFIG_UNSCHEDULED)
+    {
+        (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_RX_IDLED, 0U);
+        configUnscheduledCallback();
+#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
+        sRailDebugCounters.mRailEventConfigUnScheduled++;
+#endif
+    }
+
+    if (aEvents & RAIL_EVENT_CONFIG_SCHEDULED)
+    {
+#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
+        sRailDebugCounters.mRailEventConfigScheduled++;
+#endif
+    }
+
+    if (aEvents & RAIL_EVENT_SCHEDULER_STATUS)
+    {
+        schedulerEventCallback(aRailHandle);
+    }
+
+    if (aEvents & RAIL_EVENT_CAL_NEEDED)
+    {
+        RAIL_Status_t status;
+
+        status = RAIL_Calibrate(aRailHandle, NULL, RAIL_CAL_ALL_PENDING);
+        // TODO: Non-RTOS DMP case fails
+#if (!defined(SL_CATALOG_BLUETOOTH_PRESENT) || defined(SL_CATALOG_KERNEL_PRESENT))
+        assert(status == RAIL_STATUS_NO_ERROR);
+#endif
+
+#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
+        sRailDebugCounters.mRailEventCalNeeded++;
+#endif
+    }
+
+    if (aEvents & RAIL_EVENT_RSSI_AVERAGE_DONE)
+    {
+        const int16_t energyScanResultQuarterDbm = RAIL_GetAverageRssi(aRailHandle);
+        RAIL_YieldRadio(aRailHandle);
+
+        energyScanComplete(energyScanResultQuarterDbm == RAIL_RSSI_INVALID
+                               ? OT_RADIO_RSSI_INVALID
+                               : (energyScanResultQuarterDbm / QUARTER_DBM_IN_DBM));
+#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
+        sRailDebugCounters.mRailPlatRadioEnergyScanDoneCbCount++;
+#endif
+    }
+
+    otSysEventSignalPending();
+}
+
+//------------------------------------------------------------------------------
+// Main thread packet handling
+
+static bool validatePacketDetails(RAIL_RxPacketHandle_t   packetHandle,
+                                  RAIL_RxPacketDetails_t *pPacketDetails,
+                                  RAIL_RxPacketInfo_t *   pPacketInfo,
+                                  uint16_t *              packetLength)
+{
+    bool pktValid = true;
+
+    otEXPECT_ACTION((RAIL_GetRxPacketDetailsAlt(gRailHandle, packetHandle, pPacketDetails) == RAIL_STATUS_NO_ERROR),
+                    pktValid = false);
+
+    // RAIL's packetBytes includes the 1-byte PHY header but not the 2-byte CRC
+    // We want *packetLength to match the PHY header length so we add 2 for CRC
+    // and subtract 1 for PHY header.
+    *packetLength = pPacketInfo->packetBytes + 1;
+
+    // check the length in recv packet info structure; RAIL should take care of this.
+    otEXPECT_ACTION(*packetLength == pPacketInfo->firstPortionData[0], pktValid = false);
+
+    // check the length validity of recv packet; RAIL should take care of this.
+    otEXPECT_ACTION((*packetLength >= IEEE802154_MIN_LENGTH && *packetLength <= IEEE802154_MAX_LENGTH),
+                    pktValid = false);
+exit:
+    return pktValid;
+}
+
+static bool validatePacketTimestamp(RAIL_RxPacketDetails_t *pPacketDetails, uint16_t packetLength)
+{
+    bool rxTimestampValid = true;
+
+    // Get the timestamp when the SFD was received
+    otEXPECT_ACTION(pPacketDetails->timeReceived.timePosition != RAIL_PACKET_TIME_INVALID, rxTimestampValid = false);
+
+    // + 1 for the 1-byte PHY header
+    pPacketDetails->timeReceived.totalPacketBytes = packetLength + 1;
+
+    otEXPECT_ACTION((RAIL_GetRxTimeSyncWordEndAlt(gRailHandle, pPacketDetails) == RAIL_STATUS_NO_ERROR),
+                    rxTimestampValid = false);
+exit:
+    return rxTimestampValid;
+}
+
+static void updateRxFrameDetails(RAIL_RxPacketDetails_t *pPacketDetails, bool framePendingSetInOutgoingAck)
+{
+    assert(pPacketDetails != NULL);
+
+    if (pPacketDetails->isAck)
+    {
+        sReceiveAckFrame.mInfo.mRxInfo.mRssi      = pPacketDetails->rssi;
+        sReceiveAckFrame.mInfo.mRxInfo.mLqi       = pPacketDetails->lqi;
+        sReceiveAckFrame.mInfo.mRxInfo.mTimestamp = pPacketDetails->timeReceived.packetTime;
+    }
+    else
+    {
+        sReceiveFrame.mInfo.mRxInfo.mRssi      = pPacketDetails->rssi;
+        sReceiveFrame.mInfo.mRxInfo.mLqi       = pPacketDetails->lqi;
+        sReceiveFrame.mInfo.mRxInfo.mTimestamp = pPacketDetails->timeReceived.packetTime;
+        // Set this flag only when the packet is really acknowledged with frame pending set.
+        sReceiveFrame.mInfo.mRxInfo.mAckedWithFramePending = framePendingSetInOutgoingAck;
+    }
+}
+
+static void processNextRxPacket(otInstance *aInstance)
+{
+    RAIL_RxPacketHandle_t  packetHandle = RAIL_RX_PACKET_HANDLE_INVALID;
+    RAIL_RxPacketInfo_t    packetInfo;
+    RAIL_RxPacketDetails_t packetDetails;
+    RAIL_Status_t          status;
+    uint16_t               length;
+    bool                   rxProcessDone = false;
+
+    CORE_DECLARE_IRQ_STATE;
+    CORE_ENTER_ATOMIC();
+
+    packetHandle = RAIL_GetRxPacketInfo(gRailHandle, RAIL_RX_PACKET_HANDLE_OLDEST_COMPLETE, &packetInfo);
+    otEXPECT_ACTION(
+        (packetHandle != RAIL_RX_PACKET_HANDLE_INVALID && packetInfo.packetStatus == RAIL_RX_PACKET_READY_SUCCESS),
+        packetHandle = RAIL_RX_PACKET_HANDLE_INVALID);
+
+    otEXPECT(validatePacketDetails(packetHandle, &packetDetails, &packetInfo, &length));
+
+    // skip length byte
+    otEXPECT(packetInfo.firstPortionBytes > 0);
+    packetInfo.firstPortionData++;
+    packetInfo.firstPortionBytes--;
+    packetInfo.packetBytes--;
+
+    // As received ACK frames are already processed in packetReceivedCallback,
+    // we only need to read and process the non-ACK frames here.
+    otEXPECT(sPromiscuous || (!packetDetails.isAck && (length != IEEE802154_ACK_LENGTH)));
+
+    // read packet
+    RAIL_CopyRxPacket(sReceiveFrame.mPsdu, &packetInfo);
+    sReceiveFrame.mLength = length;
+
+    uint8_t *macFcfPointer = sReceiveFrame.mPsdu;
+
+    // Check the reserved bit in the MAC header to see whether the frame pending
+    // bit was set in the outgoing ACK
+    // Then, clear it.
+    bool framePendingSetInOutgoingAck = ((*macFcfPointer & IEEE802154_FRAME_PENDING_SET_IN_OUTGOING_ACK) != 0);
+    *macFcfPointer &= ~IEEE802154_FRAME_PENDING_SET_IN_OUTGOING_ACK;
+
+    status = RAIL_ReleaseRxPacket(gRailHandle, packetHandle);
+    if (status == RAIL_STATUS_NO_ERROR)
+    {
+        packetHandle = RAIL_RX_PACKET_HANDLE_INVALID;
+    }
+
+    otEXPECT(validatePacketTimestamp(&packetDetails, length));
+    updateRxFrameDetails(&packetDetails, framePendingSetInOutgoingAck);
+    rxProcessDone = true;
+
+exit:
+    if (packetHandle != RAIL_RX_PACKET_HANDLE_INVALID)
+    {
+        RAIL_ReleaseRxPacket(gRailHandle, packetHandle);
+    }
+    CORE_EXIT_ATOMIC();
+
+    // signal MAC layer
+    if (rxProcessDone)
+    {
+        sReceiveError = OT_ERROR_NONE;
+
+#if OPENTHREAD_CONFIG_DIAG_ENABLE
+        if (otPlatDiagModeGet())
+        {
+            otPlatDiagRadioReceiveDone(aInstance, &sReceiveFrame, sReceiveError);
+        }
+        else
+#endif
+        {
+            otLogInfoPlat("Received %d bytes", sReceiveFrame.mLength);
+            otPlatRadioReceiveDone(aInstance, &sReceiveFrame, sReceiveError);
+#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
+            sRailDebugCounters.mRailPlatRadioReceiveDoneCbCount++;
+#endif
+        }
+        otSysEventSignalPending();
+    }
+}
+
+static void processTxComplete(otInstance *aInstance)
+{
+    if (sState == OT_RADIO_STATE_TRANSMIT && sTransmitBusy == false)
+    {
+        if (sTransmitError != OT_ERROR_NONE)
+        {
+            otLogDebgPlat("Transmit failed ErrorCode=%d", sTransmitError);
+        }
+
+        sState = OT_RADIO_STATE_RECEIVE;
+#if OPENTHREAD_CONFIG_DIAG_ENABLE
+        if (otPlatDiagModeGet())
+        {
+            otPlatDiagRadioTransmitDone(aInstance, &sTransmitFrame, sTransmitError);
+        }
+        else
+#endif
+            if (((sTransmitFrame.mPsdu[0] & IEEE802154_FRAME_FLAG_ACK_REQUIRED) == 0) ||
+                (sTransmitError != OT_ERROR_NONE))
+        {
+            otPlatRadioTxDone(aInstance, &sTransmitFrame, NULL, sTransmitError);
+        }
+        else
+        {
+            otPlatRadioTxDone(aInstance, &sTransmitFrame, &sReceiveAckFrame, sTransmitError);
+        }
+
+#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
+        sRailDebugCounters.mRailPlatRadioTxDoneCbCount++;
+#endif
+        otSysEventSignalPending();
+    }
+}
+
+void efr32RadioProcess(otInstance *aInstance)
+{
+    (void)handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TICK, 0U);
+
+    // We should process the received packet first. Adding it at the end of this function,
+    // will delay the stack notification until the next call to efr32RadioProcess()
+    processNextRxPacket(aInstance);
+    processTxComplete(aInstance);
+
+    if (sEnergyScanMode == ENERGY_SCAN_MODE_ASYNC && sEnergyScanStatus == ENERGY_SCAN_STATUS_COMPLETED)
+    {
+        sEnergyScanStatus = ENERGY_SCAN_STATUS_IDLE;
+        otPlatRadioEnergyScanDone(aInstance, sEnergyScanResultDbm);
+        otSysEventSignalPending();
+
+#if RADIO_CONFIG_DEBUG_COUNTERS_SUPPORT
+        sRailDebugCounters.mRailEventEnergyScanCompleted++;
+#endif
+    }
+}
+
+//------------------------------------------------------------------------------
+// Antenn Diveristy, Wifi coexistence and Run time PHY select support
+
+#ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_PHY_SELECT_PRESENT
+
+otError setRadioState(otRadioState state)
+{
+    otError error = OT_ERROR_NONE;
+
+    // Defer idling the radio if we have an ongoing TX task
+    otEXPECT_ACTION((!getInternalFlag(ONGOING_TX_FLAGS)), error = OT_ERROR_FAILED);
+
+    switch (state)
+    {
+    case OT_RADIO_STATE_RECEIVE:
+        otEXPECT_ACTION(radioSetRx(sReceiveFrame.mChannel) == OT_ERROR_NONE, error = OT_ERROR_FAILED);
+        break;
+    case OT_RADIO_STATE_SLEEP:
+        radioSetIdle();
+        break;
+    default:
+        error = OT_ERROR_FAILED;
+    }
+exit:
+    return error;
+}
+
+void sl_ot_update_active_radio_config(void)
+{
+    CORE_DECLARE_IRQ_STATE;
+    CORE_ENTER_ATOMIC();
+
+    // Proceed with PHY selection only if 2.4 GHz band is used
+    otEXPECT(sBandConfig.mChannelConfig == NULL);
+
+    otRadioState currentState = sState;
+    otEXPECT(setRadioState(OT_RADIO_STATE_SLEEP) == OT_ERROR_NONE);
+    sl_rail_util_plugin_config_2p4ghz_radio(gRailHandle);
+    otEXPECT(setRadioState(currentState) == OT_ERROR_NONE);
+
+exit:
+    CORE_EXIT_ATOMIC();
+    return;
+}
+#endif // SL_CATALOG_RAIL_UTIL_IEEE802154_PHY_SELECT_PRESENT
+
+#ifdef SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT
+void efr32AntennaConfigInit(void)
+{
+    RAIL_Status_t status;
+    sl_rail_util_ant_div_init();
+    status = sl_rail_util_ant_div_update_antenna_config();
+    assert(status == RAIL_STATUS_NO_ERROR);
+}
+#endif // SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT
+
+#ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT
+
+static void changeDynamicEvents(void)
+{
+    const RAIL_Events_t eventMask =
+        RAIL_EVENTS_NONE | RAIL_EVENT_RX_SYNC1_DETECT | RAIL_EVENT_RX_SYNC2_DETECT | RAIL_EVENT_RX_FRAME_ERROR |
+        RAIL_EVENT_RX_FIFO_OVERFLOW | RAIL_EVENT_RX_ADDRESS_FILTERED | RAIL_EVENT_RX_PACKET_ABORTED |
+        RAIL_EVENT_RX_FILTER_PASSED | RAIL_EVENT_TX_CHANNEL_CLEAR | RAIL_EVENT_TX_CCA_RETRY | RAIL_EVENT_TX_START_CCA;
+    RAIL_Events_t eventValues = RAIL_EVENTS_NONE;
+
+    if (phyStackEventIsEnabled())
+    {
+        eventValues |= eventMask;
+    }
+    updateEvents(eventMask, eventValues);
+}
+#endif // SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT
+
+static void efr32PhyStackInit(void)
+{
+#ifdef SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT
+    efr32AntennaConfigInit();
+#endif // SL_CATALOG_RAIL_UTIL_ANT_DIV_PRESENT
+
+#ifdef SL_CATALOG_RAIL_UTIL_COEX_PRESENT
+    efr32CoexInit();
+#endif // SL_CATALOG_RAIL_UTIL_COEX_PRESENT
+
+#ifdef SL_CATALOG_RAIL_UTIL_IEEE802154_STACK_EVENT_PRESENT
+    changeDynamicEvents();
+#endif
+}
+
+#ifdef SL_CATALOG_RAIL_UTIL_COEX_PRESENT
+
+static void emRadioEnableAutoAck(void)
+{
+    CORE_DECLARE_IRQ_STATE;
+    CORE_ENTER_ATOMIC();
+
+    if (getInternalFlag(FLAG_RADIO_INIT_DONE))
+    {
+        if ((rhoActive >= RHO_INT_ACTIVE) // Internal always holds ACKs
+            || ((rhoActive > RHO_INACTIVE) && ((sl_rail_util_coex_get_options() & SL_RAIL_UTIL_COEX_OPT_ACK_HOLDOFF) !=
+                                               SL_RAIL_UTIL_COEX_OPT_DISABLED)))
+        {
+            RAIL_PauseRxAutoAck(gRailHandle, true);
+        }
+        else
+        {
+            RAIL_PauseRxAutoAck(gRailHandle, false);
+        }
+    }
+    CORE_EXIT_ATOMIC();
+}
+
+static void emRadioEnablePta(bool enable)
+{
+    halInternalInitPta();
+
+    // When PTA is enabled, we want to negate PTA_REQ as soon as an incoming
+    // frame is aborted, e.g. due to filtering.  To do that we must turn off
+    // the TRACKABFRAME feature that's normally on to benefit sniffing on PTI.
+    assert(RAIL_ConfigRxOptions(gRailHandle, RAIL_RX_OPTION_TRACK_ABORTED_FRAMES,
+                                (enable ? RAIL_RX_OPTIONS_NONE : RAIL_RX_OPTION_TRACK_ABORTED_FRAMES)) ==
+           RAIL_STATUS_NO_ERROR);
+}
+
+static void efr32CoexInit(void)
+{
+    sl_rail_util_coex_options_t coexOptions = sl_rail_util_coex_get_options();
+
+#if SL_OPENTHREAD_COEX_MAC_HOLDOFF_ENABLE
+    coexOptions |= SL_RAIL_UTIL_COEX_OPT_MAC_HOLDOFF;
+#endif // SL_OPENTHREAD_COEX_MAC_HOLDOFF_ENABLE
+
+    sl_rail_util_coex_set_options(coexOptions);
+
+    emRadioEnableAutoAck(); // Might suspend AutoACK if RHO already in effect
+    emRadioEnablePta(sl_rail_util_coex_is_enabled());
+}
+
+// Managing radio transmission
+static void onPtaGrantTx(sl_rail_util_coex_req_t ptaStatus)
+{
+    // Only pay attention to first PTA Grant callback, ignore any further ones
+    if (ptaGntEventReported)
+    {
+        return;
+    }
+    ptaGntEventReported = true;
+
+    assert(ptaStatus == SL_RAIL_UTIL_COEX_REQCB_GRANTED);
+    // PTA is telling us we've gotten GRANT and should send ASAP *without* CSMA
+    setInternalFlag(FLAG_CURRENT_TX_USE_CSMA, false);
+    txCurrentPacket();
+}
+
+static void tryTxCurrentPacket(void)
+{
+    assert(getInternalFlag(FLAG_ONGOING_TX_DATA));
+
+    ptaGntEventReported = false;
+    sl_rail_util_ieee802154_stack_event_t ptaStatus =
+        handlePhyStackEvent(SL_RAIL_UTIL_IEEE802154_STACK_EVENT_TX_PENDED_MAC, (uint32_t)&onPtaGrantTx);
+    if (ptaStatus == SL_RAIL_UTIL_IEEE802154_STACK_STATUS_SUCCESS)
+    {
+        // Normal case where PTA allows us to start the (CSMA) transmit below
+        txCurrentPacket();
+    }
+    else if (ptaStatus == SL_RAIL_UTIL_IEEE802154_STACK_STATUS_CB_PENDING)
+    {
+        // onPtaGrantTx() callback will take over (and might already have)
+    }
+    else if (ptaStatus == SL_RAIL_UTIL_IEEE802154_STACK_STATUS_HOLDOFF)
+    {
+        txFailedCallback(false, TX_COMPLETE_RESULT_OTHER_FAIL);
+    }
+}
+
+// Managing CCA Threshold
+static void setCcaThreshold(void)
+{
+    if (sCcaThresholdDbm == CCA_THRESHOLD_UNINIT)
+    {
+        sCcaThresholdDbm = CCA_THRESHOLD_DEFAULT;
+    }
+    CORE_DECLARE_IRQ_STATE;
+    CORE_ENTER_ATOMIC();
+    int8_t thresholddBm = sCcaThresholdDbm;
+
+    if (getInternalFlag(FLAG_RADIO_INIT_DONE))
+    {
+        if (rhoActive > RHO_INACTIVE)
+        {
+            thresholddBm = RAIL_RSSI_INVALID_DBM;
+        }
+        assert(RAIL_SetCcaThreshold(gRailHandle, thresholddBm) == RAIL_STATUS_NO_ERROR);
+    }
+    CORE_EXIT_ATOMIC();
+}
+
+static void emRadioHoldOffInternalIsr(uint8_t active)
+{
+    if (active != rhoActive)
+    {
+        rhoActive = active; // Update rhoActive early
+        if (getInternalFlag(FLAG_RADIO_INIT_DONE))
+        {
+            setCcaThreshold();
+            emRadioEnableAutoAck();
+        }
+    }
+}
+
+// External API used by Coex Component
+void emRadioHoldOffIsr(bool active)
+{
+    emRadioHoldOffInternalIsr((uint8_t)active | (rhoActive & ~RHO_EXT_ACTIVE));
+}
+
+#if SL_OPENTHREAD_COEX_COUNTER_ENABLE
+
+void sl_rail_util_coex_counter_on_event(sl_rail_util_coex_event_t event)
+{
+    otEXPECT(event < SL_RAIL_UTIL_COEX_EVENT_COUNT);
+    sCoexCounters[event] += 1;
+exit:
+    return;
+}
+
+void efr32RadioGetCoexCounters(uint32_t (*aCoexCounters)[SL_RAIL_UTIL_COEX_EVENT_COUNT])
+{
+    memset((void *)aCoexCounters, 0, sizeof(*aCoexCounters));
+    memcpy(aCoexCounters, sCoexCounters, sizeof(*aCoexCounters));
+}
+
+void efr32RadioClearCoexCounters(void)
+{
+    memset((void *)sCoexCounters, 0, sizeof(sCoexCounters));
+}
+
+#endif // SL_OPENTHREAD_COEX_COUNTER_ENABLE
+#endif // SL_CATALOG_RAIL_UTIL_COEX_PRESENT
+
 RAIL_AntennaConfig_t halAntennaConfig;
 
 void initAntenna(void)
diff --git a/examples/platforms/efr32/src/system.c b/examples/platforms/efr32/src/system.c
index 9e3f57a..90fd724 100644
--- a/examples/platforms/efr32/src/system.c
+++ b/examples/platforms/efr32/src/system.c
@@ -39,7 +39,6 @@
 #include <string.h>
 
 #include "openthread-system.h"
-#include <openthread/platform/uart.h>
 
 #include "common/logging.hpp"
 
@@ -55,15 +54,11 @@
 #include "em_system.h"
 #include "hal-config.h"
 #include "hal_common.h"
+#include "platform-efr32.h"
 #include "rail.h"
+#include "sl_device_init_nvic.h"
 #include "sl_mpu.h"
 #include "sl_sleeptimer.h"
-#if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
-#include "sl_malloc.h"
-#include "openthread/heap.h"
-#endif
-
-#include "platform-efr32.h"
 
 #if (HAL_FEM_ENABLE)
 #include "fem-control.h"
@@ -170,18 +165,8 @@
 
     __disable_irq();
 
-#if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
-    otHeapSetCAllocFree(sl_calloc, sl_free);
-#endif
-#undef FIXED_EXCEPTION
-#define FIXED_EXCEPTION(vectorNumber, functionName, deviceIrqn, deviceIrqHandler)
-#define EXCEPTION(vectorNumber, functionName, deviceIrqn, deviceIrqHandler, priorityLevel, subpriority) \
-    NVIC_SetPriority(deviceIrqn, NVIC_EncodePriority(PRIGROUP_POSITION - 1, priorityLevel, subpriority));
-#include NVIC_CONFIG
-#undef EXCEPTION
-
-    NVIC_SetPriorityGrouping(PRIGROUP_POSITION - 1);
     CHIP_Init();
+    sl_device_init_nvic();
     halInitChipSpecific();
     BSP_Init(BSP_INIT_BCC);
 
diff --git a/examples/platforms/efr32/src/uart.c b/examples/platforms/efr32/src/uart.c
index 0ba169f..638bf9e 100644
--- a/examples/platforms/efr32/src/uart.c
+++ b/examples/platforms/efr32/src/uart.c
@@ -31,18 +31,32 @@
  *   This file implements the OpenThread platform abstraction for UART communication.
  *
  */
-
 #include <stddef.h>
+#include <string.h>
 
 #include "openthread-system.h"
-#include <openthread/platform/uart.h>
+#include <openthread-core-config.h>
 
 #include "utils/code_utils.h"
+#include "utils/uart.h"
 
+#include "ecode.h"
 #include "em_core.h"
+#include "sl_sleeptimer.h"
+#include "sl_status.h"
 #include "uartdrv.h"
 
 #include "hal-config.h"
+#include "platform-efr32.h"
+#include "sl_uartdrv_usart_vcom_config.h"
+
+#define HELPER1(x) USART##x##_RX_IRQn
+#define HELPER2(x) HELPER1(x)
+#define USART_IRQ HELPER2(SL_UARTDRV_USART_VCOM_PERIPHERAL_NO)
+
+#define HELPER3(x) USART##x##_RX_IRQHandler
+#define HELPER4(x) HELPER3(x)
+#define USART_IRQHandler HELPER4(SL_UARTDRV_USART_VCOM_PERIPHERAL_NO)
 
 enum
 {
@@ -89,9 +103,14 @@
 
 static UARTDRV_HandleData_t sUartHandleData;
 static UARTDRV_Handle_t     sUartHandle = &sUartHandleData;
-static uint8_t              sReceiveBuffer[2];
-static const uint8_t *      sTransmitBuffer = NULL;
-static volatile uint16_t    sTransmitLength = 0;
+
+// In order to reduce the probability of data loss due to disabled interrupts, we use
+// two duplicate receive buffers so we can always have one "active" receive request.
+#define RECEIVE_BUFFER_SIZE 128
+static uint8_t       sReceiveBuffer1[RECEIVE_BUFFER_SIZE];
+static uint8_t       sReceiveBuffer2[RECEIVE_BUFFER_SIZE];
+static uint8_t       lastCount           = 0;
+static volatile bool sCheckForTxComplete = false;
 
 typedef struct ReceiveFifo_t
 {
@@ -106,17 +125,21 @@
 static ReceiveFifo_t sReceiveFifo;
 
 static void processReceive(void);
+static void processTransmit(void);
 
 static void receiveDone(UARTDRV_Handle_t aHandle, Ecode_t aStatus, uint8_t *aData, UARTDRV_Count_t aCount)
 {
+    OT_UNUSED_VARIABLE(aStatus);
+
     // We can only write if incrementing mTail doesn't equal mHead
-    if (sReceiveFifo.mHead != (sReceiveFifo.mTail + 1) % kReceiveFifoSize)
+    if (sReceiveFifo.mHead != (sReceiveFifo.mTail + aCount - lastCount) % kReceiveFifoSize)
     {
-        sReceiveFifo.mBuffer[sReceiveFifo.mTail] = aData[0];
-        sReceiveFifo.mTail                       = (sReceiveFifo.mTail + 1) % kReceiveFifoSize;
+        memcpy(sReceiveFifo.mBuffer + sReceiveFifo.mTail, aData + lastCount, aCount - lastCount);
+        sReceiveFifo.mTail = (sReceiveFifo.mTail + aCount - lastCount) % kReceiveFifoSize;
+        lastCount          = 0;
     }
 
-    UARTDRV_Receive(aHandle, aData, 1, receiveDone);
+    UARTDRV_Receive(aHandle, aData, aCount, receiveDone);
     otSysEventSignalPending();
 }
 
@@ -127,12 +150,24 @@
     OT_UNUSED_VARIABLE(aData);
     OT_UNUSED_VARIABLE(aCount);
 
-    sTransmitLength = 0;
+    // This value will be used later in processTransmit() to call otPlatUartSendDone()
+    sCheckForTxComplete = true;
+
     otSysEventSignalPending();
 }
 
 static void processReceive(void)
 {
+    uint8_t *       aData;
+    UARTDRV_Count_t aCount, remaining;
+    CORE_ATOMIC_SECTION(UARTDRV_GetReceiveStatus(sUartHandle, &aData, &aCount, &remaining);
+
+                        if (aCount > lastCount) {
+                            memcpy(sReceiveFifo.mBuffer + sReceiveFifo.mTail, aData + lastCount, aCount - lastCount);
+                            sReceiveFifo.mTail = (sReceiveFifo.mTail + aCount - lastCount) % kReceiveFifoSize;
+                            lastCount          = aCount;
+                        })
+
     // Copy tail to prevent multiple reads
     uint16_t tail = sReceiveFifo.mTail;
 
@@ -155,20 +190,76 @@
     }
 }
 
+static void flushTimeoutAlarmCallback(sl_sleeptimer_timer_handle_t *aHandle, void *aData)
+{
+    OT_UNUSED_VARIABLE(aHandle);
+    OT_UNUSED_VARIABLE(aData);
+    bool *flushTimedOut = (bool *)aData;
+    *flushTimedOut      = true;
+}
+
 otError otPlatUartFlush(void)
 {
-    return OT_ERROR_NOT_IMPLEMENTED;
+    otError                      error         = OT_ERROR_NONE;
+    sl_status_t                  status        = SL_STATUS_OK;
+    volatile bool                flushTimedOut = false;
+    sl_sleeptimer_timer_handle_t flushTimer;
+
+    // Start flush timeout timer
+    status = sl_sleeptimer_start_timer_ms(&flushTimer, OPENTHREAD_CONFIG_EFR32_UART_TX_FLUSH_TIMEOUT_MS,
+                                          flushTimeoutAlarmCallback, NULL, 0,
+                                          SL_SLEEPTIMER_NO_HIGH_PRECISION_HF_CLOCKS_REQUIRED_FLAG);
+    otEXPECT_ACTION(status == SL_STATUS_OK, error = OT_ERROR_FAILED);
+
+    // Block until DMA has finished transmitting every buffer in sUartTxQueue and becomes idle
+    uint8_t transmitQueueDepth = 0;
+    bool    uartFullyFlushed   = false;
+    bool    uartIdle           = false;
+
+    do
+    {
+        // Check both peripheral status and queue depth
+        transmitQueueDepth = UARTDRV_GetTransmitDepth(sUartHandle);
+        uartIdle           = UARTDRV_GetPeripheralStatus(sUartHandle) & (UARTDRV_STATUS_TXIDLE | UARTDRV_STATUS_TXC);
+        uartFullyFlushed   = uartIdle && (transmitQueueDepth == 0);
+    } while (!uartFullyFlushed && !flushTimedOut);
+
+    sl_sleeptimer_stop_timer(&flushTimer);
+
+    if (flushTimedOut)
+    {
+        // Abort all transmits
+        UARTDRV_Abort(sUartHandle, uartdrvAbortTransmit);
+    }
+
+exit:
+    return error;
 }
 
 static void processTransmit(void)
 {
-    if (sTransmitBuffer != NULL && sTransmitLength == 0)
+    // NOTE: This check needs to be done in here and cannot be done in transmitDone because the transmit may not be
+    // fully complete when the transmitDone callback is called.
+    if (!sCheckForTxComplete)
     {
-        sTransmitBuffer = NULL;
+        return;
+    }
+
+    bool    queueEmpty = UARTDRV_GetPeripheralStatus(sUartHandle) & (UARTDRV_STATUS_TXIDLE | UARTDRV_STATUS_TXC);
+    uint8_t transmitQueueDepth = UARTDRV_GetTransmitDepth(sUartHandle);
+
+    if (transmitQueueDepth == 0 && queueEmpty)
+    {
+        sCheckForTxComplete = false;
         otPlatUartSendDone();
     }
 }
 
+void USART_IRQHandler(void)
+{
+    otSysEventSignalPending();
+}
+
 otError otPlatUartEnable(void)
 {
     UARTDRV_InitUart_t uartInit = USART_INIT;
@@ -178,10 +269,15 @@
 
     UARTDRV_Init(sUartHandle, &uartInit);
 
-    for (uint8_t i = 0; i < sizeof(sReceiveBuffer); i++)
-    {
-        UARTDRV_Receive(sUartHandle, &sReceiveBuffer[i], sizeof(sReceiveBuffer[i]), receiveDone);
-    }
+    // When one receive request is completed, the other buffer is used for a separate receive request, issued
+    // immediately.
+    UARTDRV_Receive(sUartHandle, sReceiveBuffer1, RECEIVE_BUFFER_SIZE, receiveDone);
+    UARTDRV_Receive(sUartHandle, sReceiveBuffer2, RECEIVE_BUFFER_SIZE, receiveDone);
+
+    // Enable USART0 interrupt to wake OT task when data arrives
+    NVIC_ClearPendingIRQ(USART_IRQ);
+    NVIC_EnableIRQ(USART_IRQ);
+    USART_IntEnable(SL_UARTDRV_USART_VCOM_PERIPHERAL, USART_IF_RXDATAV);
 
     return OT_ERROR_NONE;
 }
@@ -193,14 +289,18 @@
 
 otError otPlatUartSend(const uint8_t *aBuf, uint16_t aBufLength)
 {
-    otError error = OT_ERROR_NONE;
+    otError error  = OT_ERROR_NONE;
+    Ecode_t status = ECODE_EMDRV_UARTDRV_OK;
 
-    otEXPECT_ACTION(sTransmitBuffer == NULL, error = OT_ERROR_BUSY);
+    // Ensure that no ongoing transmits have started finishing.
+    // This prevents queued buffers from being modified before they are transmitted
+    if (sCheckForTxComplete)
+    {
+        otPlatUartFlush();
+    }
 
-    sTransmitBuffer = aBuf;
-    sTransmitLength = aBufLength;
-
-    UARTDRV_Transmit(sUartHandle, (uint8_t *)sTransmitBuffer, sTransmitLength, transmitDone);
+    status = UARTDRV_Transmit(sUartHandle, (uint8_t *)aBuf, aBufLength, transmitDone);
+    otEXPECT_ACTION(status == ECODE_EMDRV_UARTDRV_OK, error = OT_ERROR_FAILED);
 
 exit:
     return error;
diff --git a/examples/platforms/gp712/CMakeLists.txt b/examples/platforms/gp712/CMakeLists.txt
new file mode 100644
index 0000000..c4c6239
--- /dev/null
+++ b/examples/platforms/gp712/CMakeLists.txt
@@ -0,0 +1,105 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+set(OT_PLATFORM_LIB "openthread-gp712" PARENT_SCOPE)
+
+if(NOT OT_CONFIG)
+    set(OT_CONFIG "openthread-core-gp712-config.h")
+    set(OT_CONFIG ${OT_CONFIG} PARENT_SCOPE)
+endif()
+
+list(APPEND OT_PLATFORM_DEFINES
+    "OPENTHREAD_CORE_CONFIG_PLATFORM_CHECK_FILE=\"openthread-core-gp712-config-check.h\""
+)
+
+list(APPEND OT_PLATFORM_DEFINES
+    "_BSD_SOURCE=1"
+    "_DEFAULT_SOURCE=1"
+)
+
+set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE)
+
+set(OT_PUBLIC_INCLUDES ${OT_PUBLIC_INCLUDES} PARENT_SCOPE)
+
+if(OT_CFLAGS MATCHES "-pedantic-errors")
+    string(REPLACE "-pedantic-errors" "" OT_CFLAGS "${OT_CFLAGS}")
+endif()
+
+if(OT_CFLAGS MATCHES "-Wno-c\\+\\+14-compat")
+    string(REPLACE "-Wno-c++14-compat" "" OT_CFLAGS "${OT_CFLAGS}")
+endif()
+
+list(APPEND OT_PLATFORM_DEFINES "OPENTHREAD_PROJECT_CORE_CONFIG_FILE=\"${OT_CONFIG}\"")
+
+add_library(openthread-gp712
+    alarm.c
+    diag.c     
+    entropy.c
+    flash.c
+    logging.c
+    misc.c
+    radio.c
+    system.c
+    uart-posix.c
+)
+
+set_target_properties(
+    openthread-gp712
+    PROPERTIES
+        C_STANDARD 99
+        CXX_STANDARD 11
+)
+
+target_link_libraries(openthread-gp712
+    PRIVATE
+        gp712-driver
+        ${OT_MBEDTLS}
+        ot-config
+    PUBLIC
+        -lrt
+        -pthread
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+)
+
+target_compile_definitions(openthread-gp712
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_options(openthread-gp712
+    PRIVATE
+        ${OT_CFLAGS}
+)
+
+target_include_directories(openthread-gp712
+    PRIVATE
+        ${OT_PUBLIC_INCLUDES}
+        ${PROJECT_SOURCE_DIR}/src/core
+        ${PROJECT_SOURCE_DIR}/examples/platforms
+)
diff --git a/examples/platforms/gp712/Makefile.am b/examples/platforms/gp712/Makefile.am
index 9009d46..706870a 100644
--- a/examples/platforms/gp712/Makefile.am
+++ b/examples/platforms/gp712/Makefile.am
@@ -1,5 +1,5 @@
 #
-#  Copyright (c) 2016-2017, The OpenThread Authors.
+#  Copyright (c) 2019, The OpenThread Authors.
 #  All rights reserved.
 #
 #  Redistribution and use in source and binary forms, with or without
@@ -32,39 +32,36 @@
 override CFLAGS    := $(filter-out -Wcast-align,$(CFLAGS))
 override CXXFLAGS  := $(filter-out -Wcast-align,$(CXXFLAGS))
 
-lib_LIBRARIES                             = libopenthread-gp712.a
+lib_LIBRARIES                                   = libopenthread-gp712.a
 
-libopenthread_gp712_a_CPPFLAGS            = \
-    -I$(top_srcdir)/include                 \
-    -I$(top_srcdir)/examples/platforms      \
-    -I$(top_srcdir)/src/core                \
-    -lrt                                    \
-    -lpthread                               \
+libopenthread_gp712_a_CPPFLAGS                  = \
+    -I$(top_srcdir)/include                       \
+    -I$(top_srcdir)/src/core                      \
+    -I$(top_srcdir)/examples/platforms            \
+    -I$(top_srcdir)/examples/platforms/gp712      \
+    -lrt                                          \
+    -pthread                                      \
     $(NULL)
 
-PLATFORM_SOURCES                          = \
-    alarm.c                                 \
-    alarm_qorvo.h                           \
-    diag.c                                  \
-    entropy.c                               \
-    flash.c                                 \
-    logging.c                               \
-    misc.c                                  \
-    openthread-core-gp712-config.h          \
-    openthread-core-gp712-config-check.h    \
-    platform_qorvo.h                        \
-    radio.c                                 \
-    radio_qorvo.h                           \
-    random_qorvo.h                          \
-    system.c                                \
-    uart-posix.c                            \
-    uart_qorvo.h                            \
+PLATFORM_SOURCES                                = \
+    alarm.c                                       \
+    alarm_qorvo.h                                 \
+    diag.c                                        \
+    entropy.c                                     \
+    flash.c                                       \
+    logging.c                                     \
+    misc.c                                        \
+    openthread-core-gp712-config.h                \
+    openthread-core-gp712-config-check.h          \
+    platform_qorvo.h                              \
+    radio.c                                       \
+    radio_qorvo.h                                 \
+    random_qorvo.h                                \
+    system.c                                      \
+    uart-posix.c                                  \
+    uart_qorvo.h                                  \
     $(NULL)
 
-Dash                                      = -
-libopenthread_gp712_a_LIBADD              = \
-    $(shell find $(top_builddir)/examples/platforms/utils $(Dash)type f $(Dash)name "*.o")
-
 libopenthread_gp712_a_SOURCES             = \
     $(PLATFORM_SOURCES)                     \
     $(NULL)
diff --git a/examples/platforms/gp712/Makefile.platform.am b/examples/platforms/gp712/Makefile.platform.am
index 7a979f6..d7d844f 100644
--- a/examples/platforms/gp712/Makefile.platform.am
+++ b/examples/platforms/gp712/Makefile.platform.am
@@ -1,5 +1,5 @@
 #
-#  Copyright (c) 2017, The OpenThread Authors.
+#  Copyright (c) 2019, The OpenThread Authors.
 #  All rights reserved.
 #
 #  Redistribution and use in source and binary forms, with or without
@@ -32,5 +32,15 @@
 
 LDADD_COMMON                                                          += \
     $(top_builddir)/examples/platforms/gp712/libopenthread-gp712.a       \
-    $(top_srcdir)/third_party/Qorvo/gp712/libQorvoRPi.a                  \
     $(NULL)
+if OPENTHREAD_ENABLE_FTD
+LDADD_COMMON                                                          += \
+    $(top_srcdir)/third_party/Qorvo/repo/gp712/lib/libQorvoGP712_ftd.a   \
+    $(NULL)
+else # OPENTHREAD_ENABLE_FTD
+if OPENTHREAD_ENABLE_MTD
+LDADD_COMMON                                                          += \
+    $(top_srcdir)/third_party/Qorvo/repo/gp712/lib/libQorvoGP712_mtd.a   \
+    $(NULL)
+endif # OPENTHREAD_ENABLE_MTD
+endif
diff --git a/examples/platforms/gp712/README.md b/examples/platforms/gp712/README.md
index f8d3aec..ceacb88 100644
--- a/examples/platforms/gp712/README.md
+++ b/examples/platforms/gp712/README.md
@@ -4,13 +4,13 @@
 
 ## Toolchain
 
-This example use the GNU GCC toolchain on the Raspberry Pi. To build on the Pi:
+This example uses the GNU GCC toolchain on the Raspberry Pi. To build on the Pi:
 
 1. Download the repo to the Pi
-2. go to the subfolder in the openthread repo: third_party/nlbuild-autotools/repo/tools/packages and enter this command:
+2. go to the subfolder in the openthread repo: third_party/nlbuild-autotools/repo/ and enter this command:
 
 ```bash
-$ sudo /bin/bash build
+$ make tools
 ```
 
 Note that you may need to install additional packages to make this build work, depending on your actual RPi OS version. The build process will complain if additional packages are required.
@@ -26,7 +26,21 @@
 
 After a successful build, the `elf` files are found in `<path-to-openthread>/output/gp712/bin`.
 
-Building a variant which interfaces via a tcp socket is also possible. Replace the uart-posix.c with uart-socket.c in the Makefile.am from examples/platforms/gp712/Makefile.am and rebuild. Now it should be possible to open a telnet to socket 9190 of the raspberry pi from a remote PC. This also easier testing with the official Thread Test Harness.
+## Cmake build
+
+Make sure arm-linux-gnueabihf-gcc compiler installed in `$PATH`
+
+```bash
+cd <path-to-openthread>
+./script/cmake-build gp712
+```
+
+After a successful build, binary files will be generated:
+
+```
+./build/gp712/examples/apps/ncp/ot-rcp
+./build/gp712/examples/apps/cli/ot-cli-ftd
+```
 
 ##
 
diff --git a/examples/platforms/gp712/alarm.c b/examples/platforms/gp712/alarm.c
index d3c8c0e..a32c83b 100644
--- a/examples/platforms/gp712/alarm.c
+++ b/examples/platforms/gp712/alarm.c
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2016-2017, The OpenThread Authors.
+ *  Copyright (c) 2019, The OpenThread Authors.
  *  All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
@@ -40,10 +40,6 @@
 #include <openthread/platform/alarm-milli.h>
 #include <openthread/platform/diag.h>
 
-void qorvoAlarmInit(void)
-{
-}
-
 uint32_t otPlatAlarmMilliGetNow(void)
 {
     return qorvoAlarmGetTimeMs();
@@ -59,7 +55,7 @@
     OT_UNUSED_VARIABLE(t0);
 
     qorvoAlarmUnScheduleEventArg((qorvoAlarmCallback_t)qorvoAlarmFired, aInstance);
-    qorvoAlarmScheduleEventArg(dt * 1000, qorvoAlarmFired, aInstance);
+    qorvoAlarmScheduleEventArg(dt, qorvoAlarmFired, aInstance);
 }
 
 void otPlatAlarmMilliStop(otInstance *aInstance)
diff --git a/examples/platforms/gp712/alarm_qorvo.h b/examples/platforms/gp712/alarm_qorvo.h
index 1073d11..c5b5036 100644
--- a/examples/platforms/gp712/alarm_qorvo.h
+++ b/examples/platforms/gp712/alarm_qorvo.h
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2016-2017, The OpenThread Authors.
+ *  Copyright (c) 2019, The OpenThread Authors.
  *  All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
diff --git a/tests/scripts/expect/v1_2-rcp.exp b/examples/platforms/gp712/arm-linux-gnueabihf.cmake
old mode 100755
new mode 100644
similarity index 67%
copy from tests/scripts/expect/v1_2-rcp.exp
copy to examples/platforms/gp712/arm-linux-gnueabihf.cmake
index 776d880..c77a859
--- a/tests/scripts/expect/v1_2-rcp.exp
+++ b/examples/platforms/gp712/arm-linux-gnueabihf.cmake
@@ -1,6 +1,5 @@
-#!/usr/bin/expect -f
 #
-#  Copyright (c) 2020, The OpenThread Authors.
+#  Copyright (c) 2021, The OpenThread Authors.
 #  All rights reserved.
 #
 #  Redistribution and use in source and binary forms, with or without
@@ -27,39 +26,18 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
-source "tests/scripts/expect/_common.exp"
+set(CMAKE_SYSTEM_NAME              Generic)
+set(CMAKE_SYSTEM_PROCESSOR         ARM)
 
-spawn_node 1
+set(CMAKE_C_COMPILER               arm-linux-gnueabihf-gcc)
+set(CMAKE_CXX_COMPILER             arm-linux-gnueabihf-g++)
+set(CMAKE_ASM_COMPILER             arm-linux-gnueabihf-as)
+set(CMAKE_RANLIB                   arm-linux-gnueabihf-ranlib)
 
-send "panid 0xface\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-wait_for "state" "leader"
-expect "Done"
+set(COMMON_C_FLAGS                 "-fdata-sections -ffunction-sections")
 
-send "ipaddr mleid\n"
-expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-set addr $expect_out(1,string)
-expect "Done"
 
-spawn_node 2 mtd
-
-send "panid 0xface\n"
-expect "Done"
-send "mode -\n"
-expect "Done"
-send "csl period 5000\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-wait_for "state" "child"
-expect "Done"
-send "ping $addr\n"
-expect "16 bytes from $addr: icmp_seq=1"
-
-dispose_all
+set(CMAKE_C_FLAGS_INIT             "${COMMON_C_FLAGS} -std=gnu99")
+set(CMAKE_CXX_FLAGS_INIT           "${COMMON_C_FLAGS} -fno-exceptions -fno-rtti")
+set(CMAKE_ASM_FLAGS_INIT           "${COMMON_C_FLAGS}")
+set(CMAKE_EXE_LINKER_FLAGS_INIT    "${COMMON_C_FLAGS}")
diff --git a/examples/platforms/gp712/diag.c b/examples/platforms/gp712/diag.c
index 45b933e..e79c2f2 100644
--- a/examples/platforms/gp712/diag.c
+++ b/examples/platforms/gp712/diag.c
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2016-2017, The OpenThread Authors.
+ *  Copyright (c) 2019, The OpenThread Authors.
  *  All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
@@ -31,7 +31,7 @@
 #include <string.h>
 #include <sys/time.h>
 
-#include <openthread/config.h>
+#include <openthread-core-config.h>
 #include <openthread/platform/alarm-milli.h>
 #include <openthread/platform/radio.h>
 
diff --git a/examples/platforms/gp712/entropy.c b/examples/platforms/gp712/entropy.c
index c279955..bf25db5 100644
--- a/examples/platforms/gp712/entropy.c
+++ b/examples/platforms/gp712/entropy.c
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2016-2019, The OpenThread Authors.
+ *  Copyright (c) 2019, The OpenThread Authors.
  *  All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
@@ -41,53 +41,14 @@
 
 #include "utils/code_utils.h"
 
-#if __SANITIZE_ADDRESS__ != 0
-
-static uint32_t sState = 1;
-
-#endif // __SANITIZE_ADDRESS__
-
 void qorvoRandomInit(void)
 {
-#if __SANITIZE_ADDRESS__ != 0
-
-    sState = (uint32_t)time(NULL);
-
-#endif // __SANITIZE_ADDRESS__
 }
 
-#if __SANITIZE_ADDRESS__ != 0
-
-static uint32_t randomUint32Get(void)
-{
-    uint32_t mlcg, p, q;
-    uint64_t tmpstate;
-
-    tmpstate = (uint64_t)33614 * (uint64_t)sState;
-    q        = tmpstate & 0xffffffff;
-    q        = q >> 1;
-    p        = tmpstate >> 32;
-    mlcg     = p + q;
-
-    if (mlcg & 0x80000000)
-    {
-        mlcg &= 0x7fffffff;
-        mlcg++;
-    }
-
-    sState = mlcg;
-
-    return mlcg;
-}
-
-#endif // __SANITIZE_ADDRESS__
-
 otError otPlatEntropyGet(uint8_t *aOutput, uint16_t aOutputLength)
 {
     otError error = OT_ERROR_NONE;
 
-#if __SANITIZE_ADDRESS__ == 0
-
     FILE * file = NULL;
     size_t readLength;
 
@@ -106,26 +67,5 @@
         fclose(file);
     }
 
-#else // __SANITIZE_ADDRESS__
-
-    /*
-     * THE IMPLEMENTATION BELOW IS NOT COMPLIANT WITH THE THREAD SPECIFICATION.
-     *
-     * Address Sanitizer triggers test failures when reading random
-     * values from /dev/urandom.  The pseudo-random number generator
-     * implementation below is only used to enable continuous
-     * integration checks with Address Sanitizer enabled.
-     */
-    otEXPECT_ACTION(aOutput && aOutputLength, error = OT_ERROR_INVALID_ARGS);
-
-    for (uint16_t length = 0; length < aOutputLength; length++)
-    {
-        aOutput[length] = (uint8_t)randomUint32Get();
-    }
-
-exit:
-
-#endif // __SANITIZE_ADDRESS__
-
     return error;
 }
diff --git a/examples/platforms/gp712/flash.c b/examples/platforms/gp712/flash.c
index 1d94a9b..4439952 100644
--- a/examples/platforms/gp712/flash.c
+++ b/examples/platforms/gp712/flash.c
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2016-2017, The OpenThread Authors.
+ *  Copyright (c) 2019, The OpenThread Authors.
  *  All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
@@ -30,6 +30,7 @@
 
 #include "platform_qorvo.h"
 
+#undef NDEBUG
 #include <assert.h>
 #include <fcntl.h>
 #include <stdio.h>
@@ -38,7 +39,7 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
-#include <openthread/config.h>
+#include <openthread-core-config.h>
 #include <openthread/platform/flash.h>
 
 static int sFlashFd = -1;
diff --git a/examples/platforms/gp712/logging.c b/examples/platforms/gp712/logging.c
index 6e8d1f7..1b15a79 100644
--- a/examples/platforms/gp712/logging.c
+++ b/examples/platforms/gp712/logging.c
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2016-2017, The OpenThread Authors.
+ *  Copyright (c) 2019, The OpenThread Authors.
  *  All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
@@ -28,7 +28,6 @@
 
 #include "platform_qorvo.h"
 #include <openthread-core-config.h>
-#include <openthread/config.h>
 
 #include <ctype.h>
 #include <inttypes.h>
@@ -41,16 +40,11 @@
 #include <openthread/platform/logging.h>
 #include <openthread/platform/toolchain.h>
 
+#include "uart_qorvo.h"
 #include "utils/code_utils.h"
 
 // Macro to append content to end of the log string.
 
-#define LOG_PRINTF(...)                                                                   \
-    charsWritten = snprintf(&logString[offset], sizeof(logString) - offset, __VA_ARGS__); \
-    otEXPECT_ACTION(charsWritten >= 0, logString[offset] = 0);                            \
-    offset += (unsigned int)charsWritten;                                                 \
-    otEXPECT_ACTION(offset < sizeof(logString), logString[sizeof(logString) - 1] = 0)
-
 #if (OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_PLATFORM_DEFINED)
 
 int PlatOtLogLevelToSysLogLevel(otLogLevel aLogLevel)
@@ -88,23 +82,10 @@
 
 OT_TOOL_WEAK void otPlatLog(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aFormat, ...)
 {
-    OT_UNUSED_VARIABLE(aLogRegion);
-
-    char         logString[512];
-    unsigned int offset;
-    int          charsWritten;
-    va_list      args;
-
-    offset = 0;
-
+    va_list args;
     va_start(args, aFormat);
-    charsWritten = vsnprintf(&logString[offset], sizeof(logString) - offset, aFormat, args);
+    qorvoUartLog(aLogLevel, aLogRegion, aFormat, args);
     va_end(args);
-
-    otEXPECT_ACTION(charsWritten >= 0, logString[offset] = 0);
-
-exit:
-    syslog(PlatOtLogLevelToSysLogLevel(aLogLevel), "%s", logString);
 }
 
 #endif
diff --git a/examples/platforms/gp712/misc.c b/examples/platforms/gp712/misc.c
index 15a297c..b332dc5 100644
--- a/examples/platforms/gp712/misc.c
+++ b/examples/platforms/gp712/misc.c
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2016-2017, The OpenThread Authors.
+ *  Copyright (c) 2019, The OpenThread Authors.
  *  All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
diff --git a/examples/platforms/gp712/openthread-core-gp712-config.h b/examples/platforms/gp712/openthread-core-gp712-config.h
index e71718f..0988e6d 100644
--- a/examples/platforms/gp712/openthread-core-gp712-config.h
+++ b/examples/platforms/gp712/openthread-core-gp712-config.h
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2016-2017, The OpenThread Authors.
+ *  Copyright (c) 2019, The OpenThread Authors.
  *  All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
@@ -53,4 +53,84 @@
  */
 #define OPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE 1
 
+/**
+ * @def OPENTHREAD_CONFIG_MAC_SOFTWARE_ACK_TIMEOUT_ENABLE
+ *
+ * Define to 1 if you want to enable software ACK timeout logic.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_MAC_SOFTWARE_ACK_TIMEOUT_ENABLE
+#define OPENTHREAD_CONFIG_MAC_SOFTWARE_ACK_TIMEOUT_ENABLE 0
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_MAC_SOFTWARE_RETRANSMIT_ENABLE
+ *
+ * Define to 1 if you want to enable software retransmission logic.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_MAC_SOFTWARE_RETRANSMIT_ENABLE
+#define OPENTHREAD_CONFIG_MAC_SOFTWARE_RETRANSMIT_ENABLE 1
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_MAC_SOFTWARE_CSMA_BACKOFF_ENABLE
+ *
+ * Define to 1 if you want to enable software CSMA-CA backoff logic.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_MAC_SOFTWARE_CSMA_BACKOFF_ENABLE
+#define OPENTHREAD_CONFIG_MAC_SOFTWARE_CSMA_BACKOFF_ENABLE 1
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE
+ *
+ * Define to 1 if you want to enable software transmission security logic.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE
+#define OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_TIMING_ENABLE
+ *
+ * Define to 1 to enable software transmission target time logic.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_TIMING_ENABLE
+#define OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_TIMING_ENABLE (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_DIAG_ENABLE
+ *
+ * Define as 1 to enable the diag feature.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_DIAG_ENABLE
+#define OPENTHREAD_CONFIG_DIAG_ENABLE 1
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_NCP_SPI_ENABLE
+ *
+ * Define as 1 to enable SPI NCP interface.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_NCP_SPI_ENABLE
+#define OPENTHREAD_CONFIG_NCP_SPI_ENABLE 0
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
+ *
+ * Define as 1 to enable UART NCP interface.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
+#define OPENTHREAD_CONFIG_NCP_HDLC_ENABLE 1
+#endif
+
 #endif // OPENTHREAD_CORE_GP712_CONFIG_H_
diff --git a/examples/platforms/gp712/platform_qorvo.h b/examples/platforms/gp712/platform_qorvo.h
index b6c7eb3..af657b8 100644
--- a/examples/platforms/gp712/platform_qorvo.h
+++ b/examples/platforms/gp712/platform_qorvo.h
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2017, The OpenThread Authors.
+ *  Copyright (c) 2019, The OpenThread Authors.
  *  All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
@@ -36,7 +36,6 @@
 #define PLATFORM_QORVO_H_
 
 #include <openthread-core-config.h>
-#include <openthread/config.h>
 
 #include <stdbool.h>
 #include <stdint.h>
diff --git a/examples/platforms/gp712/radio.c b/examples/platforms/gp712/radio.c
index faa5c65..23fb1df 100644
--- a/examples/platforms/gp712/radio.c
+++ b/examples/platforms/gp712/radio.c
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2016-2017, The OpenThread Authors.
+ *  Copyright (c) 2019, The OpenThread Authors.
  *  All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
@@ -77,6 +77,11 @@
 
 static otCachedSettings_t otCachedSettings;
 
+/* Upper layer relies on txpower could be set before receive, but MAC have per-channel config for it.
+   Store txpower until channel set in Receive(). */
+#define PENDING_TX_POWER_NONE (-1)
+static int8_t pendingTxPower = PENDING_TX_POWER_NONE;
+
 static uint8_t sScanstate         = 0;
 static int8_t  sLastReceivedPower = 127;
 
@@ -152,13 +157,12 @@
 
     otError error = OT_ERROR_INVALID_STATE;
 
-    if (sState == OT_RADIO_STATE_RECEIVE)
+    if (sState == OT_RADIO_STATE_RECEIVE || sState == OT_RADIO_STATE_SLEEP)
     {
         qorvoRadioSetRxOnWhenIdle(false);
         error  = OT_ERROR_NONE;
         sState = OT_RADIO_STATE_SLEEP;
     }
-
     return error;
 }
 
@@ -171,6 +175,11 @@
     if ((sState != OT_RADIO_STATE_DISABLED) && (sScanstate == 0))
     {
         qorvoRadioSetCurrentChannel(aChannel);
+        if (pendingTxPower != PENDING_TX_POWER_NONE)
+        {
+            qorvoRadioSetTransmitPower(pendingTxPower);
+            pendingTxPower = PENDING_TX_POWER_NONE;
+        }
         error = OT_ERROR_NONE;
     }
 
@@ -186,8 +195,7 @@
 
 otError otPlatRadioTransmit(otInstance *aInstance, otRadioFrame *aPacket)
 {
-    otError err = OT_ERROR_NONE;
-
+    otError err    = OT_ERROR_NONE;
     pQorvoInstance = aInstance;
 
     otEXPECT_ACTION(sState != OT_RADIO_STATE_DISABLED, err = OT_ERROR_INVALID_STATE);
@@ -226,10 +234,6 @@
         sLastReceivedPower = aPacket->mInfo.mRxInfo.mRssi;
     }
 
-    // TODO Set this flag only when the packet is really acknowledged with frame pending set.
-    // See https://github.com/openthread/openthread/pull/3785
-    aPacket->mInfo.mRxInfo.mAckedWithFramePending = true;
-
     otPlatRadioReceiveDone(pQorvoInstance, aPacket, aError);
 }
 
@@ -334,20 +338,55 @@
 
 otError otPlatRadioGetTransmitPower(otInstance *aInstance, int8_t *aPower)
 {
-    // TODO: Create a proper implementation for this driver.
     OT_UNUSED_VARIABLE(aInstance);
-    OT_UNUSED_VARIABLE(aPower);
 
-    return OT_ERROR_NOT_IMPLEMENTED;
+    otError result;
+
+    if (aPower == NULL)
+    {
+        return OT_ERROR_INVALID_ARGS;
+    }
+
+    if ((sState == OT_RADIO_STATE_DISABLED) || (sScanstate != 0))
+    {
+        *aPower = (pendingTxPower == PENDING_TX_POWER_NONE) ? 0 : pendingTxPower;
+        return OT_ERROR_NONE;
+    }
+
+    result = qorvoRadioGetTransmitPower(aPower);
+
+    if (result == OT_ERROR_INVALID_STATE)
+    {
+        // Channel was not set, so txpower is ambigious
+        *aPower = (pendingTxPower == PENDING_TX_POWER_NONE) ? 0 : pendingTxPower;
+        return OT_ERROR_NONE;
+    }
+
+    return result;
 }
 
 otError otPlatRadioSetTransmitPower(otInstance *aInstance, int8_t aPower)
 {
-    // TODO: Create a proper implementation for this driver.
     OT_UNUSED_VARIABLE(aInstance);
-    OT_UNUSED_VARIABLE(aPower);
 
-    return OT_ERROR_NOT_IMPLEMENTED;
+    otError result;
+
+    if ((sState == OT_RADIO_STATE_DISABLED) || (sScanstate != 0))
+    {
+        pendingTxPower = aPower;
+        return OT_ERROR_NONE;
+    }
+
+    result = qorvoRadioSetTransmitPower(aPower);
+
+    if (result == OT_ERROR_INVALID_STATE)
+    {
+        // Channel was not set, so txpower is ambigious
+        pendingTxPower = aPower;
+        result         = OT_ERROR_NONE;
+    }
+
+    return result;
 }
 
 otError otPlatRadioGetCcaEnergyDetectThreshold(otInstance *aInstance, int8_t *aThreshold)
@@ -372,3 +411,9 @@
 
     return GP712_RECEIVE_SENSITIVITY;
 }
+
+const char *otPlatRadioGetVersionString(otInstance *aInstance)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+    return "OPENTHREAD/Qorvo/0.0";
+}
diff --git a/examples/platforms/gp712/radio_qorvo.h b/examples/platforms/gp712/radio_qorvo.h
index 0749ee1..573cf0a 100644
--- a/examples/platforms/gp712/radio_qorvo.h
+++ b/examples/platforms/gp712/radio_qorvo.h
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2017, The OpenThread Authors.
+ *  Copyright (c) 2019, The OpenThread Authors.
  *  All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
@@ -35,6 +35,7 @@
 #ifndef RADIO_QORVO_H_
 #define RADIO_QORVO_H_
 
+#include <stdbool.h>
 #include <stdint.h>
 
 #include <openthread/error.h>
@@ -174,6 +175,22 @@
 otError qorvoRadioClearSrcMatchExtEntry(const uint8_t *aExtAddress, uint16_t panid);
 
 /**
+ * This function gets the transmit power for current channel
+ *
+ * @param[out]  aPower  The transmit power
+ *
+ */
+otError qorvoRadioGetTransmitPower(int8_t *aPower);
+
+/**
+ * This function sets the transmit power for current channel
+ *
+ * @param[in]  aPower  The transmit power
+ *
+ */
+otError qorvoRadioSetTransmitPower(int8_t aPower);
+
+/**
  * This callback is called when the energy scan is finished.
  *
  * @param[in]  aEnergyScanMaxRssi  The amount of energy detected during the ED scan.
diff --git a/examples/platforms/gp712/random_qorvo.h b/examples/platforms/gp712/random_qorvo.h
index 8dd7eef..277019c 100644
--- a/examples/platforms/gp712/random_qorvo.h
+++ b/examples/platforms/gp712/random_qorvo.h
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2017, The OpenThread Authors.
+ *  Copyright (c) 2019, The OpenThread Authors.
  *  All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
diff --git a/examples/platforms/gp712/system.c b/examples/platforms/gp712/system.c
index 3126ad0..147421a 100644
--- a/examples/platforms/gp712/system.c
+++ b/examples/platforms/gp712/system.c
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2016-2017, The OpenThread Authors.
+ *  Copyright (c) 2019, The OpenThread Authors.
  *  All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
@@ -38,8 +38,9 @@
 #include "stdlib.h"
 
 #include <openthread/tasklet.h>
-#include <openthread/platform/uart.h>
 
+#include "alarm_qorvo.h"
+#include "platform_qorvo.h"
 #include "radio_qorvo.h"
 #include "random_qorvo.h"
 #include "uart_qorvo.h"
@@ -52,7 +53,7 @@
 int    gArgumentsCount = 0;
 char **gArguments      = NULL;
 
-bool qorvoPlatGotoSleepCheck(void)
+uint8_t qorvoPlatGotoSleepCheck(void)
 {
     bool canGotoSleep = false;
 
@@ -71,7 +72,7 @@
 
     qorvoPlatInit((qorvoPlatGotoSleepCheckCallback_t)qorvoPlatGotoSleepCheck);
     platformUartInit();
-    // qorvoAlarmInit();
+    qorvoAlarmInit();
     qorvoRandomInit();
     qorvoRadioInit();
 }
diff --git a/examples/platforms/gp712/uart-posix.c b/examples/platforms/gp712/uart-posix.c
index e98c977..2b37685 100644
--- a/examples/platforms/gp712/uart-posix.c
+++ b/examples/platforms/gp712/uart-posix.c
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2016-2017, The OpenThread Authors.
+ *  Copyright (c) 2019, The OpenThread Authors.
  *  All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
@@ -25,6 +25,7 @@
  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  *  POSSIBILITY OF SUCH DAMAGE.
  */
+#define _XOPEN_SOURCE 500
 
 #include "alarm_qorvo.h"
 #include "platform_qorvo.h"
@@ -39,9 +40,8 @@
 #include <termios.h>
 #include <unistd.h>
 
-#include <openthread/platform/uart.h>
-
 #include "utils/code_utils.h"
+#include "utils/uart.h"
 
 #ifdef __linux__
 #include <sys/prctl.h>
@@ -87,6 +87,11 @@
     restore_stdin_termios();
     restore_stdout_termios();
     dup2(s_out_fd, STDOUT_FILENO);
+
+    qorvoPlatUnRegisterPollFunction(s_in_fd);
+    qorvoPlatUnRegisterPollFunction(s_out_fd);
+
+    otPlatUartDisable();
 }
 
 void platformUartInit(void)
@@ -95,8 +100,19 @@
     s_out_fd = dup(STDOUT_FILENO);
     dup2(STDERR_FILENO, STDOUT_FILENO);
 
+    int res = fcntl(s_in_fd, F_SETFD, fcntl(s_in_fd, F_GETFD) | FD_CLOEXEC);
+    otEXPECT_ACTION(res != -1, perror("fcntl() FD_CLOEXEC failed"));
+
+    res = fcntl(s_out_fd, F_SETFD, fcntl(s_out_fd, F_GETFD) | FD_CLOEXEC);
+    otEXPECT_ACTION(res != -1, perror("fcntl() FD_CLOEXEC failed"));
+
     qorvoPlatRegisterPollFunction(s_in_fd, cbKeyPressed);
     qorvoPlatRegisterPollFunction(s_out_fd, cbKeyPressed);
+
+    return;
+
+exit:
+    exit(1);
 }
 
 otError otPlatUartEnable(void)
@@ -173,14 +189,11 @@
         otEXPECT_ACTION(tcsetattr(s_out_fd, TCSANOW, &termios) == 0, perror("tcsetattr"); error = OT_ERROR_GENERIC);
     }
 
+    return error;
+
 exit:
-
-    if (error != OT_ERROR_NONE)
-    {
-        close(s_in_fd);
-        close(s_out_fd);
-    }
-
+    close(s_in_fd);
+    close(s_out_fd);
     return error;
 }
 
@@ -203,13 +216,70 @@
     s_write_buffer = aBuf;
     s_write_length = aBufLength;
 
+    qorvoAlarmScheduleEventArg(0, platformDummy, (void *)&s_in_fd);
+
 exit:
     return error;
 }
 
+void platformUartUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, fd_set *aErrorFdSet, int *aMaxFd)
+{
+    if (aReadFdSet != NULL)
+    {
+        FD_SET(s_in_fd, aReadFdSet);
+
+        if (aErrorFdSet != NULL)
+        {
+            FD_SET(s_in_fd, aErrorFdSet);
+        }
+
+        if (aMaxFd != NULL && *aMaxFd < s_in_fd)
+        {
+            *aMaxFd = s_in_fd;
+        }
+    }
+
+    if ((aWriteFdSet != NULL) && (s_write_length > 0))
+    {
+        FD_SET(s_out_fd, aWriteFdSet);
+
+        if (aErrorFdSet != NULL)
+        {
+            FD_SET(s_out_fd, aErrorFdSet);
+        }
+
+        if (aMaxFd != NULL && *aMaxFd < s_out_fd)
+        {
+            *aMaxFd = s_out_fd;
+        }
+    }
+}
+
 otError otPlatUartFlush(void)
 {
-    return OT_ERROR_NOT_IMPLEMENTED;
+    otError error = OT_ERROR_NONE;
+    ssize_t count;
+
+    otEXPECT_ACTION(s_write_buffer != NULL && s_write_length > 0, error = OT_ERROR_INVALID_STATE);
+
+    while ((count = write(s_out_fd, s_write_buffer, s_write_length)) > 0 && (s_write_length -= count) > 0)
+    {
+        s_write_buffer += count;
+    }
+
+    if (count != -1)
+    {
+        assert(s_write_length == 0);
+        s_write_buffer = NULL;
+    }
+    else
+    {
+        perror("write(UART)");
+        exit(EXIT_FAILURE);
+    }
+
+exit:
+    return error;
 }
 
 void platformUartProcess(void)
@@ -254,7 +324,6 @@
                 perror("read");
                 exit(EXIT_FAILURE);
             }
-
             otPlatUartReceived(s_receive_buffer, (uint16_t)rval);
         }
 
@@ -262,19 +331,34 @@
         {
             rval = write(s_out_fd, s_write_buffer, s_write_length);
 
-            if (rval <= 0)
+            if (rval >= 0)
+            {
+                s_write_buffer += (uint16_t)rval;
+                s_write_length -= (uint16_t)rval;
+
+                if (s_write_length == 0)
+                {
+                    otPlatUartSendDone();
+                }
+            }
+            else if (errno != EINTR)
             {
                 perror("write");
                 exit(EXIT_FAILURE);
             }
-
-            s_write_buffer += (uint16_t)rval;
-            s_write_length -= (uint16_t)rval;
-
-            if (s_write_length == 0)
-            {
-                otPlatUartSendDone();
-            }
         }
     }
 }
+
+/**
+ *  The weak stubs functions definition.
+ */
+OT_TOOL_WEAK void otPlatUartSendDone(void)
+{
+}
+
+OT_TOOL_WEAK void otPlatUartReceived(const uint8_t *aBuf, uint16_t aBufLength)
+{
+    (void)aBuf;
+    (void)aBufLength;
+}
diff --git a/examples/platforms/gp712/uart-socket.c b/examples/platforms/gp712/uart-socket.c
deleted file mode 100644
index b516581..0000000
--- a/examples/platforms/gp712/uart-socket.c
+++ /dev/null
@@ -1,340 +0,0 @@
-/*
- *  Copyright (c) 2016-2017, The OpenThread Authors.
- *  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
- *   This file implements the OpenThread platform abstraction for cli over ip socket communication.
- *
- */
-
-#include "alarm_qorvo.h"
-#include "platform_qorvo.h"
-#include "uart_qorvo.h"
-
-#include <assert.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <poll.h>
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <termios.h>
-#include <unistd.h>
-
-#include <netdb.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <sys/socket.h>
-#include <sys/types.h>
-
-#include <pthread.h>
-
-#include <openthread/config.h>
-
-#include <common/code_utils.hpp>
-#include <openthread/platform/uart.h>
-
-#include "utils/code_utils.h"
-
-#define BUFFER_MAX_SIZE 255
-#define SOCKET_PORT 9190
-#define SOCKET_WRITE(socketInfo, buf, length) \
-    sendto(socketInfo.socketId, (const char *)buf, length, 0, &socketInfo.addr, sizeof(socketInfo.addr))
-#define SOCKET_READ(socketId, buf, length) recv(socketId, buf, length, 0)
-
-typedef struct
-{
-    uint32_t        socketId;
-    bool            isValid;
-    struct sockaddr addr;
-    pthread_t       rfReadThread;
-} PlatSocket_t;
-
-PlatSocket_t PlatSocketConnection;
-int          PlatSocketPipeFd[2];
-int          PlatServerSocketId;
-
-void PlatSocketRxNewConn(uint8_t id);
-void PlatSocketInit(void);
-void PlatSocketDeInit(void);
-int  PlatSocketTxData(uint16_t length, uint8_t *pData, uint32_t socketId);
-
-#define PLAT_UART_MAX_CHAR 1024
-
-uint32_t PlatSocketId = 0;
-
-void PlatSocketSendInput(void *buffer)
-{
-    uint8_t  len = 0;
-    uint8_t *buf = (uint8_t *)buffer;
-    len          = strlen((char *)buf);
-    otPlatUartReceived((uint8_t *)buf, (uint16_t)len);
-    free(buf);
-    buf = 0;
-    len = 0;
-}
-
-void PlatSocketRx(uint16_t length, const char *buffer, uint32_t socketId)
-{
-    uint8_t *buf = 0;
-    PlatSocketId = socketId;
-
-    if (length > 0)
-    {
-        buf = malloc(length + 2);
-        memcpy(buf, buffer, length);
-        buf[length]     = '\n';
-        buf[length + 1] = 0;
-        qorvoAlarmScheduleEventArg(0, PlatSocketSendInput, (void *)buf);
-    }
-}
-
-void PlatSocketClose(uint32_t socketId)
-{
-    OT_UNUSED_VARIABLE(socketId);
-}
-
-otError otPlatUartEnable(void)
-{
-    PlatSocketInit();
-    return OT_ERROR_NONE;
-}
-
-otError otPlatUartDisable(void)
-{
-    PlatSocketDeInit();
-    return OT_ERROR_NONE;
-}
-
-otError otPlatUartSend(const uint8_t *aBuf, uint16_t aBufLength)
-{
-    otError error = OT_ERROR_NONE;
-    char    localbuf[PLAT_UART_MAX_CHAR];
-
-    memcpy(localbuf, aBuf, aBufLength);
-    localbuf[aBufLength] = 0;
-    printf("%s", localbuf);
-
-    if (PlatSocketId)
-    {
-        PlatSocketTxData(aBufLength, (uint8_t *)aBuf, PlatSocketId);
-    }
-
-    otPlatUartSendDone();
-    return error;
-}
-
-otError otPlatUartFlush(void)
-{
-    return OT_ERROR_NOT_IMPLEMENTED;
-}
-
-void platformUartInit(void)
-{
-}
-
-void platformUartProcess(void)
-{
-}
-
-int PlatSocketListenForClients()
-{
-    // Setup server side socket
-    int                sockfd;
-    struct sockaddr_in serv_addr;
-    uint32_t           flag = 1;
-    int                ret;
-
-    sockfd = socket(AF_INET, SOCK_STREAM, 0);
-    otEXPECT_ACTION(sockfd >= 0, sockfd = -1);
-
-    // disable Nagle's algorithm to avoid long latency
-    setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag));
-    // allow reuse of the same address
-    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char *)&flag, sizeof(flag));
-    memset(&serv_addr, 0, sizeof(serv_addr));
-
-    serv_addr.sin_addr.s_addr = INADDR_ANY;
-    serv_addr.sin_family      = AF_INET;
-    serv_addr.sin_port        = htons(SOCKET_PORT);
-    ret                       = bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
-    otEXPECT_ACTION(ret >= 0, close(sockfd); sockfd = -1);
-    ret = listen(sockfd, 10);
-    otEXPECT_ACTION(ret != -1, exit(1));
-exit:
-    return sockfd;
-}
-
-void PlatSocketRxSignaled(uint8_t id)
-{
-    OT_UNUSED_VARIABLE(id);
-
-    // Dummy callback function to flush pipe
-    uint8_t readChar;
-    // Remove trigger byte from pipe
-    read(PlatSocketPipeFd[0], &readChar, 1);
-}
-
-void *PlatSocketReadThread(void *pClientSocket)
-{
-    char          buffer[BUFFER_MAX_SIZE];
-    PlatSocket_t *clientSocket = ((PlatSocket_t *)pClientSocket);
-
-    memset(buffer, 0, BUFFER_MAX_SIZE);
-
-    while (1)
-    {
-        int readLen = SOCKET_READ(clientSocket->socketId, buffer, BUFFER_MAX_SIZE);
-
-        if (readLen < 0)
-        {
-            perror("Reading socket");
-            break;
-        }
-        else
-        {
-            if (readLen == 0)
-            {
-                break;
-            }
-
-            {
-                uint8_t someByte = 0x12; // No functional use  only using pipe to kick main thread
-
-                PlatSocketRx(readLen, buffer, clientSocket->socketId);
-
-                write(PlatSocketPipeFd[1], &someByte, 1); //[1] = write fd
-            }
-        }
-    }
-
-    clientSocket->isValid = 0;
-    qorvoPlatUnRegisterPollFunction(clientSocket->socketId);
-    close(clientSocket->socketId);
-
-    PlatSocketClose(clientSocket->socketId);
-
-    return NULL;
-}
-
-void PlatSocketRxNewConn(uint8_t id)
-{
-    // Find first non-valid client in list - add here
-    if (PlatSocketConnection.isValid == 0)
-    {
-        // Add new client to client list
-        socklen_t len;
-        len        = sizeof(PlatSocketConnection.addr);
-        int retval = accept(id, (struct sockaddr *)&PlatSocketConnection.addr, (socklen_t *)&len);
-
-        if (retval >= 0)
-        {
-            int retErr;
-            PlatSocketConnection.socketId = retval;
-            retErr =
-                pthread_create(&PlatSocketConnection.rfReadThread, NULL, PlatSocketReadThread, &PlatSocketConnection);
-
-            if (retErr)
-            {
-                close(PlatSocketConnection.socketId);
-            }
-            else
-            {
-                PlatSocketConnection.isValid = 1;
-            }
-        }
-    }
-    else
-    {
-        int tempfd;
-        tempfd = accept(id, (struct sockaddr *)NULL, NULL);
-
-        if (tempfd >= 0)
-        {
-            close(tempfd);
-        }
-    }
-}
-
-/*****************************************************************************
- *                    Public Function Definitions
- *****************************************************************************/
-
-void PlatSocketInit(void)
-{
-    memset(&PlatSocketConnection, 0, sizeof(PlatSocketConnection));
-
-    // in case we are a server, setup listening for client
-    PlatServerSocketId = PlatSocketListenForClients();
-    qorvoPlatRegisterPollFunction(PlatServerSocketId, PlatSocketRxNewConn);
-
-    // hack
-    pipe(PlatSocketPipeFd);
-    qorvoPlatRegisterPollFunction(PlatSocketPipeFd[0], PlatSocketRxSignaled);
-}
-
-void platformUartRestore(void)
-{
-    PlatSocketDeInit();
-}
-
-void PlatSocketDeInit(void)
-{
-    qorvoPlatUnRegisterPollFunction(PlatServerSocketId);
-    close(PlatServerSocketId);
-    qorvoPlatUnRegisterPollFunction(PlatSocketPipeFd[0]);
-    close(PlatSocketPipeFd[0]);
-    close(PlatSocketPipeFd[1]);
-    close(PlatSocketConnection.socketId);
-}
-
-int PlatSocketTxData(uint16_t length, uint8_t *pData, uint32_t socketId)
-{
-    int result = -1;
-
-    // All sockets
-    if (PlatSocketConnection.isValid)
-    {
-        if (PlatSocketConnection.socketId == socketId)
-        {
-            if (SOCKET_WRITE(PlatSocketConnection, (const char *)pData, length) < 0)
-            {
-                perror("TxSocket: Error Writing to client");
-                close(PlatSocketConnection.socketId);
-                PlatSocketConnection.isValid = 0;
-            }
-            else
-            {
-                result = 0;
-            }
-        }
-    }
-
-    return result;
-}
diff --git a/examples/platforms/gp712/uart_qorvo.h b/examples/platforms/gp712/uart_qorvo.h
index 3e348bd..ab933d0 100644
--- a/examples/platforms/gp712/uart_qorvo.h
+++ b/examples/platforms/gp712/uart_qorvo.h
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2017, The OpenThread Authors.
+ *  Copyright (c) 2019, The OpenThread Authors.
  *  All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
@@ -36,18 +36,7 @@
 #define UART_QORVO_H_
 
 #include <stdint.h>
-
-/**
- * This function initializes the UART driver.
- *
- */
-void qorvoUartInit(void);
-
-/**
- * This function performs UART driver processing.
- *
- */
-void qorvoUartProcess(void);
+#include <openthread/platform/logging.h>
 
 /**
  * This function enables the UART driver.
@@ -85,4 +74,15 @@
  */
 void qorvoUartSendOutput(const uint8_t *aBuf, uint16_t aBufLength);
 
+/**
+ * Function which transmits logging via the qorvo platform.
+ *
+ * @param[in]   aLogLevel   The severity level of logging.
+ * @param[in]   aLogRegion  The code region where the logging originated from.
+ * @param[in]   aFormat     The format strings.
+ * @param[in]   varargs     The arguments for the format string.
+ *
+ */
+void qorvoUartLog(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aFormat, ...);
+
 #endif // UART_QORVO_H_
diff --git a/examples/platforms/k32w/jn5189/openthread-core-jn5189-config.h b/examples/platforms/k32w/jn5189/openthread-core-jn5189-config.h
index 51f7d27..0d01e7d 100755
--- a/examples/platforms/k32w/jn5189/openthread-core-jn5189-config.h
+++ b/examples/platforms/k32w/jn5189/openthread-core-jn5189-config.h
@@ -102,12 +102,12 @@
 /* TODO */
 
 /**
- * @def OPENTHREAD_CONFIG_NCP_UART_ENABLE
+ * @def OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
  *
- * Define to 1 to enable NCP UART support.
+ * Define to 1 to enable NCP HDLC support.
  *
  */
-#define OPENTHREAD_CONFIG_NCP_UART_ENABLE 1
+#define OPENTHREAD_CONFIG_NCP_HDLC_ENABLE 1
 
 /**
  * @def OPENTHREAD_SETTINGS_RAM
diff --git a/examples/platforms/k32w/k32w061/openthread-core-k32w061-config.h b/examples/platforms/k32w/k32w061/openthread-core-k32w061-config.h
index c6e877b..874bc8a 100755
--- a/examples/platforms/k32w/k32w061/openthread-core-k32w061-config.h
+++ b/examples/platforms/k32w/k32w061/openthread-core-k32w061-config.h
@@ -102,12 +102,12 @@
 /* TODO */
 
 /**
- * @def OPENTHREAD_CONFIG_NCP_UART_ENABLE
+ * @def OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
  *
- * Define to 1 to enable NCP UART support.
+ * Define to 1 to enable NCP HDLC support.
  *
  */
-#define OPENTHREAD_CONFIG_NCP_UART_ENABLE 1
+#define OPENTHREAD_CONFIG_NCP_HDLC_ENABLE 1
 
 /**
  * @def OPENTHREAD_SETTINGS_RAM
diff --git a/examples/platforms/k32w/src/logging.c b/examples/platforms/k32w/src/logging.c
index af55c2d..39a82c7 100755
--- a/examples/platforms/k32w/src/logging.c
+++ b/examples/platforms/k32w/src/logging.c
@@ -38,7 +38,6 @@
 #include <openthread/config.h>
 #include <openthread/platform/logging.h>
 #include <openthread/platform/toolchain.h>
-#include <openthread/platform/uart.h>
 
 #include "stdio.h"
 #include "string.h"
diff --git a/examples/platforms/k32w/src/radio.c b/examples/platforms/k32w/src/radio.c
index 1286d54..f95df25 100755
--- a/examples/platforms/k32w/src/radio.c
+++ b/examples/platforms/k32w/src/radio.c
@@ -203,10 +203,11 @@
                            E_MMAC_RX_ALIGN_NORMAL | E_MMAC_RX_USE_AUTO_ACK | E_MMAC_RX_NO_MALFORMED |
                            E_MMAC_RX_NO_FCS_ERROR | E_MMAC_RX_ADDRESS_MATCH;
 
-tsRxFrameFormat        sTxMacFrame;                      /* TX Frame */
-static tsRxFrameFormat sRxAckFrame;                      /* Frame used for keeping the ACK */
-static otRadioFrame    sRxOtFrame;                       /* Used for TX/RX frame conversion */
-static uint8           sRxData[OT_RADIO_FRAME_MAX_SIZE]; /* mPsdu buffer for sRxOtFrame */
+tsRxFrameFormat         sTxMacFrame;                      /* TX Frame */
+static tsRxFrameFormat  sRxAckFrame;                      /* Frame used for keeping the ACK */
+static otRadioFrame     sRxOtFrame;                       /* Used for TX/RX frame conversion */
+static uint8            sRxData[OT_RADIO_FRAME_MAX_SIZE]; /* mPsdu buffer for sRxOtFrame */
+static tsRxFrameFormat *pLastRxFrame;
 
 static bool         sRadioInitForLp    = FALSE;
 static bool         sPromiscuousEnable = FALSE;
@@ -1277,13 +1278,13 @@
     {
         if ((pRxFrame = K32WGetFrame(sRxFrame, &sRxFrameIndex)) != NULL)
         {
-            vMMAC_SetRxFrame(pRxFrame);
-            K32WRestartRx();
+            pLastRxFrame = pRxFrame;
+            vMMAC_StartMacReceive(&pRxFrame->sFrameBody, sRxOpt);
         }
     }
     else
     {
-        K32WRestartRx();
+        vMMAC_StartMacReceive(&pLastRxFrame->sFrameBody, sRxOpt);
     }
 }
 
diff --git a/examples/platforms/k32w/src/system.c b/examples/platforms/k32w/src/system.c
index 979014e..9843719 100644
--- a/examples/platforms/k32w/src/system.c
+++ b/examples/platforms/k32w/src/system.c
@@ -32,9 +32,9 @@
  *
  */
 #include "board.h"
+#include "clock_config.h"
 #include "pin_mux.h"
 #include "platform-k32w.h"
-#include "openthread/platform/uart.h"
 
 #include <stdbool.h>
 #include <stdint.h>
diff --git a/examples/platforms/k32w/src/uart.c b/examples/platforms/k32w/src/uart.c
index 53ad84d..55493fa 100755
--- a/examples/platforms/k32w/src/uart.c
+++ b/examples/platforms/k32w/src/uart.c
@@ -40,7 +40,7 @@
 
 /* Openthread general includes */
 #include <utils/code_utils.h>
-#include "openthread/platform/uart.h"
+#include <utils/uart.h>
 
 #if USE_RTOS
 #include "UART_Serial_Adapter.h"
diff --git a/examples/platforms/kw41z/README.md b/examples/platforms/kw41z/README.md
index 310e84d..7597699 100644
--- a/examples/platforms/kw41z/README.md
+++ b/examples/platforms/kw41z/README.md
@@ -125,4 +125,4 @@
 
 For a list of all available commands, visit [OpenThread CLI Reference README.md][cli].
 
-[cli]: https://github.com/openthread/openthread/blob/master/src/cli/README.md
+[cli]: https://github.com/openthread/openthread/blob/main/src/cli/README.md
diff --git a/examples/platforms/kw41z/openthread-core-kw41z-config.h b/examples/platforms/kw41z/openthread-core-kw41z-config.h
index 923dc03..7e24b8a 100644
--- a/examples/platforms/kw41z/openthread-core-kw41z-config.h
+++ b/examples/platforms/kw41z/openthread-core-kw41z-config.h
@@ -95,11 +95,11 @@
 #define OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE 0
 
 /**
- * @def OPENTHREAD_CONFIG_NCP_UART_ENABLE
+ * @def OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
  *
- * Define to 1 to enable NCP UART support.
+ * Define to 1 to enable NCP HDLC support.
  *
  */
-#define OPENTHREAD_CONFIG_NCP_UART_ENABLE 1
+#define OPENTHREAD_CONFIG_NCP_HDLC_ENABLE 1
 
 #endif // OPENTHREAD_CORE_KW41Z_CONFIG_H_
diff --git a/examples/platforms/kw41z/system.c b/examples/platforms/kw41z/system.c
index 8e0dc27..777bb38 100644
--- a/examples/platforms/kw41z/system.c
+++ b/examples/platforms/kw41z/system.c
@@ -38,7 +38,8 @@
 #include "fsl_port.h"
 #include "platform-kw41z.h"
 #include <stdint.h>
-#include "openthread/platform/uart.h"
+
+#include "utils/uart.h"
 
 otInstance *sInstance;
 
diff --git a/examples/platforms/kw41z/uart.c b/examples/platforms/kw41z/uart.c
index 226e04e..9b59057 100644
--- a/examples/platforms/kw41z/uart.c
+++ b/examples/platforms/kw41z/uart.c
@@ -37,7 +37,7 @@
 #include <stdint.h>
 
 #include <utils/code_utils.h>
-#include "openthread/platform/uart.h"
+#include <utils/uart.h>
 
 #include "fsl_clock.h"
 #include "fsl_lpuart.h"
diff --git a/examples/platforms/nrf528xx/CMakeLists.txt b/examples/platforms/nrf528xx/CMakeLists.txt
new file mode 100644
index 0000000..8b474e0
--- /dev/null
+++ b/examples/platforms/nrf528xx/CMakeLists.txt
@@ -0,0 +1,103 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+if(OT_NCP_SPI)
+    list(APPEND OT_PLATFORM_DEFINES "SPIS_AS_SERIAL_TRANSPORT=1")
+else()
+    option(OT_USB "enable nrf USB as serial transport support")
+    if(OT_USB)
+        list(APPEND OT_PLATFORM_DEFINES "USB_CDC_AS_SERIAL_TRANSPORT=1")
+    else()
+        list(APPEND OT_PLATFORM_DEFINES "UART_AS_SERIAL_TRANSPORT=1")
+    endif()
+endif()
+
+set(NRF_COMM_SOURCES
+    src/alarm.c
+    src/diag.c
+    src/entropy.c
+    src/fem.c
+    src/flash.c
+    src/logging.c
+    src/misc.c
+    src/radio.c
+    src/system.c
+    src/temp.c
+)
+
+set(NRF_TRANSPORT_SOURCES
+    src/transport/spi-slave.c
+    src/transport/transport.c
+    src/transport/uart.c
+    src/transport/usb-cdc-uart.c
+)
+
+set(NRF_SINGLEPHY_SOURCES
+    src/flash_nosd.c
+)
+
+set(NRF_SOFTDEVICE_SOURCES
+    src/flash_sd.c
+    src/softdevice.c
+)
+
+set(NRF_INCLUDES
+    ${CMAKE_CURRENT_SOURCE_DIR}/src
+    ${PROJECT_SOURCE_DIR}/examples/platforms
+    ${PROJECT_SOURCE_DIR}/src/core
+)
+
+if(OT_PLATFORM STREQUAL "nrf52811")
+    set(NRF_TRANSPORT_SOURCES
+        src/transport/spi-slave.c
+        src/transport/transport.c
+        src/transport/uart.c
+    )
+    set(NRF52811_3RD_LIBS
+        nordicsemi-nrf52811-radio-driver
+        nordicsemi-nrf52811-sdk
+        jlinkrtt
+    )
+    include(nrf52811/nrf52811.cmake)
+elseif(OT_PLATFORM STREQUAL "nrf52833")
+    set(NRF52833_3RD_LIBS
+        nordicsemi-nrf52833-radio-driver
+        nordicsemi-nrf52833-radio-driver-softdevice
+        nordicsemi-nrf52833-sdk
+        jlinkrtt
+    )
+    include(nrf52833/nrf52833.cmake)
+elseif(OT_PLATFORM STREQUAL "nrf52840")
+    set(NRF52840_3RD_LIBS
+        nordicsemi-nrf52840-radio-driver
+        nordicsemi-nrf52840-radio-driver-softdevice
+        nordicsemi-nrf52840-sdk
+        jlinkrtt
+    )
+    include(nrf52840/nrf52840.cmake)
+endif()
diff --git a/examples/platforms/nrf528xx/nrf52811/Makefile.am b/examples/platforms/nrf528xx/nrf52811/Makefile.am
index 386e152..43803ba 100644
--- a/examples/platforms/nrf528xx/nrf52811/Makefile.am
+++ b/examples/platforms/nrf528xx/nrf52811/Makefile.am
@@ -64,7 +64,6 @@
     -I$(top_srcdir)/third_party/NordicSemiconductor/drivers/radio/rsch/raal                                  \
     -I$(top_srcdir)/third_party/NordicSemiconductor/drivers/radio/platform/lp_timer                          \
     -I$(top_srcdir)/third_party/NordicSemiconductor/drivers/radio/platform/temperature                       \
-    -Wno-unused-parameter                                                                                    \
     $(NULL)
 
 # Only reference the SDK header files included in third_party/NordicSemiconductor
@@ -99,7 +98,6 @@
     -I$(top_srcdir)/third_party/NordicSemiconductor/nrfx/drivers/include                                     \
     -I$(top_srcdir)/third_party/NordicSemiconductor/nrfx/mdk                                                 \
     -I$(top_srcdir)/third_party/NordicSemiconductor/nrfx/soc                                                 \
-    -Wno-unused-parameter                                                                                    \
     $(NULL)
 endif
 
diff --git a/examples/platforms/nrf528xx/nrf52811/arm-none-eabi.cmake b/examples/platforms/nrf528xx/nrf52811/arm-none-eabi.cmake
new file mode 100644
index 0000000..a2e520a
--- /dev/null
+++ b/examples/platforms/nrf528xx/nrf52811/arm-none-eabi.cmake
@@ -0,0 +1,56 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+set(CMAKE_SYSTEM_NAME              Generic)
+set(CMAKE_SYSTEM_PROCESSOR         ARM)
+
+set(CMAKE_C_COMPILER               arm-none-eabi-gcc)
+set(CMAKE_CXX_COMPILER             arm-none-eabi-g++)
+set(CMAKE_ASM_COMPILER             arm-none-eabi-as)
+set(CMAKE_RANLIB                   arm-none-eabi-ranlib)
+
+execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE COMPILER_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+set(COMMON_C_FLAGS                 "-mcpu=cortex-m4 -mfloat-abi=soft -mthumb -mabi=aapcs -fdata-sections -ffunction-sections")
+
+if(COMPILER_VERSION VERSION_GREATER_EQUAL 7)
+    set(COMMON_C_FLAGS             "${COMMON_C_FLAGS} -Wno-expansion-to-defined")
+endif()
+
+set(CMAKE_C_FLAGS_INIT             "${COMMON_C_FLAGS} -std=gnu99")
+set(CMAKE_CXX_FLAGS_INIT           "${COMMON_C_FLAGS} -fno-exceptions -fno-rtti")
+set(CMAKE_ASM_FLAGS_INIT           "${COMMON_C_FLAGS} -x assembler-with-cpp")
+set(CMAKE_EXE_LINKER_FLAGS_INIT    "${COMMON_C_FLAGS} -specs=nano.specs -specs=nosys.specs")
+
+set(CMAKE_C_FLAGS_DEBUG            "-Og -g")
+set(CMAKE_CXX_FLAGS_DEBUG          "-Og -g")
+set(CMAKE_ASM_FLAGS_DEBUG          "-g")
+
+set(CMAKE_C_FLAGS_RELEASE          "-Os")
+set(CMAKE_CXX_FLAGS_RELEASE        "-Os")
+set(CMAKE_ASM_FLAGS_RELEASE        "")
diff --git a/examples/platforms/nrf528xx/nrf52811/nrf52811.cmake b/examples/platforms/nrf528xx/nrf52811/nrf52811.cmake
new file mode 100644
index 0000000..ffe837e
--- /dev/null
+++ b/examples/platforms/nrf528xx/nrf52811/nrf52811.cmake
@@ -0,0 +1,179 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+set(OT_PLATFORM_LIB "openthread-nrf52811" "openthread-nrf52811-transport" PARENT_SCOPE)
+
+if(NOT OT_CONFIG)
+    set(OT_CONFIG "${CMAKE_CURRENT_SOURCE_DIR}/nrf52811/openthread-core-nrf52811-config.h")
+    set(OT_CONFIG ${OT_CONFIG} PARENT_SCOPE)
+endif()
+
+set(LD_FILE "${CMAKE_CURRENT_SOURCE_DIR}/nrf52811/nrf52811.ld")
+
+set(COMM_FLAGS
+    -DDISABLE_CC310=1
+    -DCONFIG_GPIO_AS_PINRESET
+    -DNRF52811_XXAA
+    -DUSE_APP_CONFIG=1
+    -Wno-unused-parameter
+    -Wno-expansion-to-defined
+)
+
+list(APPEND OT_PLATFORM_DEFINES
+    "OPENTHREAD_CORE_CONFIG_PLATFORM_CHECK_FILE=\"openthread-core-nrf52811-config-check.h\""
+)
+
+list(APPEND OT_PUBLIC_INCLUDES
+    "${PROJECT_SOURCE_DIR}/third_party/NordicSemiconductor/libraries/nrf_security/mbedtls_plat_config"
+    "${PROJECT_SOURCE_DIR}/third_party/NordicSemiconductor/nrfx/mdk"
+    "${PROJECT_SOURCE_DIR}/third_party/NordicSemiconductor/cmsis"
+)
+
+set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE)
+target_compile_definitions(ot-config INTERFACE
+    "MBEDTLS_USER_CONFIG_FILE=\"nrf52811-mbedtls-config.h\""
+)
+set(OT_PUBLIC_INCLUDES ${OT_PUBLIC_INCLUDES} PARENT_SCOPE)
+
+if(OT_CFLAGS MATCHES "-pedantic-errors")
+    string(REPLACE "-pedantic-errors" "" OT_CFLAGS "${OT_CFLAGS}")
+endif()
+
+list(APPEND OT_PLATFORM_DEFINES "OPENTHREAD_PROJECT_CORE_CONFIG_FILE=\"${OT_CONFIG}\"")
+
+add_library(openthread-nrf52811
+    ${NRF_COMM_SOURCES}
+    ${NRF_SINGLEPHY_SOURCES}
+    $<TARGET_OBJECTS:openthread-platform-utils>
+)
+
+add_library(openthread-nrf52811-transport
+    ${NRF_TRANSPORT_SOURCES}
+)
+
+add_library(openthread-nrf52811-sdk
+    ${NRF_COMM_SOURCES}
+    ${NRF_SINGLEPHY_SOURCES}
+    $<TARGET_OBJECTS:openthread-platform-utils>
+)
+
+set_target_properties(openthread-nrf52811 openthread-nrf52811-transport openthread-nrf52811-sdk
+    PROPERTIES
+        C_STANDARD 99
+        CXX_STANDARD 11
+)
+
+target_link_libraries(openthread-nrf52811
+    PUBLIC
+        ${OT_MBEDTLS}
+        ${NRF52811_3RD_LIBS}
+        -T${LD_FILE}
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+    PRIVATE
+        ot-config
+)
+
+target_link_libraries(openthread-nrf52811-transport
+    PUBLIC
+        ${OT_MBEDTLS}
+        -T${LD_FILE}
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+    PRIVATE
+        nordicsemi-nrf52811-sdk
+        ot-config
+)
+
+target_link_libraries(openthread-nrf52811-sdk
+    PUBLIC
+        ${OT_MBEDTLS}
+        ${NRF52811_3RD_LIBS}
+        -T${LD_FILE}
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+    PRIVATE
+        ot-config
+)
+
+target_compile_definitions(openthread-nrf52811
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_definitions(openthread-nrf52811-transport
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_definitions(openthread-nrf52811-sdk
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_options(openthread-nrf52811
+    PRIVATE
+        ${OT_CFLAGS}
+        ${COMM_FLAGS}
+        -DRAAL_SINGLE_PHY=1
+)
+
+target_compile_options(openthread-nrf52811-transport
+    PRIVATE
+        ${OT_CFLAGS}
+        ${COMM_FLAGS}
+)
+
+target_compile_options(openthread-nrf52811-sdk
+    PRIVATE
+        ${OT_CFLAGS}
+        ${COMM_FLAGS}
+        -DRAAL_SINGLE_PHY=1
+)
+
+target_include_directories(openthread-nrf52811
+    PRIVATE
+        ${CMAKE_CURRENT_SOURCE_DIR}/nrf52811
+        ${NRF_INCLUDES}
+        ${OT_PUBLIC_INCLUDES}
+)
+
+target_include_directories(openthread-nrf52811-transport
+    PRIVATE
+        ${CMAKE_CURRENT_SOURCE_DIR}/nrf52811
+        ${NRF_INCLUDES}
+        ${OT_PUBLIC_INCLUDES}
+)
+
+target_include_directories(openthread-nrf52811-sdk
+    PRIVATE
+        ${CMAKE_CURRENT_SOURCE_DIR}/nrf52811
+        ${NRF_INCLUDES}
+        ${OT_PUBLIC_INCLUDES}
+)
+
diff --git a/examples/platforms/nrf528xx/nrf52811/platform-config.h b/examples/platforms/nrf528xx/nrf52811/platform-config.h
index e98954a..660a352 100644
--- a/examples/platforms/nrf528xx/nrf52811/platform-config.h
+++ b/examples/platforms/nrf528xx/nrf52811/platform-config.h
@@ -55,7 +55,7 @@
  *
  */
 #ifndef RTC_INSTANCE
-#define RTC_INSTANCE NRF_RTC0
+#define RTC_INSTANCE NRF_RTC1
 #endif
 
 /**
@@ -65,7 +65,7 @@
  *
  */
 #ifndef RTC_IRQ_HANDLER
-#define RTC_IRQ_HANDLER RTC0_IRQHandler
+#define RTC_IRQ_HANDLER RTC1_IRQHandler
 #endif
 
 /**
@@ -75,7 +75,7 @@
  *
  */
 #ifndef RTC_IRQN
-#define RTC_IRQN RTC0_IRQn
+#define RTC_IRQN RTC1_IRQn
 #endif
 
 /**
diff --git a/examples/platforms/nrf528xx/nrf52833/Makefile.am b/examples/platforms/nrf528xx/nrf52833/Makefile.am
index eb59eb5..21ee117 100644
--- a/examples/platforms/nrf528xx/nrf52833/Makefile.am
+++ b/examples/platforms/nrf528xx/nrf52833/Makefile.am
@@ -66,7 +66,6 @@
     -I$(top_srcdir)/third_party/NordicSemiconductor/drivers/radio/rsch/raal/softdevice                       \
     -I$(top_srcdir)/third_party/NordicSemiconductor/drivers/radio/platform/lp_timer                          \
     -I$(top_srcdir)/third_party/NordicSemiconductor/drivers/radio/platform/temperature                       \
-    -Wno-unused-parameter                                                                                    \
     $(NULL)
 
 # Only reference the SDK header files included in third_party/NordicSemiconductor
@@ -108,7 +107,6 @@
     -I$(top_srcdir)/third_party/NordicSemiconductor/nrfx/soc                                                 \
     -I$(top_srcdir)/third_party/NordicSemiconductor/softdevice/s140/headers                                  \
     -I$(top_srcdir)/third_party/NordicSemiconductor/softdevice/s140/headers/nrf52                            \
-    -Wno-unused-parameter                                                                                    \
     $(NULL)
 endif
 
@@ -144,6 +142,7 @@
 SOFTDEVICE_CPPFLAGS                                                                                        = \
     -DSOFTDEVICE_PRESENT                                                                                     \
     -DS140                                                                                                   \
+    -Wno-unused-parameter                                                                                    \
     $(NULL)
 
 PLATFORM_SOURCES                                                                                           = \
diff --git a/examples/platforms/nrf528xx/nrf52833/arm-none-eabi.cmake b/examples/platforms/nrf528xx/nrf52833/arm-none-eabi.cmake
new file mode 100644
index 0000000..3147abe
--- /dev/null
+++ b/examples/platforms/nrf528xx/nrf52833/arm-none-eabi.cmake
@@ -0,0 +1,56 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+set(CMAKE_SYSTEM_NAME              Generic)
+set(CMAKE_SYSTEM_PROCESSOR         ARM)
+
+set(CMAKE_C_COMPILER               arm-none-eabi-gcc)
+set(CMAKE_CXX_COMPILER             arm-none-eabi-g++)
+set(CMAKE_ASM_COMPILER             arm-none-eabi-as)
+set(CMAKE_RANLIB                   arm-none-eabi-ranlib)
+
+execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE COMPILER_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+set(COMMON_C_FLAGS                 "-mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -mthumb -mabi=aapcs -fdata-sections -ffunction-sections")
+
+if(COMPILER_VERSION VERSION_GREATER_EQUAL 7)
+    set(COMMON_C_FLAGS             "${COMMON_C_FLAGS} -Wno-expansion-to-defined")
+endif()
+
+set(CMAKE_C_FLAGS_INIT             "${COMMON_C_FLAGS} -std=gnu99")
+set(CMAKE_CXX_FLAGS_INIT           "${COMMON_C_FLAGS} -fno-exceptions -fno-rtti")
+set(CMAKE_ASM_FLAGS_INIT           "${COMMON_C_FLAGS} -x assembler-with-cpp")
+set(CMAKE_EXE_LINKER_FLAGS_INIT    "${COMMON_C_FLAGS} -specs=nano.specs -specs=nosys.specs")
+
+set(CMAKE_C_FLAGS_DEBUG            "-Og -g")
+set(CMAKE_CXX_FLAGS_DEBUG          "-Og -g")
+set(CMAKE_ASM_FLAGS_DEBUG          "-g")
+
+set(CMAKE_C_FLAGS_RELEASE          "-Os")
+set(CMAKE_CXX_FLAGS_RELEASE        "-Os")
+set(CMAKE_ASM_FLAGS_RELEASE        "")
diff --git a/examples/platforms/nrf528xx/nrf52833/nrf52833.cmake b/examples/platforms/nrf528xx/nrf52833/nrf52833.cmake
new file mode 100644
index 0000000..659559a
--- /dev/null
+++ b/examples/platforms/nrf528xx/nrf52833/nrf52833.cmake
@@ -0,0 +1,220 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+set(OT_PLATFORM_LIB "openthread-nrf52833" "openthread-nrf52833-transport" PARENT_SCOPE)
+
+if(NOT OT_CONFIG)
+    set(OT_CONFIG "${CMAKE_CURRENT_SOURCE_DIR}/nrf52833/openthread-core-nrf52833-config.h")
+    set(OT_CONFIG ${OT_CONFIG} PARENT_SCOPE)
+endif()
+
+option(OT_BOOTLOADER "OT nrf bootloader type")
+if(OT_BOOTLOADER STREQUAL "USB")
+    list(APPEND OT_PLATFORM_DEFINES "APP_USBD_NRF_DFU_TRIGGER_ENABLED=1")
+    set(LD_FILE "${CMAKE_CURRENT_SOURCE_DIR}/nrf52833/nrf52833_bootloader_usb.ld")
+elseif(OT_BOOTLOADER STREQUAL "UART")
+    set(LD_FILE "${CMAKE_CURRENT_SOURCE_DIR}/nrf52833/nrf52833_bootloader_uart.ld")
+elseif(OT_BOOTLOADER STREQUAL "BLE")
+    set(LD_FILE "${CMAKE_CURRENT_SOURCE_DIR}/nrf52833/nrf52833_bootloader_ble.ld")
+else()
+    set(LD_FILE "${CMAKE_CURRENT_SOURCE_DIR}/nrf52833/nrf52833.ld")
+endif()
+
+list(APPEND OT_PLATFORM_DEFINES
+    "OPENTHREAD_CORE_CONFIG_PLATFORM_CHECK_FILE=\"openthread-core-nrf52833-config-check.h\""
+)
+list(APPEND OT_PUBLIC_INCLUDES
+    "${PROJECT_SOURCE_DIR}/third_party/NordicSemiconductor/libraries/nrf_security/mbedtls_plat_config"
+    "${PROJECT_SOURCE_DIR}/third_party/NordicSemiconductor/libraries/nrf_security/nrfx/mdk"
+     "${PROJECT_SOURCE_DIR}/third_party/NordicSemiconductor/libraries/cmsis"
+)
+
+set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE)
+target_compile_definitions(ot-config INTERFACE
+    "MBEDTLS_USER_CONFIG_FILE=\"nrf52833-mbedtls-config.h\""
+)
+set(OT_PUBLIC_INCLUDES ${OT_PUBLIC_INCLUDES} PARENT_SCOPE)
+
+set(COMM_FLAGS
+    -DDISABLE_CC310=1
+    -DCONFIG_GPIO_AS_PINRESET
+    -DNRF52833_XXAA
+    -DUSE_APP_CONFIG=1
+    -Wno-unused-parameter
+    -Wno-expansion-to-defined
+)
+if(OT_CFLAGS MATCHES "-pedantic-errors")
+    string(REPLACE "-pedantic-errors" "" OT_CFLAGS "${OT_CFLAGS}")
+endif()
+list(APPEND OT_PLATFORM_DEFINES "OPENTHREAD_PROJECT_CORE_CONFIG_FILE=\"${OT_CONFIG}\"")
+
+add_library(openthread-nrf52833
+    ${NRF_COMM_SOURCES}
+    ${NRF_SINGLEPHY_SOURCES}
+    $<TARGET_OBJECTS:openthread-platform-utils>
+)
+
+add_library(openthread-nrf52833-transport
+    ${NRF_TRANSPORT_SOURCES}
+)
+
+add_library(openthread-nrf52833-sdk
+    ${NRF_COMM_SOURCES}
+    ${NRF_SINGLEPHY_SOURCES}
+    $<TARGET_OBJECTS:openthread-platform-utils>
+)
+
+add_library(openthread-nrf52833-softdevice-sdk
+    ${NRF_COMM_SOURCES}
+    ${NRF_SOFTDEVICE_SOURCES}
+    $<TARGET_OBJECTS:openthread-platform-utils>
+)
+
+set_target_properties(openthread-nrf52833 openthread-nrf52833-transport openthread-nrf52833-sdk openthread-nrf52833-softdevice-sdk
+    PROPERTIES
+        C_STANDARD 99
+        CXX_STANDARD 11
+)
+
+target_link_libraries(openthread-nrf52833
+    PUBLIC
+        ${OT_MBEDTLS}
+        ${NRF52833_3RD_LIBS}
+        -T${LD_FILE}
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+    PRIVATE
+        ot-config
+)
+
+target_link_libraries(openthread-nrf52833-transport
+    PUBLIC
+        ${OT_MBEDTLS}
+        -T${LD_FILE}
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+    PRIVATE
+        nordicsemi-nrf52833-sdk
+        ot-config
+)
+
+target_link_libraries(openthread-nrf52833-sdk
+    PUBLIC
+        ${OT_MBEDTLS}
+        ${NRF52833_3RD_LIBS}
+        -T${LD_FILE}
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+    PRIVATE
+        ot-config
+)
+
+target_link_libraries(openthread-nrf52833-softdevice-sdk
+    PUBLIC
+        ${OT_MBEDTLS}
+        ${NRF52833_3RD_LIBS}
+        -T${LD_FILE}
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+    PRIVATE
+        ot-config
+)
+
+target_compile_definitions(openthread-nrf52833
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_definitions(openthread-nrf52833-transport
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_definitions(openthread-nrf52833-sdk
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_definitions(openthread-nrf52833-softdevice-sdk
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_options(openthread-nrf52833
+    PRIVATE
+        ${OT_CFLAGS}
+        ${COMM_FLAGS}
+)
+
+target_compile_options(openthread-nrf52833-transport
+    PRIVATE
+        ${OT_CFLAGS}
+        ${COMM_FLAGS}
+)
+
+target_compile_options(openthread-nrf52833-sdk
+    PRIVATE
+        ${OT_CFLAGS}
+        ${COMM_FLAGS}
+)
+
+target_compile_options(openthread-nrf52833-softdevice-sdk
+    PRIVATE
+        ${OT_CFLAGS}
+        ${COMM_FLAGS}
+        -DSOFTDEVICE_PRESENT
+        -DS140
+)
+
+target_include_directories(openthread-nrf52833
+    PRIVATE
+        ${CMAKE_CURRENT_SOURCE_DIR}/nrf52833
+        ${NRF_INCLUDES}
+        ${OT_PUBLIC_INCLUDES}
+)
+
+target_include_directories(openthread-nrf52833-transport
+    PRIVATE
+        ${CMAKE_CURRENT_SOURCE_DIR}/nrf52833
+        ${NRF_INCLUDES}
+        ${OT_PUBLIC_INCLUDES}
+)
+
+target_include_directories(openthread-nrf52833-sdk
+    PRIVATE
+        ${CMAKE_CURRENT_SOURCE_DIR}/nrf52833
+        ${NRF_INCLUDES}
+        ${OT_PUBLIC_INCLUDES}
+)
+
+target_include_directories(openthread-nrf52833-softdevice-sdk
+    PRIVATE
+        ${CMAKE_CURRENT_SOURCE_DIR}/nrf52833
+        ${NRF_INCLUDES}
+        ${OT_PUBLIC_INCLUDES}
+)
diff --git a/examples/platforms/nrf528xx/nrf52833/openthread-core-nrf52833-config.h b/examples/platforms/nrf528xx/nrf52833/openthread-core-nrf52833-config.h
index e037cd9..14e6991 100644
--- a/examples/platforms/nrf528xx/nrf52833/openthread-core-nrf52833-config.h
+++ b/examples/platforms/nrf528xx/nrf52833/openthread-core-nrf52833-config.h
@@ -221,7 +221,7 @@
  *
  */
 #ifndef OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS
-#define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS 2048
+#define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS 4000
 #endif
 
 /**
diff --git a/examples/platforms/nrf528xx/nrf52840/Makefile.am b/examples/platforms/nrf528xx/nrf52840/Makefile.am
index 6062fa4..863775b 100644
--- a/examples/platforms/nrf528xx/nrf52840/Makefile.am
+++ b/examples/platforms/nrf528xx/nrf52840/Makefile.am
@@ -66,7 +66,6 @@
     -I$(top_srcdir)/third_party/NordicSemiconductor/drivers/radio/rsch/raal/softdevice                       \
     -I$(top_srcdir)/third_party/NordicSemiconductor/drivers/radio/platform/lp_timer                          \
     -I$(top_srcdir)/third_party/NordicSemiconductor/drivers/radio/platform/temperature                       \
-    -Wno-unused-parameter                                                                                    \
     $(NULL)
 
 # Only reference the SDK header files included in third_party/NordicSemiconductor
@@ -108,7 +107,6 @@
     -I$(top_srcdir)/third_party/NordicSemiconductor/nrfx/soc                                                 \
     -I$(top_srcdir)/third_party/NordicSemiconductor/softdevice/s140/headers                                  \
     -I$(top_srcdir)/third_party/NordicSemiconductor/softdevice/s140/headers/nrf52                            \
-    -Wno-unused-parameter                                                                                    \
     $(NULL)
 endif
 
@@ -145,6 +143,7 @@
 SOFTDEVICE_CPPFLAGS                                                                                        = \
     -DSOFTDEVICE_PRESENT                                                                                     \
     -DS140                                                                                                   \
+    -Wno-unused-parameter                                                                                    \
     $(NULL)
 
 PLATFORM_SOURCES                                                                                           = \
@@ -154,7 +153,7 @@
 libopenthread_nrf52840_a_CPPFLAGS                                                                          = \
     $(COMMONCPPFLAGS)                                                                                        \
     $(SINGLEPHY_CPPFLAGS)                                                                                    \
-    -DPLATFORM_OPENTHREAD_VANILLA                                                                             \
+    -DPLATFORM_OPENTHREAD_VANILLA                                                                            \
     $(NULL)
 
 libopenthread_nrf52840_a_SOURCES                                                                           = \
diff --git a/examples/platforms/nrf528xx/nrf52840/README.md b/examples/platforms/nrf528xx/nrf52840/README.md
index b105709..4b8c5c8 100644
--- a/examples/platforms/nrf528xx/nrf52840/README.md
+++ b/examples/platforms/nrf528xx/nrf52840/README.md
@@ -135,11 +135,13 @@
 When building an external application with OpenThread libraries and CryptoCell 310 hardware acceleration, use the following configuration:
 
 - Crypto libraries:
-  - `third_party/NordicSemiconductor/libraries/nrf_security/lib/libmbedcrypto_cc310_backend.a`
-  - `third_party/NordicSemiconductor/libraries/nrf_security/lib/libmbedtls_tls_vanilla.a`
-  - `third_party/NordicSemiconductor/libraries/nrf_security/lib/libmbedtls_x509_vanilla.a`
-  - `third_party/NordicSemiconductor/libraries/nrf_security/lib/libmbedtls_base_vanilla.a`
-  - `third_party/NordicSemiconductor/libraries/nrf_security/lib/libnrf_cc310_platform_0.9.2.a`
+  - `/third_party/NordicSemiconductor/libraries/nrf_security/lib/libmbedcrypto_shared.a`
+  - `/third_party/NordicSemiconductor/libraries/nrf_security/lib/libmbedtls_tls_vanilla.a`
+  - `/third_party/NordicSemiconductor/libraries/nrf_security/lib/libmbedtls_x509_vanilla.a`
+  - `/third_party/NordicSemiconductor/libraries/nrf_security/lib/libmbedcrypto_cc3xx.a`
+  - `/third_party/NordicSemiconductor/libraries/nrf_security/lib/libnrf_cc310_platform_0.9.4.a`
+  - `/third_party/NordicSemiconductor/libraries/nrf_security/lib/libmbedcrypto_oberon.a`
+  - `/third_party/NordicSemiconductor/libraries/nrf_security/lib/libmbedtls_base_vanilla.a`
 - Include directories:
   - `third_party/NordicSemiconductor/libraries/nrf_security/mbedtls_platform_config`
   - `third_party/NordicSemiconductor/libraries/nrf_security/include`
diff --git a/examples/platforms/nrf528xx/nrf52840/arm-none-eabi.cmake b/examples/platforms/nrf528xx/nrf52840/arm-none-eabi.cmake
new file mode 100644
index 0000000..3147abe
--- /dev/null
+++ b/examples/platforms/nrf528xx/nrf52840/arm-none-eabi.cmake
@@ -0,0 +1,56 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+set(CMAKE_SYSTEM_NAME              Generic)
+set(CMAKE_SYSTEM_PROCESSOR         ARM)
+
+set(CMAKE_C_COMPILER               arm-none-eabi-gcc)
+set(CMAKE_CXX_COMPILER             arm-none-eabi-g++)
+set(CMAKE_ASM_COMPILER             arm-none-eabi-as)
+set(CMAKE_RANLIB                   arm-none-eabi-ranlib)
+
+execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE COMPILER_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
+
+set(COMMON_C_FLAGS                 "-mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -mthumb -mabi=aapcs -fdata-sections -ffunction-sections")
+
+if(COMPILER_VERSION VERSION_GREATER_EQUAL 7)
+    set(COMMON_C_FLAGS             "${COMMON_C_FLAGS} -Wno-expansion-to-defined")
+endif()
+
+set(CMAKE_C_FLAGS_INIT             "${COMMON_C_FLAGS} -std=gnu99")
+set(CMAKE_CXX_FLAGS_INIT           "${COMMON_C_FLAGS} -fno-exceptions -fno-rtti")
+set(CMAKE_ASM_FLAGS_INIT           "${COMMON_C_FLAGS} -x assembler-with-cpp")
+set(CMAKE_EXE_LINKER_FLAGS_INIT    "${COMMON_C_FLAGS} -specs=nano.specs -specs=nosys.specs")
+
+set(CMAKE_C_FLAGS_DEBUG            "-Og -g")
+set(CMAKE_CXX_FLAGS_DEBUG          "-Og -g")
+set(CMAKE_ASM_FLAGS_DEBUG          "-g")
+
+set(CMAKE_C_FLAGS_RELEASE          "-Os")
+set(CMAKE_CXX_FLAGS_RELEASE        "-Os")
+set(CMAKE_ASM_FLAGS_RELEASE        "")
diff --git a/examples/platforms/nrf528xx/nrf52840/nrf52840.cmake b/examples/platforms/nrf528xx/nrf52840/nrf52840.cmake
new file mode 100644
index 0000000..fcf1a65
--- /dev/null
+++ b/examples/platforms/nrf528xx/nrf52840/nrf52840.cmake
@@ -0,0 +1,233 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+set(OT_PLATFORM_LIB "openthread-nrf52840" "openthread-nrf52840-transport" PARENT_SCOPE)
+
+if(NOT OT_CONFIG)
+    set(OT_CONFIG "${CMAKE_CURRENT_SOURCE_DIR}/nrf52840/openthread-core-nrf52840-config.h")
+    set(OT_CONFIG ${OT_CONFIG} PARENT_SCOPE)
+endif()
+
+option(OT_BOOTLOADER "OT nrf bootloader type")
+if(OT_BOOTLOADER STREQUAL "USB")
+    list(APPEND OT_PLATFORM_DEFINES "APP_USBD_NRF_DFU_TRIGGER_ENABLED=1")
+    set(LD_FILE "${CMAKE_CURRENT_SOURCE_DIR}/nrf52840/nrf52840_bootloader_usb.ld")
+elseif(OT_BOOTLOADER STREQUAL "UART")
+    set(LD_FILE "${CMAKE_CURRENT_SOURCE_DIR}/nrf52840/nrf52840_bootloader_uart.ld")
+elseif(OT_BOOTLOADER STREQUAL "BLE")
+    set(LD_FILE "${CMAKE_CURRENT_SOURCE_DIR}/nrf52840/nrf52840_bootloader_ble.ld")
+else()
+    set(LD_FILE "${CMAKE_CURRENT_SOURCE_DIR}/nrf52840/nrf52840.ld")
+endif()
+
+list(APPEND OT_PLATFORM_DEFINES
+    "OPENTHREAD_CORE_CONFIG_PLATFORM_CHECK_FILE=\"openthread-core-nrf52840-config-check.h\""
+)
+if(NOT OT_EXTERNAL_MBEDTLS)
+    if(OT_MBEDTLS_THREADING)
+        list(APPEND OT_PLATFORM_DEFINES "MBEDTLS_THREADING_C")
+        list(APPEND OT_PLATFORM_DEFINES "MBEDTLS_THREADING_ALT")
+        list(APPEND OT_PUBLIC_INCLUDES
+            "${PROJECT_SOURCE_DIR}/third_party/NordicSemiconductor/libraries/nrf_security/include/software-only-threading"
+        )
+    endif()
+else()
+    list(APPEND OT_PLATFORM_DEFINES "MBEDTLS_CONFIG_FILE=\"nrf-config.h\"")
+    list(APPEND OT_PUBLIC_INCLUDES
+        "${PROJECT_SOURCE_DIR}/third_party/NordicSemiconductor/libraries/nrf_security/include"
+        "${PROJECT_SOURCE_DIR}/third_party/NordicSemiconductor/libraries/nrf_security/config"
+        "${PROJECT_SOURCE_DIR}/third_party/NordicSemiconductor/libraries/nrf_security/nrf_cc310_plat/include"
+    )
+endif()
+
+set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE)
+target_compile_definitions(ot-config INTERFACE
+    "MBEDTLS_USER_CONFIG_FILE=\"nrf52840-mbedtls-config.h\""
+)
+list(APPEND OT_PUBLIC_INCLUDES "${PROJECT_SOURCE_DIR}/third_party/NordicSemiconductor/libraries/nrf_security/mbedtls_plat_config")
+set(OT_PUBLIC_INCLUDES ${OT_PUBLIC_INCLUDES} PARENT_SCOPE)
+
+set(COMM_FLAGS
+    -DCONFIG_GPIO_AS_PINRESET
+    -DNRF52840_XXAA
+    -DUSE_APP_CONFIG=1
+    -Wno-unused-parameter
+    -Wno-expansion-to-defined
+)
+if(OT_CFLAGS MATCHES "-pedantic-errors")
+    string(REPLACE "-pedantic-errors" "" OT_CFLAGS "${OT_CFLAGS}")
+endif()
+list(APPEND OT_PLATFORM_DEFINES "OPENTHREAD_PROJECT_CORE_CONFIG_FILE=\"${OT_CONFIG}\"")
+
+add_library(openthread-nrf52840
+    ${NRF_COMM_SOURCES}
+    ${NRF_SINGLEPHY_SOURCES}
+    $<TARGET_OBJECTS:openthread-platform-utils>
+)
+
+add_library(openthread-nrf52840-transport
+    ${NRF_TRANSPORT_SOURCES}
+)
+
+add_library(openthread-nrf52840-sdk
+    ${NRF_COMM_SOURCES}
+    ${NRF_SINGLEPHY_SOURCES}
+    $<TARGET_OBJECTS:openthread-platform-utils>
+)
+
+add_library(openthread-nrf52840-softdevice-sdk
+    ${NRF_COMM_SOURCES}
+    ${NRF_SOFTDEVICE_SOURCES}
+    $<TARGET_OBJECTS:openthread-platform-utils>
+)
+
+set_target_properties(openthread-nrf52840 openthread-nrf52840-transport openthread-nrf52840-sdk openthread-nrf52840-softdevice-sdk
+    PROPERTIES
+        C_STANDARD 99
+        CXX_STANDARD 11
+)
+
+target_link_libraries(openthread-nrf52840
+    PUBLIC
+        ${OT_MBEDTLS}
+        ${NRF52840_3RD_LIBS}
+        -T${LD_FILE}
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+    PRIVATE
+        ot-config
+)
+
+target_link_libraries(openthread-nrf52840-transport
+    PUBLIC
+        ${OT_MBEDTLS}
+        -T${LD_FILE}
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+    PRIVATE
+        nordicsemi-nrf52840-sdk
+        ot-config
+)
+
+target_link_libraries(openthread-nrf52840-sdk
+    PUBLIC
+        ${OT_MBEDTLS}
+        ${NRF52840_3RD_LIBS}
+        -T${LD_FILE}
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+    PRIVATE
+        ot-config
+)
+
+target_link_libraries(openthread-nrf52840-softdevice-sdk
+    PUBLIC
+        ${OT_MBEDTLS}
+        ${NRF52840_3RD_LIBS}
+        -T${LD_FILE}
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+    PRIVATE
+        ot-config
+)
+
+target_compile_definitions(openthread-nrf52840
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_definitions(openthread-nrf52840-transport
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_definitions(openthread-nrf52840-sdk
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_definitions(openthread-nrf52840-softdevice-sdk
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_options(openthread-nrf52840
+    PRIVATE
+        ${OT_CFLAGS}
+        ${COMM_FLAGS}
+        -DPLATFORM_OPENTHREAD_VANILLA
+)
+
+target_compile_options(openthread-nrf52840-transport
+    PRIVATE
+        ${OT_CFLAGS}
+        ${COMM_FLAGS}
+        -DPLATFORM_OPENTHREAD_VANILLA
+)
+
+target_compile_options(openthread-nrf52840-sdk
+    PRIVATE
+        ${OT_CFLAGS}
+        ${COMM_FLAGS}
+)
+
+target_compile_options(openthread-nrf52840-softdevice-sdk
+    PRIVATE
+        ${OT_CFLAGS}
+        ${COMM_FLAGS}
+        -DSOFTDEVICE_PRESENT
+        -DS140
+)
+
+target_include_directories(openthread-nrf52840
+    PRIVATE
+        ${CMAKE_CURRENT_SOURCE_DIR}/nrf52840
+        ${NRF_INCLUDES}
+        ${OT_PUBLIC_INCLUDES}
+)
+
+target_include_directories(openthread-nrf52840-transport
+    PRIVATE
+        ${CMAKE_CURRENT_SOURCE_DIR}/nrf52840
+        ${NRF_INCLUDES}
+        ${OT_PUBLIC_INCLUDES}
+)
+
+target_include_directories(openthread-nrf52840-sdk
+    PRIVATE
+        ${CMAKE_CURRENT_SOURCE_DIR}/nrf52840
+        ${NRF_INCLUDES}
+        ${OT_PUBLIC_INCLUDES}
+)
+
+target_include_directories(openthread-nrf52840-softdevice-sdk
+    PRIVATE
+        ${CMAKE_CURRENT_SOURCE_DIR}/nrf52840
+        ${NRF_INCLUDES}
+        ${OT_PUBLIC_INCLUDES}
+)
diff --git a/examples/platforms/nrf528xx/nrf52840/openthread-core-nrf52840-config.h b/examples/platforms/nrf528xx/nrf52840/openthread-core-nrf52840-config.h
index 3150c16..2ca2f20 100644
--- a/examples/platforms/nrf528xx/nrf52840/openthread-core-nrf52840-config.h
+++ b/examples/platforms/nrf528xx/nrf52840/openthread-core-nrf52840-config.h
@@ -221,7 +221,7 @@
  *
  */
 #ifndef OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS
-#define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS 2048
+#define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS 4000
 #endif
 
 /**
diff --git a/examples/platforms/nrf528xx/src/platform-nrf5.h b/examples/platforms/nrf528xx/src/platform-nrf5.h
index e93e757..2034a2f 100644
--- a/examples/platforms/nrf528xx/src/platform-nrf5.h
+++ b/examples/platforms/nrf528xx/src/platform-nrf5.h
@@ -199,4 +199,10 @@
 
 int8_t nrf5GetChannelMaxTransmitPower(uint8_t aChannel);
 
+/**
+ * Callback function for region changed.
+ *
+ */
+void nrf5HandleRegionChanged(uint16_t aRegionCode);
+
 #endif // PLATFORM_NRF5_H_
diff --git a/examples/platforms/nrf528xx/src/radio.c b/examples/platforms/nrf528xx/src/radio.c
index 71abf52..9368f56 100644
--- a/examples/platforms/nrf528xx/src/radio.c
+++ b/examples/platforms/nrf528xx/src/radio.c
@@ -45,12 +45,12 @@
 
 #include "common/code_utils.hpp"
 #include "utils/code_utils.h"
+#include "utils/link_metrics.h"
 #include "utils/mac_frame.h"
 
 #include <platform-config.h>
 #include <openthread/platform/alarm-micro.h>
 #include <openthread/platform/diag.h>
-#include <openthread/platform/logging.h>
 #include <openthread/platform/radio.h>
 #include <openthread/platform/time.h>
 
@@ -108,18 +108,20 @@
 static otRadioFrame sAckFrame;
 static bool         sAckedWithFramePending;
 
-static int8_t sMaxTxPowerTable[OT_RADIO_2P4GHZ_OQPSK_CHANNEL_MAX - OT_RADIO_2P4GHZ_OQPSK_CHANNEL_MIN + 1];
-static int8_t sDefaultTxPower;
-static int8_t sLnaGain = 0;
+static int8_t   sMaxTxPowerTable[OT_RADIO_2P4GHZ_OQPSK_CHANNEL_MAX - OT_RADIO_2P4GHZ_OQPSK_CHANNEL_MIN + 1];
+static int8_t   sDefaultTxPower;
+static int8_t   sLnaGain    = 0;
+static uint16_t sRegionCode = 0;
 
 static uint32_t sEnergyDetectionTime;
 static uint8_t  sEnergyDetectionChannel;
 static int8_t   sEnergyDetected;
 
 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
-static uint32_t      sCslPeriod;
-static uint32_t      sCslSampleTime;
-static const uint8_t sCslIeHeader[OT_IE_HEADER_SIZE] = {CSL_IE_HEADER_BYTES_LO, CSL_IE_HEADER_BYTES_HI};
+static uint32_t       sCslPeriod;
+static uint32_t       sCslSampleTime;
+static const uint32_t sCslSampleDur                   = OPENTHREAD_CONFIG_CSL_SAMPLE_WINDOW * OT_US_PER_TEN_SYMBOLS;
+static const uint8_t  sCslIeHeader[OT_IE_HEADER_SIZE] = {CSL_IE_HEADER_BYTES_LO, CSL_IE_HEADER_BYTES_HI};
 #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
 
 typedef enum
@@ -194,6 +196,16 @@
     aTo[1] = (uint8_t)(aFrom >> 8);
 }
 
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
+static void convertExtAddress(uint8_t *aTo, const otExtAddress *aFrom)
+{
+    for (uint8_t i = 0; i < sizeof(otExtAddress); i++)
+    {
+        aTo[i] = aFrom->m8[sizeof(otExtAddress) - i - 1];
+    }
+}
+#endif
+
 static inline bool isPendingEventSet(RadioPendingEvents aEvent)
 {
     return sPendingEvents & (1UL << aEvent);
@@ -1056,14 +1068,30 @@
 {
     uint32_t curTime       = otPlatAlarmMicroGetNow();
     uint32_t cslPeriodInUs = sCslPeriod * OT_US_PER_TEN_SYMBOLS;
-    uint32_t diff = (cslPeriodInUs - (curTime % cslPeriodInUs) + (sCslSampleTime % cslPeriodInUs)) % cslPeriodInUs;
-
+    uint32_t diff =
+        (cslPeriodInUs - (curTime % cslPeriodInUs) + (sCslSampleTime % cslPeriodInUs) + (sCslSampleDur / 2)) %
+        cslPeriodInUs;
     return (uint16_t)(diff / OT_US_PER_TEN_SYMBOLS + 1);
 }
 #endif
 
-void nrf_802154_tx_ack_started(uint8_t *p_data)
+void nrf_802154_tx_ack_started(uint8_t *p_data, int8_t power, uint8_t lqi)
 {
+    otRadioFrame ackFrame;
+#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
+    uint8_t      linkMetricsDataLen = 0;
+    uint8_t      linkMetricsData[OT_ENH_PROBING_IE_DATA_MAX_SIZE];
+    otMacAddress macAddress;
+#else
+    OT_UNUSED_VARIABLE(power);
+    OT_UNUSED_VARIABLE(lqi);
+#endif
+
+    OT_UNUSED_VARIABLE(ackFrame);
+
+    ackFrame.mPsdu   = (uint8_t *)(p_data + 1);
+    ackFrame.mLength = p_data[0];
+
     // Check if the frame pending bit is set in ACK frame.
     sAckedWithFramePending = p_data[FRAME_PENDING_OFFSET] & FRAME_PENDING_BIT;
 
@@ -1072,13 +1100,18 @@
 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
     if (sCslPeriod > 0)
     {
-        otRadioFrame ackFrame;
-        ackFrame.mPsdu   = (uint8_t *)(p_data + 1);
-        ackFrame.mLength = p_data[0];
         otMacFrameSetCslIe(&ackFrame, sCslPeriod, getCslPhase());
     }
 #endif
 
+#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
+    otMacFrameGetDstAddr(&ackFrame, &macAddress);
+    if ((linkMetricsDataLen = otLinkMetricsEnhAckGenData(&macAddress, lqi, power, linkMetricsData)) > 0)
+    {
+        otMacFrameSetEnhAckProbingIe(&ackFrame, linkMetricsData, linkMetricsDataLen);
+    }
+#endif
+
     txAckProcessSecurity(p_data);
 #endif
 }
@@ -1089,6 +1122,7 @@
                                           uint8_t        aLqi,
                                           uint32_t       ack_time)
 {
+    OT_UNUSED_VARIABLE(aFrame); // For ARM gcc
     assert(aFrame == sTransmitPsdu);
 
     if (aAckPsdu == NULL)
@@ -1113,6 +1147,7 @@
 
 void nrf_802154_transmit_failed(const uint8_t *aFrame, nrf_802154_tx_error_t error)
 {
+    OT_UNUSED_VARIABLE(aFrame); // For ARM gcc
     assert(aFrame == sTransmitPsdu);
 
     switch (error)
@@ -1153,7 +1188,9 @@
 void nrf_802154_tx_started(const uint8_t *aFrame)
 {
     bool processSecurity = false;
+
     assert(aFrame == sTransmitPsdu);
+    OT_UNUSED_VARIABLE(aFrame);
 
 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
     if (sCslPeriod > 0)
@@ -1262,49 +1299,66 @@
 }
 #endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
 
-#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
-static void updateIeData(otInstance *aInstance, const uint8_t *aShortAddr, const uint8_t *aExtAddr)
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
+static void updateIeData(otInstance *aInstance, otShortAddress aShortAddr, const otExtAddress *aExtAddr)
 {
+    OT_UNUSED_VARIABLE(aInstance);
+
     int8_t  offset = 0;
     uint8_t ackIeData[OT_ACK_IE_MAX_SIZE];
+    uint8_t extAddr[OT_EXT_ADDRESS_SIZE];
+    uint8_t shortAddr[SHORT_ADDRESS_SIZE];
+#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
+    uint8_t      enhAckProbingDataLen = 0;
+    otMacAddress macAddress;
+    macAddress.mType                  = OT_MAC_ADDRESS_TYPE_SHORT;
+    macAddress.mAddress.mShortAddress = aShortAddr;
+#endif
 
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
     if (sCslPeriod > 0)
     {
         memcpy(ackIeData, sCslIeHeader, OT_IE_HEADER_SIZE);
         offset += OT_IE_HEADER_SIZE + OT_CSL_IE_SIZE; // reserve space for CSL IE
     }
+#endif
+
+#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
+    if ((enhAckProbingDataLen = otLinkMetricsEnhAckGetDataLen(&macAddress)) > 0)
+    {
+        offset += otMacFrameGenerateEnhAckProbingIe(ackIeData + offset, NULL, enhAckProbingDataLen);
+    }
+#endif
+
+    convertShortAddress(shortAddr, aShortAddr);
+    convertExtAddress(extAddr, aExtAddr);
 
     if (offset > 0)
     {
-        nrf_802154_ack_data_set(aShortAddr, false, ackIeData, offset, NRF_802154_ACK_DATA_IE);
-        nrf_802154_ack_data_set(aExtAddr, true, ackIeData, offset, NRF_802154_ACK_DATA_IE);
+        nrf_802154_ack_data_set(shortAddr, false, ackIeData, offset, NRF_802154_ACK_DATA_IE);
+        nrf_802154_ack_data_set(extAddr, true, ackIeData, offset, NRF_802154_ACK_DATA_IE);
     }
     else
     {
-        nrf_802154_ack_data_clear(aShortAddr, false, NRF_802154_ACK_DATA_IE);
-        nrf_802154_ack_data_clear(aExtAddr, true, NRF_802154_ACK_DATA_IE);
+        nrf_802154_ack_data_clear(shortAddr, false, NRF_802154_ACK_DATA_IE);
+        nrf_802154_ack_data_clear(extAddr, true, NRF_802154_ACK_DATA_IE);
     }
 }
+#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
 
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
 otError otPlatRadioEnableCsl(otInstance *aInstance, uint32_t aCslPeriod, const otExtAddress *aExtAddr)
 {
     otError        error = OT_ERROR_NONE;
-    uint8_t        parentExtAddr[OT_EXT_ADDRESS_SIZE];
-    uint8_t        parentShortAddress[SHORT_ADDRESS_SIZE];
-    const uint8_t *shortAddress;
+    otShortAddress shortAddress;
 
     sCslPeriod = aCslPeriod;
 
-    for (uint32_t i = 0; i < sizeof(parentExtAddr); i++)
-    {
-        parentExtAddr[i] = aExtAddr->m8[sizeof(parentExtAddr) - i - 1];
-    }
+    shortAddress = nrf_802154_pib_short_address_get()[1]
+                   << 8; // Don't need the other byte because this is parent's short address
+    shortAddress &= 0xfc00;
 
-    shortAddress = nrf_802154_pib_short_address_get();
-    memcpy(parentShortAddress, shortAddress, SHORT_ADDRESS_SIZE);
-    parentShortAddress[0] &= 0xfc;
-
-    updateIeData(aInstance, parentShortAddress, parentExtAddr);
+    updateIeData(aInstance, shortAddress, aExtAddr);
 
     return error;
 }
@@ -1324,8 +1378,17 @@
                                           const otExtAddress * aExtAddress)
 {
     OT_UNUSED_VARIABLE(aInstance);
+    OT_UNUSED_VARIABLE(aLinkMetrics);
+    OT_UNUSED_VARIABLE(aShortAddress);
+    OT_UNUSED_VARIABLE(aExtAddress);
 
-    return OT_ERROR_NOT_IMPLEMENTED;
+    otError error = OT_ERROR_NONE;
+
+    SuccessOrExit(error = otLinkMetricsConfigureEnhAckProbing(aShortAddress, aExtAddress, aLinkMetrics));
+    updateIeData(aInstance, aShortAddress, aExtAddress);
+
+exit:
+    return error;
 }
 #endif
 
@@ -1362,3 +1425,29 @@
 
     return power;
 }
+
+otError otPlatRadioSetRegion(otInstance *aInstance, uint16_t aRegionCode)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+
+    sRegionCode = aRegionCode;
+    nrf5HandleRegionChanged(aRegionCode);
+    return OT_ERROR_NONE;
+}
+
+otError otPlatRadioGetRegion(otInstance *aInstance, uint16_t *aRegionCode)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+    otError error = OT_ERROR_NONE;
+
+    VerifyOrExit(aRegionCode != NULL, error = OT_ERROR_INVALID_ARGS);
+
+    *aRegionCode = sRegionCode;
+exit:
+    return error;
+}
+
+OT_TOOL_WEAK void nrf5HandleRegionChanged(uint16_t aRegionCode)
+{
+    OT_UNUSED_VARIABLE(aRegionCode);
+}
diff --git a/examples/platforms/nrf528xx/src/transport/transport.c b/examples/platforms/nrf528xx/src/transport/transport.c
index 2ffe205..074be88 100644
--- a/examples/platforms/nrf528xx/src/transport/transport.c
+++ b/examples/platforms/nrf528xx/src/transport/transport.c
@@ -40,6 +40,8 @@
 
 void nrf5TransportInit(bool aPseudoReset)
 {
+    OT_UNUSED_VARIABLE(aPseudoReset);
+
 #if ((UART_AS_SERIAL_TRANSPORT == 1) || (USB_CDC_AS_SERIAL_TRANSPORT == 1))
     if (!aPseudoReset)
     {
@@ -52,13 +54,14 @@
 #endif
 
 #if (SPIS_AS_SERIAL_TRANSPORT == 1)
-    OT_UNUSED_VARIABLE(aPseudoReset);
     nrf5SpiSlaveInit();
 #endif
 }
 
 void nrf5TransportDeinit(bool aPseudoReset)
 {
+    OT_UNUSED_VARIABLE(aPseudoReset);
+
 #if ((UART_AS_SERIAL_TRANSPORT == 1) || (USB_CDC_AS_SERIAL_TRANSPORT == 1))
     if (!aPseudoReset)
     {
@@ -67,7 +70,6 @@
 #endif
 
 #if (SPIS_AS_SERIAL_TRANSPORT == 1)
-    OT_UNUSED_VARIABLE(aPseudoReset);
     nrf5SpiSlaveDeinit();
 #endif
 }
diff --git a/examples/platforms/nrf528xx/src/transport/uart.c b/examples/platforms/nrf528xx/src/transport/uart.c
index 8fc18fb..7c1c607 100644
--- a/examples/platforms/nrf528xx/src/transport/uart.c
+++ b/examples/platforms/nrf528xx/src/transport/uart.c
@@ -39,8 +39,8 @@
 #include <stdint.h>
 
 #include <utils/code_utils.h>
+#include <utils/uart.h>
 #include <openthread/platform/toolchain.h>
-#include <openthread/platform/uart.h>
 
 #include "openthread-system.h"
 
diff --git a/examples/platforms/nrf528xx/src/transport/usb-cdc-uart.c b/examples/platforms/nrf528xx/src/transport/usb-cdc-uart.c
index ec3a006..a05c41a 100644
--- a/examples/platforms/nrf528xx/src/transport/usb-cdc-uart.c
+++ b/examples/platforms/nrf528xx/src/transport/usb-cdc-uart.c
@@ -46,10 +46,10 @@
 #include <common/logging.hpp>
 #include <openthread-system.h>
 #include <utils/code_utils.h>
+#include <utils/uart.h>
 #include <openthread/platform/alarm-milli.h>
 #include <openthread/platform/diag.h>
 #include <openthread/platform/misc.h>
-#include <openthread/platform/uart.h>
 
 #include "platform-nrf5-transport.h"
 
diff --git a/examples/platforms/qpg6095/CMakeLists.txt b/examples/platforms/qpg6095/CMakeLists.txt
index b59202a..36c8622 100644
--- a/examples/platforms/qpg6095/CMakeLists.txt
+++ b/examples/platforms/qpg6095/CMakeLists.txt
@@ -80,9 +80,10 @@
         ${OT_MBEDTLS}
         ot-config
     PUBLIC
-        -T${CMAKE_CURRENT_SOURCE_DIR}/qpg6095.ld
+        -T${PROJECT_SOURCE_DIR}/third_party/Qorvo/repo/qpg6095/ld/qpg6095.ld
+        -nostdlib
         -Wl,--gc-sections
-        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map,--cref
 )
 
 target_compile_definitions(openthread-qpg6095
diff --git a/examples/platforms/qpg6095/Makefile.am b/examples/platforms/qpg6095/Makefile.am
index b9122d4..1778da6 100644
--- a/examples/platforms/qpg6095/Makefile.am
+++ b/examples/platforms/qpg6095/Makefile.am
@@ -32,45 +32,49 @@
 override CFLAGS    := $(filter-out -Wcast-align,$(CFLAGS))
 override CXXFLAGS  := $(filter-out -Wcast-align,$(CXXFLAGS))
 
-lib_LIBRARIES                               = libopenthread-qpg6095.a
+lib_LIBRARIES                                   = libopenthread-qpg6095.a
 
-libopenthread_qpg6095_a_CPPFLAGS            = \
-    -I$(top_srcdir)/include                   \
-    -I$(top_srcdir)/examples/platforms        \
-    -I$(top_srcdir)/examples/platforms/utils  \
-    -I$(top_srcdir)/src/core                  \
-    -lrt                                      \
-    -lpthread                                 \
+libopenthread_qpg6095_a_CPPFLAGS                = \
+    -I$(top_srcdir)/include                       \
+    -I$(top_srcdir)/src/core                      \
+    -I$(top_srcdir)/examples/platforms            \
+    -I$(top_srcdir)/examples/platforms/utils      \
+    -I$(top_srcdir)/examples/platforms/qpg6095    \
+    -lrt                                          \
+    -lpthread                                     \
     $(NULL)
 
-CLEANFILES = 
-
-libopenthread_qpg6095_a_SOURCES             = \
-    alarm.c                                   \
-    alarm_qorvo.h                             \
-    diag.c                                    \
-    entropy.c                                 \
-    logging.c                                 \
-    misc.c                                    \
-    misc_qorvo.h                              \
-    openthread-core-qpg6095-config.h          \
-    openthread-core-qpg6095-config-check.h \
-    platform.c                                \
-    platform_qorvo.h                          \
-    radio.c                                   \
-    radio_qorvo.h                             \
-    random_qorvo.h                            \
-    settings.cpp                              \
-    settings_qorvo.h                          \
-    uart.c                                    \
-    uart_qorvo.h                              \
+PLATFORM_SOURCES                                = \
+    alarm.c                                       \
+    alarm_qorvo.h                                 \
+    diag.c                                        \
+    entropy.c                                     \
+    logging.c                                     \
+    misc.c                                        \
+    misc_qorvo.h                                  \
+    openthread-core-qpg6095-config.h              \
+    openthread-core-qpg6095-config-check.h        \
+    platform.c                                    \
+    platform_qorvo.h                              \
+    radio.c                                       \
+    radio_qorvo.h                                 \
+    random_qorvo.h                                \
+    settings.cpp                                  \
+    settings_qorvo.h                              \
+    uart.c                                        \
+    uart_qorvo.h                                  \
     $(NULL)
 
-libopenthread_qpg6095_a_LIBADD              = \
-    $(top_builddir)/examples/platforms/utils/libopenthread_platform_utils_a-settings_ram.o
+libopenthread_qpg6095_a_SOURCES                 = \
+    $(PLATFORM_SOURCES)                           \
+    $(NULL)
+
+Dash = -
+libopenthread_qpg6095_a_LIBADD                  = \
+    $(shell find $(top_builddir)/examples/platforms/utils $(Dash)type f $(Dash)name "*.o")
 
 if OPENTHREAD_BUILD_COVERAGE
-CLEANFILES                               += $(wildcard *.gcda *.gcno)
+CLEANFILES                                      = $(wildcard *.gcda *.gcno)
 endif # OPENTHREAD_BUILD_COVERAGE
 
 include $(abs_top_nlbuild_autotools_dir)/automake/post.am
diff --git a/examples/platforms/qpg6095/Makefile.platform.am b/examples/platforms/qpg6095/Makefile.platform.am
index 0184c9e..5a433f1 100644
--- a/examples/platforms/qpg6095/Makefile.platform.am
+++ b/examples/platforms/qpg6095/Makefile.platform.am
@@ -30,12 +30,20 @@
 # qpg6095 platform-specific Makefile
 #
 
-LDADD_COMMON                                                          += \
-    $(top_builddir)/examples/platforms/qpg6095/libopenthread-qpg6095.a   \
-    $(top_srcdir)/third_party/Qorvo/qpg6095/libQorvoQPG6095.a            \
+LDADD_COMMON                                                            += \
+    $(top_builddir)/examples/platforms/qpg6095/libopenthread-qpg6095.a     \
     $(NULL)
-
-LDFLAGS_COMMON                                                        += \
-    -T $(top_srcdir)/examples/platforms/qpg6095/qpg6095.ld               \
+if OPENTHREAD_ENABLE_FTD
+LDADD_COMMON                                                            += \
+    $(top_srcdir)/third_party/Qorvo/repo/qpg6095/lib/libQorvoQPG6095_ftd.a \
     $(NULL)
-
+else # OPENTHREAD_ENABLE_FTD
+if OPENTHREAD_ENABLE_MTD
+LDADD_COMMON                                                            += \
+    $(top_srcdir)/third_party/Qorvo/repo/qpg6095/lib/libQorvoQPG6095_mtd.a \
+    $(NULL)
+endif # OPENTHREAD_ENABLE_MTD
+endif
+LDFLAGS_COMMON                                                          += \
+    -T $(top_srcdir)/third_party/Qorvo/repo/qpg6095/ld/qpg6095.ld          \
+    $(NULL)
diff --git a/examples/platforms/qpg6095/arm-none-eabi.cmake b/examples/platforms/qpg6095/arm-none-eabi.cmake
index de1ed14..fc54b18 100644
--- a/examples/platforms/qpg6095/arm-none-eabi.cmake
+++ b/examples/platforms/qpg6095/arm-none-eabi.cmake
@@ -1,5 +1,5 @@
 #
-#  Copyright (c) 2020, The OpenThread Authors.
+#  Copyright (c) 2021, The OpenThread Authors.
 #  All rights reserved.
 #
 #  Redistribution and use in source and binary forms, with or without
diff --git a/examples/platforms/qpg6095/crypto/qpg6095-mbedtls-config.h b/examples/platforms/qpg6095/crypto/qpg6095-mbedtls-config.h
index 95c23b8..f04c0c0 100644
--- a/examples/platforms/qpg6095/crypto/qpg6095-mbedtls-config.h
+++ b/examples/platforms/qpg6095/crypto/qpg6095-mbedtls-config.h
@@ -25,3 +25,16 @@
  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  *  POSSIBILITY OF SUCH DAMAGE.
  */
+
+#ifndef QGP6095_MBEDTLS_CONFIG_H
+#define QGP6095_MBEDTLS_CONFIG_H
+/* enable this to speed up crypto calculations by using a ram patch for bignum.c */
+#define MBEDTLS_BIGNUM_RAMPATCH 0
+
+/* enable this in case the QPG6095 runs too slow for the crypto calculations */
+#define MBEDTLS_SLOW_CPU 1
+#define MBEDTLS_COMPUTATION_UNTILL_SEQ_NR 6
+
+#include "mbedtls/check_config.h"
+
+#endif // QGP6095_MBEDTLS_CONFIG_H
diff --git a/examples/platforms/qpg6095/diag.c b/examples/platforms/qpg6095/diag.c
index 27f541d..e79c2f2 100644
--- a/examples/platforms/qpg6095/diag.c
+++ b/examples/platforms/qpg6095/diag.c
@@ -26,16 +26,17 @@
  *  POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "platform_qorvo.h"
-
 #include <stdbool.h>
 #include <stdio.h>
 #include <string.h>
 #include <sys/time.h>
 
-#include <openthread/config.h>
+#include <openthread-core-config.h>
+#include <openthread/platform/alarm-milli.h>
 #include <openthread/platform/radio.h>
 
+#if OPENTHREAD_CONFIG_DIAG_ENABLE
+
 /**
  * Diagnostics mode variables.
  *
@@ -73,3 +74,5 @@
 {
     OT_UNUSED_VARIABLE(aInstance);
 }
+
+#endif // OPENTHREAD_CONFIG_DIAG_ENABLE
diff --git a/examples/platforms/qpg6095/entropy.c b/examples/platforms/qpg6095/entropy.c
index 39936d0..e7a989d 100644
--- a/examples/platforms/qpg6095/entropy.c
+++ b/examples/platforms/qpg6095/entropy.c
@@ -39,26 +39,11 @@
 #include <common/code_utils.hpp>
 #include <openthread/platform/radio.h>
 
-#define GP_COMPONENT_ID GP_COMPONENT_ID_APP
-
-// uint8_t pseudoRandom = 0x34;
-
-void qorvoRandomInit(void)
-{
-    // pseudoRandom += (uint8_t) (seed&0xFF);
-}
-
 otError otPlatEntropyGet(uint8_t *aOutput, uint16_t aOutputLength)
 {
     otError error = OT_ERROR_NONE;
     assert(aOutputLength < 256);
-    // uint8_t i;
 
     qorvoRandomGet((uint8_t *)aOutput, (uint8_t)aOutputLength);
-    // for(i=0; i<aOutputLength; i++)
-    // {
-    //     aOutput[i] = pseudoRandom;
-    //     pseudoRandom = (pseudoRandom * 97 + 1)%256;
-    // }
     return error;
 }
diff --git a/examples/platforms/qpg6095/logging.c b/examples/platforms/qpg6095/logging.c
index de5ab3f..b703ecb 100644
--- a/examples/platforms/qpg6095/logging.c
+++ b/examples/platforms/qpg6095/logging.c
@@ -39,8 +39,6 @@
 #include "uart_qorvo.h"
 #include "utils/code_utils.h"
 
-// Macro to append content to end of the log string.
-
 #if (OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_PLATFORM_DEFINED)
 void otPlatLog(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aFormat, ...)
 {
diff --git a/examples/platforms/qpg6095/openthread-core-qpg6095-config.h b/examples/platforms/qpg6095/openthread-core-qpg6095-config.h
index 03720ce..e6e644b 100644
--- a/examples/platforms/qpg6095/openthread-core-qpg6095-config.h
+++ b/examples/platforms/qpg6095/openthread-core-qpg6095-config.h
@@ -50,12 +50,4 @@
  */
 #define OPENTHREAD_CONFIG_PLATFORM_INFO "QPG6095"
 
-/**
- * @def OPENTHREAD_CONFIG_NCP_UART_ENABLE
- *
- * Define to 1 to enable NCP UART support.
- *
- */
-#define OPENTHREAD_CONFIG_NCP_UART_ENABLE 1
-
 #endif // OPENTHREAD_CORE_QPG6095_CONFIG_H_
diff --git a/examples/platforms/qpg6095/platform.c b/examples/platforms/qpg6095/platform.c
index 6ea68aa..53aafb2 100644
--- a/examples/platforms/qpg6095/platform.c
+++ b/examples/platforms/qpg6095/platform.c
@@ -38,14 +38,17 @@
 #include "random_qorvo.h"
 #include "uart_qorvo.h"
 #include <openthread/tasklet.h>
-#include <openthread/platform/uart.h>
 
 #include "stdio.h"
 #include "stdlib.h"
 
 static otInstance *localInstance = NULL;
 
-bool qorvoPlatGotoSleepCheck(void)
+#ifdef QORVO_USE_ROM
+extern void flash_jump_gpJumpTables_GetRomVersion(void);
+#endif // QORVO_USE_ROM
+
+uint8_t qorvoPlatGotoSleepCheck(void)
 {
     if (localInstance)
     {
@@ -61,7 +64,11 @@
 {
     OT_UNUSED_VARIABLE(argc);
     OT_UNUSED_VARIABLE(argv);
+#ifdef QORVO_USE_ROM
+    flash_jump_gpJumpTables_GetRomVersion();
+#endif // QORVO_USE_ROM
     qorvoPlatInit((qorvoPlatGotoSleepCheckCallback_t)qorvoPlatGotoSleepCheck);
+    qorvoUartInit();
     qorvoAlarmInit();
     qorvoRandomInit();
     qorvoRadioInit();
diff --git a/examples/platforms/qpg6095/platform_qorvo.h b/examples/platforms/qpg6095/platform_qorvo.h
index 1afb173..85eb380 100644
--- a/examples/platforms/qpg6095/platform_qorvo.h
+++ b/examples/platforms/qpg6095/platform_qorvo.h
@@ -38,7 +38,7 @@
 #include <stdbool.h>
 #include <stdint.h>
 
-typedef bool (*qorvoPlatGotoSleepCheckCallback_t)(void);
+typedef uint8_t (*qorvoPlatGotoSleepCheckCallback_t)(void);
 
 /**
  * This function initializes the platform.
diff --git a/examples/platforms/qpg6095/qpg6095.ld b/examples/platforms/qpg6095/qpg6095.ld
deleted file mode 100644
index aa61a2d..0000000
--- a/examples/platforms/qpg6095/qpg6095.ld
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- *  Copyright (c) 2019, The OpenThread Authors.
- *  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.
- */
-
-/* Memory Spaces Definitions */
-MEMORY
-{
-  SYSRAM  (rxw) : ORIGIN = 0x20008000, LENGTH = 0x8000
-  UCRAM  (rxw) : ORIGIN = 0x20010000, LENGTH = 0x8000
-  FLASH (rx) : ORIGIN = 0x04000000, LENGTH = 0x80000
-}
-
-SECTIONS
-{
-    flash_end = 0x4000000 + 0x80000;
-    .vpp   0x0400001C : { LONG(_ivt >> 8);  } > FLASH
-    .mw    0x04000020 : { LONG(0x693A5C81); } > FLASH
-    rts_start_offset = 0x200;
-    rts_start_address = ORIGIN(FLASH) + rts_start_offset;
-    .rt    rts_start_address : {. = ALIGN(4); *(.rt_flash);    } > FLASH
-    .isr_vector : ALIGN(0x800) { _ivt = .; KEEP(*(.isr_vector)); } > FLASH
-
-    __exidx_start = .;
-    .text :       { . = ALIGN(4); *(.text)   *(.text.*);      } > FLASH
-    .rodata :     { . = ALIGN(4); *(.rodata) *(.rodata.*);    } > FLASH
-    __exidx_end = .;
-
-    _slower_retain = ORIGIN(SYSRAM) ; /* Start retained memory in sleep */
-
-    /* Fixed sections */
-    .mw_crc 0x20008000 : { . += 0x4; } > SYSRAM
-    .crc    0x20008004 : { . += 0x4; } > SYSRAM
-    .ret_hw 0x20008008 : { . += 0x138; } > SYSRAM
-    .ret_sw 0x20008140 : { . += 0x80; }  > SYSRAM
-
-    /* Windowed sections */
-    .ram_regmap :  { . += 0x140; } > SYSRAM
-    .events :      { . += 0x10 ; } > SYSRAM
-    .pbm_options : { . += 0x400 ; } > SYSRAM
-
-    .lower_ram_retain_gpmicro_accessible (NOLOAD) :  { . = ALIGN(4); *(.lower_ram_retain_gpmicro_accessible)   *(.lower_ram_retain_gpmicro_accessible.*); } > SYSRAM
-    .lower_ram_retain (NOLOAD) :  { . = ALIGN(4); *(.lower_ram_retain)   *(.lower_ram_retain.*); } > SYSRAM
-
-    .bss :        { . = ALIGN(4); *(.bss) *(COMMON);} > SYSRAM
-
-    _elower_retain = . ; /* End memory to be retained */
-    .lower_ram_noretain (NOLOAD) :  { . = ALIGN(4); *(.lower_ram_noretain)   *(.lower_ram_noretain.*); } > SYSRAM
-    _elower_ram = . ;
-
-    . = ORIGIN(UCRAM);
-
-    PROVIDE(_shigher_retain = .);
-    .higher_ram_retain (NOLOAD) :  { . = ALIGN(4); *(.higher_ram_retain)   *(.higher_ram_retain.*); } > UCRAM
-    PROVIDE(_ehigher_retain = .);
-    .higher_ram_noretain (NOLOAD) :  { . = ALIGN(4); *(.higher_ram_noretain)   *(.higher_ram_noretain.*); } > UCRAM
-    .data :       { . = ALIGN(4); *(.data)   *(.data.*);      } > UCRAM AT > FLASH
-    .bss_uc :     { . = ALIGN(4); *(.bss.*) ;} > UCRAM
-    _ehigher_ram = . ;
-
-    /* remove the debugging information from the standard libraries */
-    /DISCARD/ : {
-        libc.a ( * )
-        libm.a ( * )
-        libgcc.a ( * )
-    }
-
-    .gpNvm flash_end - 0x4000:
-    {
-        gpNvm_Start = . ;
-        . = 0x4000 ;
-        gpNvm_End = . ;
-    } > FLASH
-
-    _etext  = ADDR(.text) + SIZEOF(.text);
-    _sidata = LOADADDR(.data);
-    _sdata  = ADDR(.data);
-    _edata  = ADDR(.data) + ALIGN(SIZEOF(.data), 4);
-    _ldata  = _edata - _sdata;
-    _sbss   = ADDR(.bss);
-    _ebss   = ADDR(.bss)  + ALIGN(SIZEOF(.bss),  4);
-    _lbss   = _ebss - _sbss;
-    /* use UCRAM for stack */
-    _sstack = _ehigher_ram;
-    _estack = ORIGIN(UCRAM) + LENGTH(UCRAM);
-    _sbss_uc = ADDR(.bss_uc);
-    _ebss_uc = ADDR(.bss_uc)  + ALIGN(SIZEOF(.bss_uc),  4);
-    _lbss_uc = _ebss_uc - _sbss_uc;
-
-    __configured_stack_size = 0x1200;
-    __stack_size     = _estack - _sstack;
-    ASSERT(__stack_size >= __configured_stack_size, "STACK too small")
-
-    _elower_ram = ADDR(.lower_ram_retain) + ALIGN(SIZEOF(.lower_ram_retain),  4);
-    __lowerram_retain_size     = _elower_retain - _slower_retain;
-
-    __higherram_retain_size     = SIZEOF(.higher_ram_retain);
-
-    sw_retention_begin = ADDR(.ret_sw);
-    sw_retention_end = ADDR(.ret_sw) + SIZEOF(.ret_sw);
-    ram_regmap_begin   = ADDR(.ram_regmap);
-    events_begin       = ADDR(.events);
-    pbm_options_begin  = ADDR(.pbm_options);
-
-    total_nr_of_events_as_an_address = SIZEOF(.events) / 16;
-}
-
-ENTRY(reset_handler)
-
diff --git a/examples/platforms/qpg6095/radio.c b/examples/platforms/qpg6095/radio.c
index 2376214..873588a 100644
--- a/examples/platforms/qpg6095/radio.c
+++ b/examples/platforms/qpg6095/radio.c
@@ -32,14 +32,15 @@
  *
  */
 
-#include "radio_qorvo.h"
-
 #include <string.h>
 
-#include <common/logging.hpp>
 #include <openthread/platform/diag.h>
 #include <openthread/platform/radio.h>
 
+#include "utils/code_utils.h"
+
+#include "radio_qorvo.h"
+
 enum
 {
     QPG6095_RECEIVE_SENSITIVITY = -100, // dBm
@@ -67,42 +68,34 @@
 extern otRadioFrame sTransmitFrame;
 
 static otRadioState sState;
-// static bool sIsReceiverEnabled = false;
-static otInstance *pQorvoInstance;
+static otInstance * pQorvoInstance;
 
 typedef struct otCachedSettings_s
 {
     uint16_t panid;
 } otCachedSettings_t;
+
 static otCachedSettings_t otCachedSettings;
 
-static uint8_t scanstate = 0;
+/* Upper layer relies on txpower could be set before receive, but MAC have per-channel config for it.
+   Store txpower until channel set in Receive(). */
+#define PENDING_TX_POWER_NONE (-1)
+static int8_t pendingTxPower = PENDING_TX_POWER_NONE;
 
-/*****************************************************************************
- *                    Static Function Prototypes
- *****************************************************************************/
-
-/*****************************************************************************
- *                    Static Data Definitions
- *****************************************************************************/
-
-/*****************************************************************************
- *                    Static Function Definitions
- *****************************************************************************/
-
-/*****************************************************************************
- *                    Public Function Definitions
- *****************************************************************************/
+static uint8_t sScanstate         = 0;
+static int8_t  sLastReceivedPower = 127;
 
 void otPlatRadioGetIeeeEui64(otInstance *aInstance, uint8_t *aIeeeEui64)
 {
     OT_UNUSED_VARIABLE(aInstance);
+
     qorvoRadioGetIeeeEui64(aIeeeEui64);
 }
 
 void otPlatRadioSetPanId(otInstance *aInstance, uint16_t panid)
 {
     OT_UNUSED_VARIABLE(aInstance);
+
     qorvoRadioSetPanId(panid);
     otCachedSettings.panid = panid;
 }
@@ -110,19 +103,22 @@
 void otPlatRadioSetExtendedAddress(otInstance *aInstance, const otExtAddress *address)
 {
     OT_UNUSED_VARIABLE(aInstance);
+
     qorvoRadioSetExtendedAddress(address->m8);
 }
 
 void otPlatRadioSetShortAddress(otInstance *aInstance, uint16_t address)
 {
     OT_UNUSED_VARIABLE(aInstance);
+
     qorvoRadioSetShortAddress(address);
 }
 
 bool otPlatRadioIsEnabled(otInstance *aInstance)
 {
     OT_UNUSED_VARIABLE(aInstance);
-    return (sState != OT_RADIO_STATE_DISABLED) ? true : false;
+
+    return (sState != OT_RADIO_STATE_DISABLED);
 }
 
 otError otPlatRadioEnable(otInstance *aInstance)
@@ -141,40 +137,52 @@
 otError otPlatRadioDisable(otInstance *aInstance)
 {
     OT_UNUSED_VARIABLE(aInstance);
-    if (otPlatRadioIsEnabled(aInstance))
+
+    otEXPECT(otPlatRadioIsEnabled(aInstance));
+
+    if (sState == OT_RADIO_STATE_RECEIVE)
     {
-        if (sState == OT_RADIO_STATE_RECEIVE)
-        {
-            qorvoRadioSetRxOnWhenIdle(false);
-        }
-        sState = OT_RADIO_STATE_DISABLED;
+        qorvoRadioSetRxOnWhenIdle(false);
     }
 
+    sState = OT_RADIO_STATE_DISABLED;
+
+exit:
     return OT_ERROR_NONE;
 }
 
 otError otPlatRadioSleep(otInstance *aInstance)
 {
     OT_UNUSED_VARIABLE(aInstance);
-    if (sState == OT_RADIO_STATE_RECEIVE)
+
+    otError error = OT_ERROR_INVALID_STATE;
+
+    if (sState == OT_RADIO_STATE_RECEIVE || sState == OT_RADIO_STATE_SLEEP)
     {
         qorvoRadioSetRxOnWhenIdle(false);
+        error  = OT_ERROR_NONE;
         sState = OT_RADIO_STATE_SLEEP;
     }
-
-    return OT_ERROR_NONE;
+    return error;
 }
 
 otError otPlatRadioReceive(otInstance *aInstance, uint8_t aChannel)
 {
-    otError error  = OT_ERROR_INVALID_STATE;
+    otError error = OT_ERROR_INVALID_STATE;
+
     pQorvoInstance = aInstance;
 
-    if ((sState != OT_RADIO_STATE_DISABLED) && (scanstate == 0))
+    if ((sState != OT_RADIO_STATE_DISABLED) && (sScanstate == 0))
     {
         qorvoRadioSetCurrentChannel(aChannel);
+        if (pendingTxPower != PENDING_TX_POWER_NONE)
+        {
+            qorvoRadioSetTransmitPower(pendingTxPower);
+            pendingTxPower = PENDING_TX_POWER_NONE;
+        }
         error = OT_ERROR_NONE;
     }
+
     if (sState == OT_RADIO_STATE_SLEEP)
     {
         qorvoRadioSetRxOnWhenIdle(true);
@@ -187,14 +195,16 @@
 
 otError otPlatRadioTransmit(otInstance *aInstance, otRadioFrame *aPacket)
 {
+    otError err = OT_ERROR_NONE;
+
     pQorvoInstance = aInstance;
 
-    if (sState == OT_RADIO_STATE_DISABLED)
-    {
-        return OT_ERROR_INVALID_STATE;
-    }
+    otEXPECT_ACTION(sState != OT_RADIO_STATE_DISABLED, err = OT_ERROR_INVALID_STATE);
 
-    return qorvoRadioTransmit(aPacket);
+    err = qorvoRadioTransmit(aPacket);
+
+exit:
+    return err;
 }
 
 void cbQorvoRadioTransmitDone(otRadioFrame *aPacket, bool aFramePending, otError aError)
@@ -220,9 +230,10 @@
 
 void cbQorvoRadioReceiveDone(otRadioFrame *aPacket, otError aError)
 {
-    // TODO Set this flag only when the packet is really acknowledged with frame pending set.
-    // See https://github.com/openthread/openthread/pull/3785
-    aPacket->mInfo.mRxInfo.mAckedWithFramePending = true;
+    if (aError == OT_ERROR_NONE)
+    {
+        sLastReceivedPower = aPacket->mInfo.mRxInfo.mRssi;
+    }
 
     otPlatRadioReceiveDone(pQorvoInstance, aPacket, aError);
 }
@@ -230,24 +241,28 @@
 otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *aInstance)
 {
     OT_UNUSED_VARIABLE(aInstance);
+
     return &sTransmitFrame;
 }
 
 int8_t otPlatRadioGetRssi(otInstance *aInstance)
 {
     OT_UNUSED_VARIABLE(aInstance);
-    return 0;
+
+    return sLastReceivedPower;
 }
 
 otRadioCaps otPlatRadioGetCaps(otInstance *aInstance)
 {
     OT_UNUSED_VARIABLE(aInstance);
+
     return OT_RADIO_CAPS_ACK_TIMEOUT | OT_RADIO_CAPS_ENERGY_SCAN | OT_RADIO_CAPS_TRANSMIT_RETRIES;
 }
 
 bool otPlatRadioGetPromiscuous(otInstance *aInstance)
 {
     OT_UNUSED_VARIABLE(aInstance);
+
     return false;
 }
 
@@ -260,36 +275,42 @@
 void otPlatRadioEnableSrcMatch(otInstance *aInstance, bool aEnable)
 {
     OT_UNUSED_VARIABLE(aInstance);
+
     qorvoRadioEnableSrcMatch(aEnable);
 }
 
 otError otPlatRadioAddSrcMatchShortEntry(otInstance *aInstance, uint16_t aShortAddress)
 {
     OT_UNUSED_VARIABLE(aInstance);
+
     return qorvoRadioAddSrcMatchShortEntry(aShortAddress, otCachedSettings.panid);
 }
 
 otError otPlatRadioAddSrcMatchExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress)
 {
     OT_UNUSED_VARIABLE(aInstance);
+
     return qorvoRadioAddSrcMatchExtEntry(aExtAddress->m8, otCachedSettings.panid);
 }
 
 otError otPlatRadioClearSrcMatchShortEntry(otInstance *aInstance, uint16_t aShortAddress)
 {
     OT_UNUSED_VARIABLE(aInstance);
+
     return qorvoRadioClearSrcMatchShortEntry(aShortAddress, otCachedSettings.panid);
 }
 
 otError otPlatRadioClearSrcMatchExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress)
 {
     OT_UNUSED_VARIABLE(aInstance);
+
     return qorvoRadioClearSrcMatchExtEntry(aExtAddress->m8, otCachedSettings.panid);
 }
 
 void otPlatRadioClearSrcMatchShortEntries(otInstance *aInstance)
 {
     OT_UNUSED_VARIABLE(aInstance);
+
     /* clear both short and extended addresses here */
     qorvoRadioClearSrcMatchEntries();
 }
@@ -297,6 +318,7 @@
 void otPlatRadioClearSrcMatchExtEntries(otInstance *aInstance)
 {
     OT_UNUSED_VARIABLE(aInstance);
+
     /* not implemented */
     /* assumes clearing of short and extended entries is done simultaniously by the openthread stack */
 }
@@ -304,36 +326,75 @@
 otError otPlatRadioEnergyScan(otInstance *aInstance, uint8_t aScanChannel, uint16_t aScanDuration)
 {
     OT_UNUSED_VARIABLE(aInstance);
-    scanstate = 1;
+
+    sScanstate = 1;
     return qorvoRadioEnergyScan(aScanChannel, aScanDuration);
 }
 
 void cbQorvoRadioEnergyScanDone(int8_t aEnergyScanMaxRssi)
 {
-    scanstate = 0;
+    sScanstate = 0;
     otPlatRadioEnergyScanDone(pQorvoInstance, aEnergyScanMaxRssi);
 }
 
 otError otPlatRadioGetTransmitPower(otInstance *aInstance, int8_t *aPower)
 {
-    // TODO: Create a proper implementation for this driver.
     OT_UNUSED_VARIABLE(aInstance);
-    OT_UNUSED_VARIABLE(aPower);
-    return OT_ERROR_NOT_IMPLEMENTED;
+
+    otError result;
+
+    if (aPower == NULL)
+    {
+        return OT_ERROR_INVALID_ARGS;
+    }
+
+    if ((sState == OT_RADIO_STATE_DISABLED) || (sScanstate != 0))
+    {
+        *aPower = (pendingTxPower == PENDING_TX_POWER_NONE) ? 0 : pendingTxPower;
+        return OT_ERROR_NONE;
+    }
+
+    result = qorvoRadioGetTransmitPower(aPower);
+
+    if (result == OT_ERROR_INVALID_STATE)
+    {
+        // Channel was not set, so txpower is ambigious
+        *aPower = (pendingTxPower == PENDING_TX_POWER_NONE) ? 0 : pendingTxPower;
+        return OT_ERROR_NONE;
+    }
+
+    return result;
 }
 
 otError otPlatRadioSetTransmitPower(otInstance *aInstance, int8_t aPower)
 {
-    // TODO: Create a proper implementation for this driver.
     OT_UNUSED_VARIABLE(aInstance);
-    OT_UNUSED_VARIABLE(aPower);
-    return OT_ERROR_NOT_IMPLEMENTED;
+
+    otError result;
+
+    if ((sState == OT_RADIO_STATE_DISABLED) || (sScanstate != 0))
+    {
+        pendingTxPower = aPower;
+        return OT_ERROR_NONE;
+    }
+
+    result = qorvoRadioSetTransmitPower(aPower);
+
+    if (result == OT_ERROR_INVALID_STATE)
+    {
+        // Channel was not set, so txpower is ambigious
+        pendingTxPower = aPower;
+        result         = OT_ERROR_NONE;
+    }
+
+    return result;
 }
 
 otError otPlatRadioGetCcaEnergyDetectThreshold(otInstance *aInstance, int8_t *aThreshold)
 {
     OT_UNUSED_VARIABLE(aInstance);
     OT_UNUSED_VARIABLE(aThreshold);
+
     return OT_ERROR_NOT_IMPLEMENTED;
 }
 
@@ -341,11 +402,19 @@
 {
     OT_UNUSED_VARIABLE(aInstance);
     OT_UNUSED_VARIABLE(aThreshold);
+
     return OT_ERROR_NOT_IMPLEMENTED;
 }
 
 int8_t otPlatRadioGetReceiveSensitivity(otInstance *aInstance)
 {
     OT_UNUSED_VARIABLE(aInstance);
+
     return QPG6095_RECEIVE_SENSITIVITY;
 }
+
+const char *otPlatRadioGetVersionString(otInstance *aInstance)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+    return "OPENTHREAD/Qorvo/0.0";
+}
diff --git a/examples/platforms/qpg6095/radio_qorvo.h b/examples/platforms/qpg6095/radio_qorvo.h
index 678a096..573cf0a 100644
--- a/examples/platforms/qpg6095/radio_qorvo.h
+++ b/examples/platforms/qpg6095/radio_qorvo.h
@@ -37,6 +37,8 @@
 
 #include <stdbool.h>
 #include <stdint.h>
+
+#include <openthread/error.h>
 #include <openthread/platform/radio.h>
 
 /**
@@ -173,6 +175,22 @@
 otError qorvoRadioClearSrcMatchExtEntry(const uint8_t *aExtAddress, uint16_t panid);
 
 /**
+ * This function gets the transmit power for current channel
+ *
+ * @param[out]  aPower  The transmit power
+ *
+ */
+otError qorvoRadioGetTransmitPower(int8_t *aPower);
+
+/**
+ * This function sets the transmit power for current channel
+ *
+ * @param[in]  aPower  The transmit power
+ *
+ */
+otError qorvoRadioSetTransmitPower(int8_t aPower);
+
+/**
  * This callback is called when the energy scan is finished.
  *
  * @param[in]  aEnergyScanMaxRssi  The amount of energy detected during the ED scan.
diff --git a/examples/platforms/qpg6095/settings.cpp b/examples/platforms/qpg6095/settings.cpp
index a6e4e44..6dff98d 100644
--- a/examples/platforms/qpg6095/settings.cpp
+++ b/examples/platforms/qpg6095/settings.cpp
@@ -38,6 +38,7 @@
 #include "openthread/platform/settings.h"
 
 #include <utils/code_utils.h>
+#include <utils/settings.h>
 
 #include "settings_qorvo.h"
 
diff --git a/examples/platforms/qpg6095/uart.c b/examples/platforms/qpg6095/uart.c
index c5336a2..5f3d1aa 100644
--- a/examples/platforms/qpg6095/uart.c
+++ b/examples/platforms/qpg6095/uart.c
@@ -41,7 +41,8 @@
 #define HAVE__BOOL 1
 
 #include <common/code_utils.hpp>
-#include <openthread/platform/uart.h>
+
+#include "utils/uart.h"
 
 otError otPlatUartEnable(void)
 {
@@ -78,3 +79,13 @@
 {
     otPlatUartSendDone();
 }
+
+OT_TOOL_WEAK void otPlatUartSendDone(void)
+{
+}
+
+OT_TOOL_WEAK void otPlatUartReceived(const uint8_t *aBuf, uint16_t aBufLength)
+{
+    OT_UNUSED_VARIABLE(aBuf);
+    OT_UNUSED_VARIABLE(aBufLength);
+}
diff --git a/examples/platforms/qpg6095/uart_qorvo.h b/examples/platforms/qpg6095/uart_qorvo.h
index 7803648..c827741 100644
--- a/examples/platforms/qpg6095/uart_qorvo.h
+++ b/examples/platforms/qpg6095/uart_qorvo.h
@@ -40,12 +40,6 @@
 #include <openthread/platform/logging.h>
 
 /**
- * This function performs UART driver processing.
- *
- */
-void qorvoUartProcess(void);
-
-/**
  * This function enables the UART driver.
  *
  */
diff --git a/examples/platforms/qpg6100/CMakeLists.txt b/examples/platforms/qpg6100/CMakeLists.txt
new file mode 100644
index 0000000..efa778b
--- /dev/null
+++ b/examples/platforms/qpg6100/CMakeLists.txt
@@ -0,0 +1,104 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+set(OT_PLATFORM_LIB "openthread-qpg6100" PARENT_SCOPE)
+
+if(NOT OT_CONFIG)
+    set(OT_CONFIG "openthread-core-qpg6100-config.h")
+    set(OT_CONFIG ${OT_CONFIG} PARENT_SCOPE)
+endif()
+
+list(APPEND OT_PLATFORM_DEFINES
+    "OPENTHREAD_CORE_CONFIG_PLATFORM_CHECK_FILE=\"openthread-core-qpg6100-config-check.h\""
+)
+set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE)
+
+target_compile_definitions(ot-config INTERFACE "MBEDTLS_USER_CONFIG_FILE=\"qpg6100-mbedtls-config.h\"")
+
+list(APPEND OT_PUBLIC_INCLUDES
+    "${CMAKE_CURRENT_SOURCE_DIR}/crypto"
+)
+set(OT_PUBLIC_INCLUDES ${OT_PUBLIC_INCLUDES} PARENT_SCOPE)
+
+if(OT_CFLAGS MATCHES "-pedantic-errors")
+    string(REPLACE "-pedantic-errors" "" OT_CFLAGS "${OT_CFLAGS}")
+endif()
+
+if(OT_CFLAGS MATCHES "-Wno-c\\+\\+14-compat")
+    string(REPLACE "-Wno-c++14-compat" "" OT_CFLAGS "${OT_CFLAGS}")
+endif()
+
+list(APPEND OT_PLATFORM_DEFINES "OPENTHREAD_PROJECT_CORE_CONFIG_FILE=\"${OT_CONFIG}\"")
+
+add_library(openthread-qpg6100
+    alarm.c
+    diag.c
+    entropy.c
+    logging.c
+    misc.c
+    platform.c
+    radio.c
+    settings.cpp
+    uart.c
+)
+
+set_target_properties(
+    openthread-qpg6100
+    PROPERTIES
+        C_STANDARD 99
+        CXX_STANDARD 11
+)
+
+target_link_libraries(openthread-qpg6100
+    PRIVATE
+        qpg6100-driver
+        ${OT_MBEDTLS}
+        ot-config
+    PUBLIC
+        -T${PROJECT_SOURCE_DIR}/third_party/Qorvo/repo/qpg6100/ld/qpg6100.ld
+        -nostdlib
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map,--cref
+)
+
+target_compile_definitions(openthread-qpg6100
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_options(openthread-qpg6100
+    PRIVATE
+        ${OT_CFLAGS}
+)
+
+target_include_directories(openthread-qpg6100
+    PRIVATE
+        ${OT_PUBLIC_INCLUDES}
+        ${PROJECT_SOURCE_DIR}/src/core
+        ${PROJECT_SOURCE_DIR}/examples/platforms
+)
diff --git a/examples/platforms/qpg6100/Makefile.am b/examples/platforms/qpg6100/Makefile.am
new file mode 100644
index 0000000..8e98877
--- /dev/null
+++ b/examples/platforms/qpg6100/Makefile.am
@@ -0,0 +1,80 @@
+#
+#  Copyright (c) 2019, The OpenThread Authors.
+#  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.
+#
+
+include $(abs_top_nlbuild_autotools_dir)/automake/pre.am
+
+# Do not enable -Wcast-align for this platform
+override CFLAGS    := $(filter-out -Wcast-align,$(CFLAGS))
+override CXXFLAGS  := $(filter-out -Wcast-align,$(CXXFLAGS))
+
+lib_LIBRARIES                                   = libopenthread-qpg6100.a
+
+libopenthread_qpg6100_a_CPPFLAGS                = \
+    -I$(top_srcdir)/include                       \
+    -I$(top_srcdir)/src/core                      \
+    -I$(top_srcdir)/examples/platforms            \
+    -I$(top_srcdir)/examples/platforms/utils      \
+    -I$(top_srcdir)/examples/platforms/qpg6100    \
+    -lrt                                          \
+    -lpthread                                     \
+    $(NULL)
+
+PLATFORM_SOURCES                                = \
+    alarm.c                                       \
+    alarm_qorvo.h                                 \
+    diag.c                                        \
+    entropy.c                                     \
+    logging.c                                     \
+    misc.c                                        \
+    misc_qorvo.h                                  \
+    openthread-core-qpg6100-config.h              \
+    openthread-core-qpg6100-config-check.h        \
+    platform.c                                    \
+    platform_qorvo.h                              \
+    radio.c                                       \
+    radio_qorvo.h                                 \
+    random_qorvo.h                                \
+    settings.cpp                                  \
+    settings_qorvo.h                              \
+    uart.c                                        \
+    uart_qorvo.h                                  \
+    $(NULL)
+
+libopenthread_qpg6100_a_SOURCES                 = \
+    $(PLATFORM_SOURCES)                           \
+    $(NULL)
+
+Dash = -
+libopenthread_qpg6100_a_LIBADD                  = \
+    $(shell find $(top_builddir)/examples/platforms/utils $(Dash)type f $(Dash)name "*.o")
+
+if OPENTHREAD_BUILD_COVERAGE
+CLEANFILES                                = $(wildcard *.gcda *.gcno)
+endif # OPENTHREAD_BUILD_COVERAGE
+
+include $(abs_top_nlbuild_autotools_dir)/automake/post.am
diff --git a/examples/platforms/qpg6100/Makefile.platform.am b/examples/platforms/qpg6100/Makefile.platform.am
new file mode 100644
index 0000000..559980f
--- /dev/null
+++ b/examples/platforms/qpg6100/Makefile.platform.am
@@ -0,0 +1,54 @@
+#
+#  Copyright (c) 2019, The OpenThread Authors.
+#  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.
+#
+
+#
+# qpg6100 platform-specific Makefile
+#
+
+LDADD_COMMON                                                            += \
+    $(top_builddir)/examples/platforms/qpg6100/libopenthread-qpg6100.a     \
+    $(NULL)
+if OPENTHREAD_ENABLE_BUILTIN_MBEDTLS
+LDADD_COMMON                                                            += \
+    $(top_srcdir)/third_party/Qorvo/repo/qpg6100/lib/libmbedtls_alt.a      \
+    $(NULL)
+endif # OPENTHREAD_ENABLE_BUILTIN_MBEDTLS
+if OPENTHREAD_ENABLE_FTD
+LDADD_COMMON                                                            += \
+    $(top_srcdir)/third_party/Qorvo/repo/qpg6100/lib/libQorvoQPG6100_ftd.a \
+    $(NULL)
+else # OPENTHREAD_ENABLE_FTD
+if OPENTHREAD_ENABLE_MTD
+LDADD_COMMON                                                            += \
+    $(top_srcdir)/third_party/Qorvo/repo/qpg6100/lib/libQorvoQPG6100_mtd.a \
+    $(NULL)
+endif # OPENTHREAD_ENABLE_MTD
+endif
+LDFLAGS_COMMON                                                          += \
+    -T $(top_srcdir)/third_party/Qorvo/repo/qpg6100/ld/qpg6100.ld          \
+    $(NULL)
diff --git a/examples/platforms/qpg6100/alarm.c b/examples/platforms/qpg6100/alarm.c
new file mode 120000
index 0000000..b6f42ba
--- /dev/null
+++ b/examples/platforms/qpg6100/alarm.c
@@ -0,0 +1 @@
+../qpg6095/alarm.c
\ No newline at end of file
diff --git a/examples/platforms/qpg6100/alarm_qorvo.h b/examples/platforms/qpg6100/alarm_qorvo.h
new file mode 120000
index 0000000..c9bb615
--- /dev/null
+++ b/examples/platforms/qpg6100/alarm_qorvo.h
@@ -0,0 +1 @@
+../qpg6095/alarm_qorvo.h
\ No newline at end of file
diff --git a/examples/platforms/qpg6100/arm-none-eabi.cmake b/examples/platforms/qpg6100/arm-none-eabi.cmake
new file mode 120000
index 0000000..de6f304
--- /dev/null
+++ b/examples/platforms/qpg6100/arm-none-eabi.cmake
@@ -0,0 +1 @@
+../qpg6095/arm-none-eabi.cmake
\ No newline at end of file
diff --git a/examples/platforms/qpg6100/crypto/aes_alt.h b/examples/platforms/qpg6100/crypto/aes_alt.h
new file mode 100644
index 0000000..f67cf47
--- /dev/null
+++ b/examples/platforms/qpg6100/crypto/aes_alt.h
@@ -0,0 +1,230 @@
+/**
+ * \file aes_alt.h
+ *
+ * \brief AES block cipher
+ *
+ *  Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
+ *  SPDX-License-Identifier: Apache-2.0
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *  not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  This file is part of mbed TLS (https://tls.mbed.org)
+ */
+#ifndef MBEDTLS_AES_ALT_H
+#define MBEDTLS_AES_ALT_H
+
+#if defined(MBEDTLS_AES_ALT)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \brief          AES context structure
+ *
+ * \note           buf is able to hold 32 extra bytes, which can be used:
+ *                 - for alignment purposes if VIA padlock is used, and/or
+ *                 - to simplify key expansion in the 256-bit case by
+ *                 generating an extra round key
+ */
+typedef struct
+{
+    uint32_t      buf[68]; /*!<  unaligned data    */
+    unsigned char key[32]; /*!< internal copy of the key */
+    unsigned int  keysize; /*!< size in bytes of the key */
+} mbedtls_aes_context;
+
+/**
+ * \brief          Initialize AES context
+ *
+ * \param ctx      AES context to be initialized
+ */
+void mbedtls_aes_init(mbedtls_aes_context *ctx);
+
+/**
+ * \brief          Clear AES context
+ *
+ * \param ctx      AES context to be cleared
+ */
+void mbedtls_aes_free(mbedtls_aes_context *ctx);
+
+/**
+ * \brief          AES key schedule (encryption)
+ *
+ * \param ctx      AES context to be initialized
+ * \param key      encryption key
+ * \param keybits  must be 128, 192 or 256
+ *
+ * \return         0 if successful, or MBEDTLS_ERR_AES_INVALID_KEY_LENGTH
+ */
+int mbedtls_aes_setkey_enc(mbedtls_aes_context *ctx, const unsigned char *key, unsigned int keybits);
+
+/**
+ * \brief          AES key schedule (decryption)
+ *
+ * \param ctx      AES context to be initialized
+ * \param key      decryption key
+ * \param keybits  must be 128, 192 or 256
+ *
+ * \return         0 if successful, or MBEDTLS_ERR_AES_INVALID_KEY_LENGTH
+ */
+int mbedtls_aes_setkey_dec(mbedtls_aes_context *ctx, const unsigned char *key, unsigned int keybits);
+
+/**
+ * \brief          AES-ECB block encryption/decryption
+ *
+ * \param ctx      AES context
+ * \param mode     MBEDTLS_AES_ENCRYPT or MBEDTLS_AES_DECRYPT
+ * \param input    16-byte input block
+ * \param output   16-byte output block
+ *
+ * \return         0 if successful
+ */
+int mbedtls_aes_crypt_ecb(mbedtls_aes_context *ctx, int mode, const unsigned char input[16], unsigned char output[16]);
+
+#if defined(MBEDTLS_CIPHER_MODE_CBC)
+/**
+ * \brief          AES-CBC buffer encryption/decryption
+ *                 Length should be a multiple of the block
+ *                 size (16 bytes)
+ *
+ * \note           Upon exit, the content of the IV is updated so that you can
+ *                 call the function same function again on the following
+ *                 block(s) of data and get the same result as if it was
+ *                 encrypted in one call. This allows a "streaming" usage.
+ *                 If on the other hand you need to retain the contents of the
+ *                 IV, you should either save it manually or use the cipher
+ *                 module instead.
+ *
+ * \param ctx      AES context
+ * \param mode     MBEDTLS_AES_ENCRYPT or MBEDTLS_AES_DECRYPT
+ * \param length   length of the input data
+ * \param iv       initialization vector (updated after use)
+ * \param input    buffer holding the input data
+ * \param output   buffer holding the output data
+ *
+ * \return         0 if successful, or MBEDTLS_ERR_AES_INVALID_INPUT_LENGTH
+ */
+int mbedtls_aes_crypt_cbc(mbedtls_aes_context *ctx,
+                          int                  mode,
+                          size_t               length,
+                          unsigned char        iv[16],
+                          const unsigned char *input,
+                          unsigned char *      output);
+#endif /* MBEDTLS_CIPHER_MODE_CBC */
+
+#if defined(MBEDTLS_CIPHER_MODE_CFB)
+/**
+ * \brief          AES-CFB128 buffer encryption/decryption.
+ *
+ * Note: Due to the nature of CFB you should use the same key schedule for
+ * both encryption and decryption. So a context initialized with
+ * mbedtls_aes_setkey_enc() for both MBEDTLS_AES_ENCRYPT and MBEDTLS_AES_DECRYPT.
+ *
+ * \note           Upon exit, the content of the IV is updated so that you can
+ *                 call the function same function again on the following
+ *                 block(s) of data and get the same result as if it was
+ *                 encrypted in one call. This allows a "streaming" usage.
+ *                 If on the other hand you need to retain the contents of the
+ *                 IV, you should either save it manually or use the cipher
+ *                 module instead.
+ *
+ * \param ctx      AES context
+ * \param mode     MBEDTLS_AES_ENCRYPT or MBEDTLS_AES_DECRYPT
+ * \param length   length of the input data
+ * \param iv_off   offset in IV (updated after use)
+ * \param iv       initialization vector (updated after use)
+ * \param input    buffer holding the input data
+ * \param output   buffer holding the output data
+ *
+ * \return         0 if successful
+ */
+int mbedtls_aes_crypt_cfb128(mbedtls_aes_context *ctx,
+                             int                  mode,
+                             size_t               length,
+                             size_t *             iv_off,
+                             unsigned char        iv[16],
+                             const unsigned char *input,
+                             unsigned char *      output);
+
+/**
+ * \brief          AES-CFB8 buffer encryption/decryption.
+ *
+ * Note: Due to the nature of CFB you should use the same key schedule for
+ * both encryption and decryption. So a context initialized with
+ * mbedtls_aes_setkey_enc() for both MBEDTLS_AES_ENCRYPT and MBEDTLS_AES_DECRYPT.
+ *
+ * \note           Upon exit, the content of the IV is updated so that you can
+ *                 call the function same function again on the following
+ *                 block(s) of data and get the same result as if it was
+ *                 encrypted in one call. This allows a "streaming" usage.
+ *                 If on the other hand you need to retain the contents of the
+ *                 IV, you should either save it manually or use the cipher
+ *                 module instead.
+ *
+ * \param ctx      AES context
+ * \param mode     MBEDTLS_AES_ENCRYPT or MBEDTLS_AES_DECRYPT
+ * \param length   length of the input data
+ * \param iv       initialization vector (updated after use)
+ * \param input    buffer holding the input data
+ * \param output   buffer holding the output data
+ *
+ * \return         0 if successful
+ */
+int mbedtls_aes_crypt_cfb8(mbedtls_aes_context *ctx,
+                           int                  mode,
+                           size_t               length,
+                           unsigned char        iv[16],
+                           const unsigned char *input,
+                           unsigned char *      output);
+#endif /*MBEDTLS_CIPHER_MODE_CFB */
+
+#if defined(MBEDTLS_CIPHER_MODE_CTR)
+/**
+ * \brief               AES-CTR buffer encryption/decryption
+ *
+ * Warning: You have to keep the maximum use of your counter in mind!
+ *
+ * Note: Due to the nature of CTR you should use the same key schedule for
+ * both encryption and decryption. So a context initialized with
+ * mbedtls_aes_setkey_enc() for both MBEDTLS_AES_ENCRYPT and MBEDTLS_AES_DECRYPT.
+ *
+ * \param ctx           AES context
+ * \param length        The length of the data
+ * \param nc_off        The offset in the current stream_block (for resuming
+ *                      within current cipher stream). The offset pointer to
+ *                      should be 0 at the start of a stream.
+ * \param nonce_counter The 128-bit nonce and counter.
+ * \param stream_block  The saved stream-block for resuming. Is overwritten
+ *                      by the function.
+ * \param input         The input data stream
+ * \param output        The output data stream
+ *
+ * \return         0 if successful
+ */
+int mbedtls_aes_crypt_ctr(mbedtls_aes_context *ctx,
+                          size_t               length,
+                          size_t *             nc_off,
+                          unsigned char        nonce_counter[16],
+                          unsigned char        stream_block[16],
+                          const unsigned char *input,
+                          unsigned char *      output);
+#endif /* MBEDTLS_CIPHER_MODE_CTR */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MBEDTLS_AES_ALT */
+
+#endif /* aes_alt.h */
diff --git a/examples/platforms/qpg6100/crypto/ccm_alt.h b/examples/platforms/qpg6100/crypto/ccm_alt.h
new file mode 100644
index 0000000..9bd9b9b
--- /dev/null
+++ b/examples/platforms/qpg6100/crypto/ccm_alt.h
@@ -0,0 +1,163 @@
+/**
+ * \file ccm_alt.h
+ *
+ * \brief CCM combines Counter mode encryption with CBC-MAC authentication
+ *        for 128-bit block ciphers.
+ *
+ * Input to CCM includes the following elements:
+ * <ul><li>Payload - data that is both authenticated and encrypted.</li>
+ * <li>Associated data (Adata) - data that is authenticated but not
+ * encrypted, For example, a header.</li>
+ * <li>Nonce - A unique value that is assigned to the payload and the
+ * associated data.</li></ul>
+ *
+ */
+/*
+ *  Copyright (C) 2006-2018, Arm Limited (or its affiliates), All Rights Reserved
+ *  SPDX-License-Identifier: Apache-2.0
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *  not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  This file is part of Mbed TLS (https://tls.mbed.org)
+ */
+
+#ifndef MBEDTLS_CCM_ALT_H
+#define MBEDTLS_CCM_ALT_H
+
+#if defined(MBEDTLS_CCM_ALT)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+#ifndef MBEDTLS_AES_MAX_KEY_SIZE
+#define MBEDTLS_AES_MAX_KEY_SIZE 32
+#endif
+
+/**
+ * \brief    The CCM context-type definition. The CCM context is passed
+ *           to the APIs called.
+ */
+typedef struct
+{
+    uint8_t key[MBEDTLS_AES_MAX_KEY_SIZE];
+    int     keysize;
+} mbedtls_ccm_context;
+
+/**
+ * \brief           This function initializes the specified CCM context,
+ *                  to make references valid, and prepare the context
+ *                  for mbedtls_ccm_setkey() or mbedtls_ccm_free().
+ *
+ * \param ctx       The CCM context to initialize.
+ */
+void mbedtls_ccm_init(mbedtls_ccm_context *ctx);
+
+/**
+ * \brief           This function initializes the CCM context set in the
+ *                  \p ctx parameter and sets the encryption key.
+ *
+ * \param ctx       The CCM context to initialize.
+ * \param cipher    The 128-bit block cipher to use.
+ * \param key       The encryption key.
+ * \param keybits   The key size in bits. This must be acceptable by the cipher.
+ *
+ * \return          \c 0 on success, or a cipher-specific error code.
+ */
+int mbedtls_ccm_setkey(mbedtls_ccm_context *ctx,
+                       mbedtls_cipher_id_t  cipher,
+                       const unsigned char *key,
+                       unsigned int         keybits);
+
+/**
+ * \brief   This function releases and clears the specified CCM context
+ *          and underlying cipher sub-context.
+ *
+ * \param ctx       The CCM context to clear.
+ */
+void mbedtls_ccm_free(mbedtls_ccm_context *ctx);
+
+/**
+ * \brief           This function encrypts a buffer using CCM.
+ *
+ * \param ctx       The CCM context to use for encryption.
+ * \param length    The length of the input data in Bytes.
+ * \param iv        Initialization vector (nonce).
+ * \param iv_len    The length of the IV in Bytes: 7, 8, 9, 10, 11, 12, or 13.
+ * \param add       The additional data field.
+ * \param add_len   The length of additional data in Bytes.
+ *                  Must be less than 2^16 - 2^8.
+ * \param input     The buffer holding the input data.
+ * \param output    The buffer holding the output data.
+ *                  Must be at least \p length Bytes wide.
+ * \param tag       The buffer holding the tag.
+ * \param tag_len   The length of the tag to generate in Bytes:
+ *                  4, 6, 8, 10, 14 or 16.
+ *
+ * \note            The tag is written to a separate buffer. To concatenate
+ *                  the \p tag with the \p output, as done in <em>RFC-3610:
+ *                  Counter with CBC-MAC (CCM)</em>, use
+ *                  \p tag = \p output + \p length, and make sure that the
+ *                  output buffer is at least \p length + \p tag_len wide.
+ *
+ * \return          \c 0 on success.
+ */
+int mbedtls_ccm_encrypt_and_tag(mbedtls_ccm_context *ctx,
+                                size_t               length,
+                                const unsigned char *iv,
+                                size_t               iv_len,
+                                const unsigned char *add,
+                                size_t               add_len,
+                                const unsigned char *input,
+                                unsigned char *      output,
+                                unsigned char *      tag,
+                                size_t               tag_len);
+
+/**
+ * \brief           This function performs a CCM authenticated decryption of a
+ *                  buffer.
+ *
+ * \param ctx       The CCM context to use for decryption.
+ * \param length    The length of the input data in Bytes.
+ * \param iv        Initialization vector.
+ * \param iv_len    The length of the IV in Bytes: 7, 8, 9, 10, 11, 12, or 13.
+ * \param add       The additional data field.
+ * \param add_len   The length of additional data in Bytes.
+ * \param input     The buffer holding the input data.
+ * \param output    The buffer holding the output data.
+ * \param tag       The buffer holding the tag.
+ * \param tag_len   The length of the tag in Bytes.
+ *
+ * \return          0 if successful and authenticated, or
+ *                  #MBEDTLS_ERR_CCM_AUTH_FAILED if the tag does not match.
+ */
+int mbedtls_ccm_auth_decrypt(mbedtls_ccm_context *ctx,
+                             size_t               length,
+                             const unsigned char *iv,
+                             size_t               iv_len,
+                             const unsigned char *add,
+                             size_t               add_len,
+                             const unsigned char *input,
+                             unsigned char *      output,
+                             const unsigned char *tag,
+                             size_t               tag_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MBEDTLS_CCM_ALT */
+
+#endif /* MBEDTLS_CCM_ALT_H */
diff --git a/examples/platforms/qpg6100/crypto/ecjpake_alt.h b/examples/platforms/qpg6100/crypto/ecjpake_alt.h
new file mode 100644
index 0000000..b1660b9
--- /dev/null
+++ b/examples/platforms/qpg6100/crypto/ecjpake_alt.h
@@ -0,0 +1,233 @@
+/**
+ * \file ecjpake_alt.h
+ *
+ * \brief Elliptic curve J-PAKE
+ */
+/*
+ *  Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
+ *  SPDX-License-Identifier: Apache-2.0
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *  not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  This file is part of mbed TLS (https://tls.mbed.org)
+ */
+#ifndef MBEDTLS_ECJPAKE_ALT_H
+#define MBEDTLS_ECJPAKE_ALT_H
+
+#if defined(MBEDTLS_ECJPAKE_ALT)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef MBEDTLS_ECJPAKE_ALT // QORVO
+/**
+ * Roles in the EC J-PAKE exchange
+ */
+typedef enum
+{
+    MBEDTLS_ECJPAKE_CLIENT = 0, /**< Client                         */
+    MBEDTLS_ECJPAKE_SERVER,     /**< Server                         */
+} mbedtls_ecjpake_role;
+#endif // MBEDTLS_ECJPAKE_ALT // QORVO
+
+#ifndef MBEDTLS_ECJPAKE_MAX_BYTES
+
+/** The maximum size in bytes for a ECJPAKE number or coordinate.
+ *
+ * Default size works for P256 as MbedTLS and the Thread standard officially
+ * only support P256. To support larger curves, chose  MBEDTLS_ECP_MAX_BYTES.
+ */
+#define MBEDTLS_ECJPAKE_MAX_BYTES (256 / 8)
+#endif
+
+/**
+ * EC J-PAKE context structure.
+ *
+ * J-PAKE is a symmetric protocol, except for the identifiers used in
+ * Zero-Knowledge Proofs, and the serialization of the second message
+ * (KeyExchange) as defined by the Thread spec.
+ *
+ * In order to benefit from this symmetry, we choose a different naming
+ * convetion from the Thread v1.0 spec. Correspondance is indicated in the
+ * description as a pair C: client name, S: server name
+ */
+typedef struct
+{
+    mbedtls_ecp_group    grp;          /**< Elliptic curve                 */
+    mbedtls_ecjpake_role role;         /**< Are we client or server?       */
+    int                  point_format; /**< Format for point export        */
+
+    mbedtls_ecp_point Xm1; /**< My public key 1   C: X1, S: X3 */
+    mbedtls_ecp_point Xm2; /**< My public key 2   C: X2, S: X4 */
+    mbedtls_ecp_point Xp1; /**< Peer public key 1 C: X3, S: X1 */
+    mbedtls_ecp_point Xp2; /**< Peer public key 2 C: X4, S: X2 */
+    mbedtls_ecp_point Xp;  /**< Peer public key   C: Xs, S: Xc */
+
+    mbedtls_mpi xm1; /**< My private key 1  C: x1, S: x3 */
+    mbedtls_mpi xm2; /**< My private key 2  C: x2, S: x4 */
+
+    mbedtls_mpi s; /**< Pre-shared secret (passphrase) */
+
+    const struct sx_ecc_curve_t *curve;   /**< Elliptic curve for HW offload  */
+    int                          hashalg; /**< Hash algorithm for HW offload  */
+} mbedtls_ecjpake_context;
+
+#ifndef MBEDTLS_ECJPAKE_ALT // QORVO
+/**
+ * \brief           Initialize a context
+ *                  (just makes it ready for setup() or free()).
+ *
+ * \param ctx       context to initialize
+ */
+void mbedtls_ecjpake_init(mbedtls_ecjpake_context *ctx);
+
+/**
+ * \brief           Set up a context for use
+ *
+ * \note            Currently the only values for hash/curve allowed by the
+ *                  standard are MBEDTLS_MD_SHA256/MBEDTLS_ECP_DP_SECP256R1.
+ *
+ * \param ctx       context to set up
+ * \param role      Our role: client or server
+ * \param hash      hash function to use (MBEDTLS_MD_XXX)
+ * \param curve     elliptic curve identifier (MBEDTLS_ECP_DP_XXX)
+ * \param secret    pre-shared secret (passphrase)
+ * \param len       length of the shared secret
+ *
+ * \return          0 if successfull,
+ *                  a negative error code otherwise
+ */
+int mbedtls_ecjpake_setup(mbedtls_ecjpake_context *ctx,
+                          mbedtls_ecjpake_role     role,
+                          mbedtls_md_type_t        hash,
+                          mbedtls_ecp_group_id     curve,
+                          const unsigned char *    secret,
+                          size_t                   len);
+
+/**
+ * \brief           Check if a context is ready for use
+ *
+ * \param ctx       Context to check
+ *
+ * \return          0 if the context is ready for use,
+ *                  MBEDTLS_ERR_ECP_BAD_INPUT_DATA otherwise
+ */
+int mbedtls_ecjpake_check(const mbedtls_ecjpake_context *ctx);
+
+/**
+ * \brief           Generate and write the first round message
+ *                  (TLS: contents of the Client/ServerHello extension,
+ *                  excluding extension type and length bytes)
+ *
+ * \param ctx       Context to use
+ * \param buf       Buffer to write the contents to
+ * \param len       Buffer size
+ * \param olen      Will be updated with the number of bytes written
+ * \param f_rng     RNG function
+ * \param p_rng     RNG parameter
+ *
+ * \return          0 if successfull,
+ *                  a negative error code otherwise
+ */
+int mbedtls_ecjpake_write_round_one(mbedtls_ecjpake_context *ctx,
+                                    unsigned char *          buf,
+                                    size_t                   len,
+                                    size_t *                 olen,
+                                    int (*f_rng)(void *, unsigned char *, size_t),
+                                    void *p_rng);
+
+/**
+ * \brief           Read and process the first round message
+ *                  (TLS: contents of the Client/ServerHello extension,
+ *                  excluding extension type and length bytes)
+ *
+ * \param ctx       Context to use
+ * \param buf       Pointer to extension contents
+ * \param len       Extension length
+ *
+ * \return          0 if successfull,
+ *                  a negative error code otherwise
+ */
+int mbedtls_ecjpake_read_round_one(mbedtls_ecjpake_context *ctx, const unsigned char *buf, size_t len);
+
+/**
+ * \brief           Generate and write the second round message
+ *                  (TLS: contents of the Client/ServerKeyExchange)
+ *
+ * \param ctx       Context to use
+ * \param buf       Buffer to write the contents to
+ * \param len       Buffer size
+ * \param olen      Will be updated with the number of bytes written
+ * \param f_rng     RNG function
+ * \param p_rng     RNG parameter
+ *
+ * \return          0 if successfull,
+ *                  a negative error code otherwise
+ */
+int mbedtls_ecjpake_write_round_two(mbedtls_ecjpake_context *ctx,
+                                    unsigned char *          buf,
+                                    size_t                   len,
+                                    size_t *                 olen,
+                                    int (*f_rng)(void *, unsigned char *, size_t),
+                                    void *p_rng);
+
+/**
+ * \brief           Read and process the second round message
+ *                  (TLS: contents of the Client/ServerKeyExchange)
+ *
+ * \param ctx       Context to use
+ * \param buf       Pointer to the message
+ * \param len       Message length
+ *
+ * \return          0 if successfull,
+ *                  a negative error code otherwise
+ */
+int mbedtls_ecjpake_read_round_two(mbedtls_ecjpake_context *ctx, const unsigned char *buf, size_t len);
+
+/**
+ * \brief           Derive the shared secret
+ *                  (TLS: Pre-Master Secret)
+ *
+ * \param ctx       Context to use
+ * \param buf       Buffer to write the contents to
+ * \param len       Buffer size
+ * \param olen      Will be updated with the number of bytes written
+ * \param f_rng     RNG function
+ * \param p_rng     RNG parameter
+ *
+ * \return          0 if successfull,
+ *                  a negative error code otherwise
+ */
+int mbedtls_ecjpake_derive_secret(mbedtls_ecjpake_context *ctx,
+                                  unsigned char *          buf,
+                                  size_t                   len,
+                                  size_t *                 olen,
+                                  int (*f_rng)(void *, unsigned char *, size_t),
+                                  void *p_rng);
+
+/**
+ * \brief           Free a context's content
+ *
+ * \param ctx       context to free
+ */
+void mbedtls_ecjpake_free(mbedtls_ecjpake_context *ctx);
+#endif // MBEDTLS_ECJPAKE_ALT // QORVO
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* MBEDTLS_ECJPAKE_ALT */
+
+#endif /* ecjpake.h */
diff --git a/examples/platforms/qpg6100/crypto/ecp_alt.h b/examples/platforms/qpg6100/crypto/ecp_alt.h
new file mode 100644
index 0000000..afd208c
--- /dev/null
+++ b/examples/platforms/qpg6100/crypto/ecp_alt.h
@@ -0,0 +1,657 @@
+/**
+ * \file ecp_alt.h
+ *
+ * \brief Elliptic curves over GF(p)
+ *
+ *  Copyright (C) 2006-2015, ARM Limited, All Rights Reserved
+ *  SPDX-License-Identifier: Apache-2.0
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License"); you may
+ *  not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ *  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  This file is part of mbed TLS (https://tls.mbed.org)
+ */
+/* Adapted for hardware acceleration by CryptoSoc
+ * Copyright (c) 2018 Barco Silex
+ * Copyright (c) 2018 François Beerten
+ */
+
+#ifndef MBEDTLS_ECP_ALT_H
+#define MBEDTLS_ECP_ALT_H
+
+/*
+ * default mbed TLS elliptic curve arithmetic implementation
+ *
+ * (in case MBEDTLS_ECP_ALT is defined then the developer has to provide an
+ * alternative implementation for the whole module and it will replace this
+ * one.)
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef MBEDTLS_ECP_ALT // QORVO
+/**
+ * Domain parameters (curve, subgroup and generator) identifiers.
+ *
+ * Only curves over prime fields are supported.
+ *
+ * \warning This library does not support validation of arbitrary domain
+ * parameters. Therefore, only well-known domain parameters from trusted
+ * sources should be used. See mbedtls_ecp_group_load().
+ */
+typedef enum
+{
+    MBEDTLS_ECP_DP_NONE = 0,
+    MBEDTLS_ECP_DP_SECP192R1,  /*!< 192-bits NIST curve  */
+    MBEDTLS_ECP_DP_SECP224R1,  /*!< 224-bits NIST curve  */
+    MBEDTLS_ECP_DP_SECP256R1,  /*!< 256-bits NIST curve  */
+    MBEDTLS_ECP_DP_SECP384R1,  /*!< 384-bits NIST curve  */
+    MBEDTLS_ECP_DP_SECP521R1,  /*!< 521-bits NIST curve  */
+    MBEDTLS_ECP_DP_BP256R1,    /*!< 256-bits Brainpool curve */
+    MBEDTLS_ECP_DP_BP384R1,    /*!< 384-bits Brainpool curve */
+    MBEDTLS_ECP_DP_BP512R1,    /*!< 512-bits Brainpool curve */
+    MBEDTLS_ECP_DP_CURVE25519, /*!< Curve25519               */
+    MBEDTLS_ECP_DP_SECP192K1,  /*!< 192-bits "Koblitz" curve */
+    MBEDTLS_ECP_DP_SECP224K1,  /*!< 224-bits "Koblitz" curve */
+    MBEDTLS_ECP_DP_SECP256K1,  /*!< 256-bits "Koblitz" curve */
+} mbedtls_ecp_group_id;
+
+/**
+ * Number of supported curves (plus one for NONE).
+ *
+ * (Montgomery curves excluded for now.)
+ */
+#define MBEDTLS_ECP_DP_MAX 12
+
+/**
+ * Curve information for use by other modules
+ */
+typedef struct
+{
+    mbedtls_ecp_group_id grp_id;   /*!< Internal identifier        */
+    uint16_t             tls_id;   /*!< TLS NamedCurve identifier  */
+    uint16_t             bit_size; /*!< Curve size in bits         */
+    const char *         name;     /*!< Human-friendly name        */
+} mbedtls_ecp_curve_info;
+
+/**
+ * \brief           ECP point structure (jacobian coordinates)
+ *
+ * \note            All functions expect and return points satisfying
+ *                  the following condition: Z == 0 or Z == 1. (Other
+ *                  values of Z are used by internal functions only.)
+ *                  The point is zero, or "at infinity", if Z == 0.
+ *                  Otherwise, X and Y are its standard (affine) coordinates.
+ */
+typedef struct
+{
+    mbedtls_mpi X; /*!<  the point's X coordinate  */
+    mbedtls_mpi Y; /*!<  the point's Y coordinate  */
+    mbedtls_mpi Z; /*!<  the point's Z coordinate  */
+} mbedtls_ecp_point;
+#endif // MBEDTLS_ECP_ALT // QORVO
+
+/**
+ * \brief           ECP group structure
+ *
+ * We consider two types of curves equations:
+ * 1. Short Weierstrass y^2 = x^3 + A x + B     mod P   (SEC1 + RFC 4492)
+ * 2. Montgomery,       y^2 = x^3 + A x^2 + x   mod P   (Curve25519 + draft)
+ * In both cases, a generator G for a prime-order subgroup is fixed. In the
+ * short weierstrass, this subgroup is actually the whole curve, and its
+ * cardinal is denoted by N.
+ *
+ * In the case of Short Weierstrass curves, our code requires that N is an odd
+ * prime. (Use odd in mbedtls_ecp_mul() and prime in mbedtls_ecdsa_sign() for blinding.)
+ *
+ * In the case of Montgomery curves, we don't store A but (A + 2) / 4 which is
+ * the quantity actually used in the formulas. Also, nbits is not the size of N
+ * but the required size for private keys.
+ *
+ * If modp is NULL, reduction modulo P is done using a generic algorithm.
+ * Otherwise, it must point to a function that takes an mbedtls_mpi in the range
+ * 0..2^(2*pbits)-1 and transforms it in-place in an integer of little more
+ * than pbits, so that the integer may be efficiently brought in the 0..P-1
+ * range by a few additions or substractions. It must return 0 on success and
+ * non-zero on failure.
+ */
+typedef struct
+{
+    mbedtls_ecp_group_id id;    /*!<  internal group identifier                     */
+    mbedtls_mpi          P;     /*!<  prime modulus of the base field               */
+    mbedtls_mpi          A;     /*!<  1. A in the equation, or 2. (A + 2) / 4. for pkparse only. */
+    mbedtls_mpi          B;     /*!<  1. B in the equation, or 2. unused. for pkparse only. */
+    mbedtls_ecp_point    G;     /*!<  generator of the (sub)group used              */
+    mbedtls_mpi          N;     /*!<  1. the order of G, or 2. unused               */
+    size_t               pbits; /*!<  number of bits in P                           */
+    size_t               nbits; /*!<  number of bits in 1. P, or 2. private keys    */
+    unsigned int         h;     /*!<  internal: 1 if the constants are static       */
+    int (*modp)(mbedtls_mpi *); /*!<  Internally unused. NULL to build some tests. */
+    mbedtls_ecp_point *T;       /*!<  Internally unused. Needed to build benchmark. */
+    size_t             T_size;  /*!<  Internally unused. Needed to build benchmark. */
+} mbedtls_ecp_group;
+
+#ifndef MBEDTLS_ECP_ALT // QORVO
+/**
+ * \brief           ECP key pair structure
+ *
+ * A generic key pair that could be used for ECDSA, fixed ECDH, etc.
+ *
+ * \note Members purposefully in the same order as struc mbedtls_ecdsa_context.
+ */
+typedef struct
+{
+    mbedtls_ecp_group grp; /*!<  Elliptic curve and base point     */
+    mbedtls_mpi       d;   /*!<  our secret value                  */
+    mbedtls_ecp_point Q;   /*!<  our public value                  */
+} mbedtls_ecp_keypair;
+#endif // MBEDTLS_ECP_ALT // QORVO
+
+/**
+ * \name SECTION: Module settings
+ *
+ * The configuration options you can set for this module are in this section.
+ * Either change them in config.h or define them on the compiler command line.
+ * \{
+ */
+
+#if !defined(MBEDTLS_ECP_MAX_BITS)
+/**
+ * Maximum size of the groups (that is, of N and P)
+ */
+#define MBEDTLS_ECP_MAX_BITS 521 /**< Maximum bit size of groups */
+#endif
+
+#define MBEDTLS_ECP_MAX_BYTES ((MBEDTLS_ECP_MAX_BITS + 7) / 8)
+#define MBEDTLS_ECP_MAX_PT_LEN (2 * MBEDTLS_ECP_MAX_BYTES + 1)
+
+#if defined(MBEDTLS_ECP_WINDOW_SIZE)
+#error "MBEDTLS_ECP_WINDOW_SIZE not applicable for HW accel"
+#endif /* MBEDTLS_ECP_WINDOW_SIZE */
+
+#if defined(MBEDTLS_ECP_FIXED_POINT_OPTIM)
+#error "MBEDTLS_ECP_FIXED_POINT_OPTIM not applicable for HW accel"
+#endif /* MBEDTLS_ECP_FIXED_POINT_OPTIM */
+
+/* \} name SECTION: Module settings */
+
+/*
+ * Point formats, from RFC 4492's enum ECPointFormat
+ */
+#define MBEDTLS_ECP_PF_UNCOMPRESSED 0 /**< Uncompressed point format */
+#define MBEDTLS_ECP_PF_COMPRESSED 1   /**< Compressed point format */
+
+/*
+ * Some other constants from RFC 4492
+ */
+#define MBEDTLS_ECP_TLS_NAMED_CURVE 3 /**< ECCurveType's named_curve */
+
+#ifndef MBEDTLS_ECP_ALT // QORVO
+/**
+ * \brief           Get the list of supported curves in order of preferrence
+ *                  (full information)
+ *
+ * \return          A statically allocated array, the last entry is 0.
+ */
+const mbedtls_ecp_curve_info *mbedtls_ecp_curve_list(void);
+
+/**
+ * \brief           Get the list of supported curves in order of preferrence
+ *                  (grp_id only)
+ *
+ * \return          A statically allocated array,
+ *                  terminated with MBEDTLS_ECP_DP_NONE.
+ */
+const mbedtls_ecp_group_id *mbedtls_ecp_grp_id_list(void);
+
+/**
+ * \brief           Get curve information from an internal group identifier
+ *
+ * \param grp_id    A MBEDTLS_ECP_DP_XXX value
+ *
+ * \return          The associated curve information or NULL
+ */
+const mbedtls_ecp_curve_info *mbedtls_ecp_curve_info_from_grp_id(mbedtls_ecp_group_id grp_id);
+
+/**
+ * \brief           Get curve information from a TLS NamedCurve value
+ *
+ * \param tls_id    A MBEDTLS_ECP_DP_XXX value
+ *
+ * \return          The associated curve information or NULL
+ */
+const mbedtls_ecp_curve_info *mbedtls_ecp_curve_info_from_tls_id(uint16_t tls_id);
+
+/**
+ * \brief           Get curve information from a human-readable name
+ *
+ * \param name      The name
+ *
+ * \return          The associated curve information or NULL
+ */
+const mbedtls_ecp_curve_info *mbedtls_ecp_curve_info_from_name(const char *name);
+
+/**
+ * \brief           Initialize a point (as zero)
+ */
+void mbedtls_ecp_point_init(mbedtls_ecp_point *pt);
+
+/**
+ * \brief           Initialize a group (to something meaningless)
+ */
+void mbedtls_ecp_group_init(mbedtls_ecp_group *grp);
+
+/**
+ * \brief           Initialize a key pair (as an invalid one)
+ */
+void mbedtls_ecp_keypair_init(mbedtls_ecp_keypair *key);
+
+/**
+ * \brief           Free the components of a point
+ */
+void mbedtls_ecp_point_free(mbedtls_ecp_point *pt);
+
+/**
+ * \brief           Free the components of an ECP group
+ */
+void mbedtls_ecp_group_free(mbedtls_ecp_group *grp);
+
+/**
+ * \brief           Free the components of a key pair
+ */
+void mbedtls_ecp_keypair_free(mbedtls_ecp_keypair *key);
+
+/**
+ * \brief           Copy the contents of point Q into P
+ *
+ * \param P         Destination point
+ * \param Q         Source point
+ *
+ * \return          0 if successful,
+ *                  MBEDTLS_ERR_MPI_ALLOC_FAILED if memory allocation failed
+ */
+int mbedtls_ecp_copy(mbedtls_ecp_point *P, const mbedtls_ecp_point *Q);
+
+/**
+ * \brief           Copy the contents of a group object
+ *
+ * \param dst       Destination group
+ * \param src       Source group
+ *
+ * \return          0 if successful,
+ *                  MBEDTLS_ERR_MPI_ALLOC_FAILED if memory allocation failed
+ */
+int mbedtls_ecp_group_copy(mbedtls_ecp_group *dst, const mbedtls_ecp_group *src);
+
+/**
+ * \brief           Set a point to zero
+ *
+ * \param pt        Destination point
+ *
+ * \return          0 if successful,
+ *                  MBEDTLS_ERR_MPI_ALLOC_FAILED if memory allocation failed
+ */
+int mbedtls_ecp_set_zero(mbedtls_ecp_point *pt);
+
+/**
+ * \brief           Tell if a point is zero
+ *
+ * \param pt        Point to test
+ *
+ * \return          1 if point is zero, 0 otherwise
+ */
+int mbedtls_ecp_is_zero(mbedtls_ecp_point *pt);
+
+/**
+ * \brief           Compare two points
+ *
+ * \note            This assumes the points are normalized. Otherwise,
+ *                  they may compare as "not equal" even if they are.
+ *
+ * \param P         First point to compare
+ * \param Q         Second point to compare
+ *
+ * \return          0 if the points are equal,
+ *                  MBEDTLS_ERR_ECP_BAD_INPUT_DATA otherwise
+ */
+int mbedtls_ecp_point_cmp(const mbedtls_ecp_point *P, const mbedtls_ecp_point *Q);
+
+/**
+ * \brief           Import a non-zero point from two ASCII strings
+ *
+ * \param P         Destination point
+ * \param radix     Input numeric base
+ * \param x         First affine coordinate as a null-terminated string
+ * \param y         Second affine coordinate as a null-terminated string
+ *
+ * \return          0 if successful, or a MBEDTLS_ERR_MPI_XXX error code
+ */
+int mbedtls_ecp_point_read_string(mbedtls_ecp_point *P, int radix, const char *x, const char *y);
+
+/**
+ * \brief           Export a point into unsigned binary data
+ *
+ * \param grp       Group to which the point should belong
+ * \param P         Point to export
+ * \param format    Point format, should be a MBEDTLS_ECP_PF_XXX macro
+ * \param olen      Length of the actual output
+ * \param buf       Output buffer
+ * \param buflen    Length of the output buffer
+ *
+ * \return          0 if successful,
+ *                  or MBEDTLS_ERR_ECP_BAD_INPUT_DATA
+ *                  or MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL
+ */
+int mbedtls_ecp_point_write_binary(const mbedtls_ecp_group *grp,
+                                   const mbedtls_ecp_point *P,
+                                   int                      format,
+                                   size_t *                 olen,
+                                   unsigned char *          buf,
+                                   size_t                   buflen);
+
+/**
+ * \brief           Import a point from unsigned binary data
+ *
+ * \param grp       Group to which the point should belong
+ * \param P         Point to import
+ * \param buf       Input buffer
+ * \param ilen      Actual length of input
+ *
+ * \return          0 if successful,
+ *                  MBEDTLS_ERR_ECP_BAD_INPUT_DATA if input is invalid,
+ *                  MBEDTLS_ERR_MPI_ALLOC_FAILED if memory allocation failed,
+ *                  MBEDTLS_ERR_ECP_FEATURE_UNAVAILABLE if the point format
+ *                  is not implemented.
+ *
+ * \note            This function does NOT check that the point actually
+ *                  belongs to the given group, see mbedtls_ecp_check_pubkey() for
+ *                  that.
+ */
+int mbedtls_ecp_point_read_binary(const mbedtls_ecp_group *grp,
+                                  mbedtls_ecp_point *      P,
+                                  const unsigned char *    buf,
+                                  size_t                   ilen);
+
+/**
+ * \brief           Import a point from a TLS ECPoint record
+ *
+ * \param grp       ECP group used
+ * \param pt        Destination point
+ * \param buf       $(Start of input buffer)
+ * \param len       Buffer length
+ *
+ * \note            buf is updated to point right after the ECPoint on exit
+ *
+ * \return          0 if successful,
+ *                  MBEDTLS_ERR_MPI_XXX if initialization failed
+ *                  MBEDTLS_ERR_ECP_BAD_INPUT_DATA if input is invalid
+ */
+int mbedtls_ecp_tls_read_point(const mbedtls_ecp_group *grp,
+                               mbedtls_ecp_point *      pt,
+                               const unsigned char **   buf,
+                               size_t                   len);
+
+/**
+ * \brief           Export a point as a TLS ECPoint record
+ *
+ * \param grp       ECP group used
+ * \param pt        Point to export
+ * \param format    Export format
+ * \param olen      length of data written
+ * \param buf       Buffer to write to
+ * \param blen      Buffer length
+ *
+ * \return          0 if successful,
+ *                  or MBEDTLS_ERR_ECP_BAD_INPUT_DATA
+ *                  or MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL
+ */
+int mbedtls_ecp_tls_write_point(const mbedtls_ecp_group *grp,
+                                const mbedtls_ecp_point *pt,
+                                int                      format,
+                                size_t *                 olen,
+                                unsigned char *          buf,
+                                size_t                   blen);
+
+/**
+ * \brief           Set a group using well-known domain parameters
+ *
+ * \param grp       Destination group
+ * \param id        Index in the list of well-known domain parameters
+ *
+ * \return          0 if successful,
+ *                  MBEDTLS_ERR_MPI_XXX if initialization failed
+ *                  MBEDTLS_ERR_ECP_FEATURE_UNAVAILABLE for unkownn groups
+ *
+ * \note            Index should be a value of RFC 4492's enum NamedCurve,
+ *                  usually in the form of a MBEDTLS_ECP_DP_XXX macro.
+ */
+int mbedtls_ecp_group_load(mbedtls_ecp_group *grp, mbedtls_ecp_group_id id);
+
+/**
+ * \brief           Set a group from a TLS ECParameters record
+ *
+ * \param grp       Destination group
+ * \param buf       &(Start of input buffer)
+ * \param len       Buffer length
+ *
+ * \note            buf is updated to point right after ECParameters on exit
+ *
+ * \return          0 if successful,
+ *                  MBEDTLS_ERR_MPI_XXX if initialization failed
+ *                  MBEDTLS_ERR_ECP_BAD_INPUT_DATA if input is invalid
+ */
+int mbedtls_ecp_tls_read_group(mbedtls_ecp_group *grp, const unsigned char **buf, size_t len);
+
+/**
+ * \brief           Write the TLS ECParameters record for a group
+ *
+ * \param grp       ECP group used
+ * \param olen      Number of bytes actually written
+ * \param buf       Buffer to write to
+ * \param blen      Buffer length
+ *
+ * \return          0 if successful,
+ *                  or MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL
+ */
+int mbedtls_ecp_tls_write_group(const mbedtls_ecp_group *grp, size_t *olen, unsigned char *buf, size_t blen);
+
+/**
+ * \brief           Multiplication by an integer: R = m * P
+ *                  (Not thread-safe to use same group in multiple threads)
+ *
+ * \note            In order to prevent timing attacks, this function
+ *                  executes the exact same sequence of (base field)
+ *                  operations for any valid m. It avoids any if-branch or
+ *                  array index depending on the value of m.
+ *
+ * \note            If f_rng is not NULL, it is used to randomize intermediate
+ *                  results in order to prevent potential timing attacks
+ *                  targeting these results. It is recommended to always
+ *                  provide a non-NULL f_rng (the overhead is negligible).
+ *
+ * \param grp       ECP group
+ * \param R         Destination point
+ * \param m         Integer by which to multiply
+ * \param P         Point to multiply
+ * \param f_rng     RNG function (see notes)
+ * \param p_rng     RNG parameter
+ *
+ * \return          0 if successful,
+ *                  MBEDTLS_ERR_ECP_INVALID_KEY if m is not a valid privkey
+ *                  or P is not a valid pubkey,
+ *                  MBEDTLS_ERR_MPI_ALLOC_FAILED if memory allocation failed
+ */
+int mbedtls_ecp_mul(mbedtls_ecp_group *      grp,
+                    mbedtls_ecp_point *      R,
+                    const mbedtls_mpi *      m,
+                    const mbedtls_ecp_point *P,
+                    int (*f_rng)(void *, unsigned char *, size_t),
+                    void *p_rng);
+
+/**
+ * \brief           Multiplication and addition of two points by integers:
+ *                  R = m * P + n * Q
+ *                  (Not thread-safe to use same group in multiple threads)
+ *
+ * \note            In contrast to mbedtls_ecp_mul(), this function does not guarantee
+ *                  a constant execution flow and timing.
+ *
+ * \param grp       ECP group
+ * \param R         Destination point
+ * \param m         Integer by which to multiply P
+ * \param P         Point to multiply by m
+ * \param n         Integer by which to multiply Q
+ * \param Q         Point to be multiplied by n
+ *
+ * \return          0 if successful,
+ *                  MBEDTLS_ERR_ECP_INVALID_KEY if m or n is not a valid privkey
+ *                  or P or Q is not a valid pubkey,
+ *                  MBEDTLS_ERR_MPI_ALLOC_FAILED if memory allocation failed
+ */
+int mbedtls_ecp_muladd(mbedtls_ecp_group *      grp,
+                       mbedtls_ecp_point *      R,
+                       const mbedtls_mpi *      m,
+                       const mbedtls_ecp_point *P,
+                       const mbedtls_mpi *      n,
+                       const mbedtls_ecp_point *Q);
+
+/**
+ * \brief           Check that a point is a valid public key on this curve
+ *
+ * \param grp       Curve/group the point should belong to
+ * \param pt        Point to check
+ *
+ * \return          0 if point is a valid public key,
+ *                  MBEDTLS_ERR_ECP_INVALID_KEY otherwise.
+ *
+ * \note            This function only checks the point is non-zero, has valid
+ *                  coordinates and lies on the curve, but not that it is
+ *                  indeed a multiple of G. This is additional check is more
+ *                  expensive, isn't required by standards, and shouldn't be
+ *                  necessary if the group used has a small cofactor. In
+ *                  particular, it is useless for the NIST groups which all
+ *                  have a cofactor of 1.
+ *
+ * \note            Uses bare components rather than an mbedtls_ecp_keypair structure
+ *                  in order to ease use with other structures such as
+ *                  mbedtls_ecdh_context of mbedtls_ecdsa_context.
+ */
+int mbedtls_ecp_check_pubkey(const mbedtls_ecp_group *grp, const mbedtls_ecp_point *pt);
+
+/**
+ * \brief           Check that an mbedtls_mpi is a valid private key for this curve
+ *
+ * \param grp       Group used
+ * \param d         Integer to check
+ *
+ * \return          0 if point is a valid private key,
+ *                  MBEDTLS_ERR_ECP_INVALID_KEY otherwise.
+ *
+ * \note            Uses bare components rather than an mbedtls_ecp_keypair structure
+ *                  in order to ease use with other structures such as
+ *                  mbedtls_ecdh_context of mbedtls_ecdsa_context.
+ */
+int mbedtls_ecp_check_privkey(const mbedtls_ecp_group *grp, const mbedtls_mpi *d);
+
+/**
+ * \brief           Generate a keypair with configurable base point
+ *
+ * \param grp       ECP group
+ * \param G         Chosen base point
+ * \param d         Destination MPI (secret part)
+ * \param Q         Destination point (public part)
+ * \param f_rng     RNG function
+ * \param p_rng     RNG parameter
+ *
+ * \return          0 if successful,
+ *                  or a MBEDTLS_ERR_ECP_XXX or MBEDTLS_MPI_XXX error code
+ *
+ * \note            Uses bare components rather than an mbedtls_ecp_keypair structure
+ *                  in order to ease use with other structures such as
+ *                  mbedtls_ecdh_context of mbedtls_ecdsa_context.
+ */
+int mbedtls_ecp_gen_keypair_base(mbedtls_ecp_group *      grp,
+                                 const mbedtls_ecp_point *G,
+                                 mbedtls_mpi *            d,
+                                 mbedtls_ecp_point *      Q,
+                                 int (*f_rng)(void *, unsigned char *, size_t),
+                                 void *p_rng);
+
+/**
+ * \brief           Generate a keypair
+ *
+ * \param grp       ECP group
+ * \param d         Destination MPI (secret part)
+ * \param Q         Destination point (public part)
+ * \param f_rng     RNG function
+ * \param p_rng     RNG parameter
+ *
+ * \return          0 if successful,
+ *                  or a MBEDTLS_ERR_ECP_XXX or MBEDTLS_MPI_XXX error code
+ *
+ * \note            Uses bare components rather than an mbedtls_ecp_keypair structure
+ *                  in order to ease use with other structures such as
+ *                  mbedtls_ecdh_context of mbedtls_ecdsa_context.
+ */
+int mbedtls_ecp_gen_keypair(mbedtls_ecp_group *grp,
+                            mbedtls_mpi *      d,
+                            mbedtls_ecp_point *Q,
+                            int (*f_rng)(void *, unsigned char *, size_t),
+                            void *p_rng);
+
+/**
+ * \brief           Generate a keypair
+ *
+ * \param grp_id    ECP group identifier
+ * \param key       Destination keypair
+ * \param f_rng     RNG function
+ * \param p_rng     RNG parameter
+ *
+ * \return          0 if successful,
+ *                  or a MBEDTLS_ERR_ECP_XXX or MBEDTLS_MPI_XXX error code
+ */
+int mbedtls_ecp_gen_key(mbedtls_ecp_group_id grp_id,
+                        mbedtls_ecp_keypair *key,
+                        int (*f_rng)(void *, unsigned char *, size_t),
+                        void *p_rng);
+
+/**
+ * \brief           Check a public-private key pair
+ *
+ * \param pub       Keypair structure holding a public key
+ * \param prv       Keypair structure holding a private (plus public) key
+ *
+ * \return          0 if successful (keys are valid and match), or
+ *                  MBEDTLS_ERR_ECP_BAD_INPUT_DATA, or
+ *                  a MBEDTLS_ERR_ECP_XXX or MBEDTLS_ERR_MPI_XXX code.
+ */
+int mbedtls_ecp_check_pub_priv(const mbedtls_ecp_keypair *pub, const mbedtls_ecp_keypair *prv);
+#endif // MBEDTLS_ECP_ALT // QORVO
+
+#if defined(MBEDTLS_SELF_TEST)
+
+/**
+ * \brief          Checkup routine
+ *
+ * \return         0 if successful, or 1 if a test failed
+ */
+int mbedtls_ecp_self_test(int verbose);
+
+#endif /* MBEDTLS_SELF_TEST */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ecp_alt.h */
diff --git a/examples/platforms/qpg6100/crypto/qpg6100-mbedtls-config.h b/examples/platforms/qpg6100/crypto/qpg6100-mbedtls-config.h
new file mode 100644
index 0000000..9830b8c
--- /dev/null
+++ b/examples/platforms/qpg6100/crypto/qpg6100-mbedtls-config.h
@@ -0,0 +1,52 @@
+/*
+ *  Copyright (c) 2019, The OpenThread Authors.
+ *  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.
+ */
+
+#ifndef QGP6100_MBEDTLS_CONFIG_H
+#define QGP6100_MBEDTLS_CONFIG_H
+
+#ifdef QORVO_CRYPTO_ENGINE
+
+#undef MBEDTLS_ECP_WINDOW_SIZE
+#undef MBEDTLS_ECP_FIXED_POINT_OPTIM
+
+#define MBEDTLS_AES_ALT
+#define MBEDTLS_CCM_ALT
+#define MBEDTLS_ECP_ALT
+#define MBEDTLS_ECJPAKE_ALT
+#define MBEDTLS_SHA256_ALT
+
+#else
+
+#define MBEDTLS_SLOW_CPU 1
+#define MBEDTLS_COMPUTATION_UNTILL_SEQ_NR 6
+
+#endif // QORVO_CRYPTO_ENGINE
+
+#include "mbedtls/check_config.h"
+
+#endif // QGP6100_MBEDTLS_CONFIG_H
diff --git a/examples/platforms/qpg6100/crypto/sha256_alt.h b/examples/platforms/qpg6100/crypto/sha256_alt.h
new file mode 100644
index 0000000..5d051e3
--- /dev/null
+++ b/examples/platforms/qpg6100/crypto/sha256_alt.h
@@ -0,0 +1,194 @@
+/**
+ * \file mbedtls_sha256.h
+ *
+ * \brief SHA-224 and SHA-256 cryptographic hash function
+ *
+ *  Copyright (C) 2006-2014, ARM Limited, All Rights Reserved
+ *
+ *  This file is part of mbed TLS (https://tls.mbed.org)
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef MBEDTLS_SHA256_ALT_H
+#define MBEDTLS_SHA256_ALT_H
+
+#if !defined(MBEDTLS_CONFIG_FILE)
+#include "config.h"
+#else
+#include MBEDTLS_CONFIG_FILE
+#endif
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \brief          SHA-256 context structure
+ */
+typedef struct
+{
+    uint32_t      total[2];   /*!< number of bytes processed  */
+    uint32_t      state[8];   /*!< intermediate digest state  */
+    unsigned char buffer[64]; /*!< data block being processed */
+    int           is224;      /*!< 0 => SHA-256, else SHA-224 */
+} mbedtls_sha256_context;
+
+/**
+ * \brief          This function initializes a SHA-256 context.
+ *
+ * \param ctx      The SHA-256 context to initialize.
+ */
+void mbedtls_sha256_init(mbedtls_sha256_context *ctx);
+
+/**
+ * \brief          This function clears a SHA-256 context.
+ *
+ * \param ctx      The SHA-256 context to clear.
+ */
+void mbedtls_sha256_free(mbedtls_sha256_context *ctx);
+
+/**
+ * \brief          This function clones the state of a SHA-256 context.
+ *
+ * \param dst      The destination context.
+ * \param src      The context to clone.
+ */
+void mbedtls_sha256_clone(mbedtls_sha256_context *dst, const mbedtls_sha256_context *src);
+
+/**
+ * \brief          This function starts a SHA-224 or SHA-256 checksum
+ *                 calculation.
+ *
+ * \param ctx      The context to initialize.
+ * \param is224    Determines which function to use.
+ *                 <ul><li>0: Use SHA-256.</li>
+ *                 <li>1: Use SHA-224.</li></ul>
+ *
+ * \return         \c 0 on success.
+ */
+int mbedtls_sha256_starts_ret(mbedtls_sha256_context *ctx, int is224);
+
+/**
+ * \brief          This function feeds an input buffer into an ongoing
+ *                 SHA-256 checksum calculation.
+ *
+ * \param ctx      SHA-256 context
+ * \param input    buffer holding the data
+ * \param ilen     length of the input data
+ *
+ * \return         \c 0 on success.
+ */
+int mbedtls_sha256_update_ret(mbedtls_sha256_context *ctx, const unsigned char *input, size_t ilen);
+
+/**
+ * \brief          This function finishes the SHA-256 operation, and writes
+ *                 the result to the output buffer.
+ *
+ * \param ctx      The SHA-256 context.
+ * \param output   The SHA-224 or SHA-256 checksum result.
+ *
+ * \return         \c 0 on success.
+ */
+int mbedtls_sha256_finish_ret(mbedtls_sha256_context *ctx, unsigned char output[32]);
+
+/**
+ * \brief          This function processes a single data block within
+ *                 the ongoing SHA-256 computation. This function is for
+ *                 internal use only.
+ *
+ * \param ctx      The SHA-256 context.
+ * \param data     The buffer holding one block of data.
+ *
+ * \return         \c 0 on success.
+ */
+int mbedtls_internal_sha256_process(mbedtls_sha256_context *ctx, const unsigned char data[64]);
+
+#if !defined(MBEDTLS_DEPRECATED_REMOVED)
+#if defined(MBEDTLS_DEPRECATED_WARNING)
+#define MBEDTLS_DEPRECATED __attribute__((deprecated))
+#else
+#define MBEDTLS_DEPRECATED
+#endif
+/**
+ * \brief          This function starts a SHA-256 checksum calculation.
+ *
+ * \deprecated     Superseded by mbedtls_sha256_starts_ret() in 2.7.0.
+ *
+ * \param ctx      The SHA-256 context to initialize.
+ * \param is224    Determines which function to use.
+ *                 <ul><li>0: Use SHA-256.</li>
+ *                 <li>1: Use SHA-224.</li></ul>
+ */
+MBEDTLS_DEPRECATED static inline void mbedtls_sha256_starts(mbedtls_sha256_context *ctx, int is224)
+{
+    mbedtls_sha256_starts_ret(ctx, is224);
+}
+
+/**
+ * \brief          This function feeds an input buffer into an ongoing
+ *                 SHA-256 checksum calculation.
+ *
+ * \deprecated     Superseded by mbedtls_sha256_update_ret() in 2.7.0.
+ *
+ * \param ctx      The SHA-256 context to initialize.
+ * \param input    The buffer holding the data.
+ * \param ilen     The length of the input data.
+ */
+MBEDTLS_DEPRECATED static inline void mbedtls_sha256_update(mbedtls_sha256_context *ctx,
+                                                            const unsigned char *   input,
+                                                            size_t                  ilen)
+{
+    mbedtls_sha256_update_ret(ctx, input, ilen);
+}
+
+/**
+ * \brief          This function finishes the SHA-256 operation, and writes
+ *                 the result to the output buffer.
+ *
+ * \deprecated     Superseded by mbedtls_sha256_finish_ret() in 2.7.0.
+ *
+ * \param ctx      The SHA-256 context.
+ * \param output   The SHA-224or SHA-256 checksum result.
+ */
+MBEDTLS_DEPRECATED static inline void mbedtls_sha256_finish(mbedtls_sha256_context *ctx, unsigned char output[32])
+{
+    mbedtls_sha256_finish_ret(ctx, output);
+}
+
+/**
+ * \brief          This function processes a single data block within
+ *                 the ongoing SHA-256 computation. This function is for
+ *                 internal use only.
+ *
+ * \deprecated     Superseded by mbedtls_internal_sha256_process() in 2.7.0.
+ *
+ * \param ctx      The SHA-256 context.
+ * \param data     The buffer holding one block of data.
+ */
+MBEDTLS_DEPRECATED static inline void mbedtls_sha256_process(mbedtls_sha256_context *ctx, const unsigned char data[64])
+{
+    mbedtls_internal_sha256_process(ctx, data);
+}
+
+#undef MBEDTLS_DEPRECATED
+#endif /* !MBEDTLS_DEPRECATED_REMOVED */
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/examples/platforms/qpg6100/diag.c b/examples/platforms/qpg6100/diag.c
new file mode 120000
index 0000000..5e48d47
--- /dev/null
+++ b/examples/platforms/qpg6100/diag.c
@@ -0,0 +1 @@
+../qpg6095/diag.c
\ No newline at end of file
diff --git a/examples/platforms/qpg6100/entropy.c b/examples/platforms/qpg6100/entropy.c
new file mode 120000
index 0000000..7a6cd5a
--- /dev/null
+++ b/examples/platforms/qpg6100/entropy.c
@@ -0,0 +1 @@
+../qpg6095/entropy.c
\ No newline at end of file
diff --git a/examples/platforms/qpg6100/logging.c b/examples/platforms/qpg6100/logging.c
new file mode 120000
index 0000000..57dabcc
--- /dev/null
+++ b/examples/platforms/qpg6100/logging.c
@@ -0,0 +1 @@
+../qpg6095/logging.c
\ No newline at end of file
diff --git a/examples/platforms/qpg6100/misc.c b/examples/platforms/qpg6100/misc.c
new file mode 120000
index 0000000..cba8d92
--- /dev/null
+++ b/examples/platforms/qpg6100/misc.c
@@ -0,0 +1 @@
+../qpg6095/misc.c
\ No newline at end of file
diff --git a/examples/platforms/qpg6100/misc_qorvo.h b/examples/platforms/qpg6100/misc_qorvo.h
new file mode 120000
index 0000000..460e055
--- /dev/null
+++ b/examples/platforms/qpg6100/misc_qorvo.h
@@ -0,0 +1 @@
+../qpg6095/misc_qorvo.h
\ No newline at end of file
diff --git a/examples/platforms/qpg6100/openthread-core-qpg6100-config-check.h b/examples/platforms/qpg6100/openthread-core-qpg6100-config-check.h
new file mode 100644
index 0000000..0ec3b40
--- /dev/null
+++ b/examples/platforms/qpg6100/openthread-core-qpg6100-config-check.h
@@ -0,0 +1,40 @@
+/*
+ *  Copyright (c) 2019, The OpenThread Authors.
+ *  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.
+ */
+
+#ifndef OPENTHREAD_CORE_QPG6100_CONFIG_CHECK_H_
+#define OPENTHREAD_CORE_QPG6100_CONFIG_CHECK_H_
+
+#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
+#error "Platform qpg6100 doesn't support configuration option: OPENTHREAD_CONFIG_TIME_SYNC_ENABLE"
+#endif
+
+#if OPENTHREAD_CONFIG_RADIO_915MHZ_OQPSK_SUPPORT
+#error "Platform qpg6100 doesn't support configuration option: OPENTHREAD_CONFIG_RADIO_915MHZ_OQPSK_SUPPORT"
+#endif
+
+#endif /* OPENTHREAD_CORE_QPG6100_CONFIG_CHECK_H_ */
diff --git a/examples/platforms/qpg6100/openthread-core-qpg6100-config.h b/examples/platforms/qpg6100/openthread-core-qpg6100-config.h
new file mode 100644
index 0000000..be3b33b
--- /dev/null
+++ b/examples/platforms/qpg6100/openthread-core-qpg6100-config.h
@@ -0,0 +1,53 @@
+/*
+ *  Copyright (c) 2019, The OpenThread Authors.
+ *  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
+ *   This file includes qpg6100 compile-time configuration constants for OpenThread.
+ */
+
+#ifndef OPENTHREAD_CORE_QPG6100_CONFIG_H_
+#define OPENTHREAD_CORE_QPG6100_CONFIG_H_
+
+/**
+ * @def OPENTHREAD_CONFIG_LEGACY_TRANSMIT_DONE
+ *
+ * Define to 1 if you want use legacy transmit done.
+ *
+ */
+#define OPENTHREAD_CONFIG_LEGACY_TRANSMIT_DONE 1
+
+/**
+ * @def OPENTHREAD_CONFIG_PLATFORM_INFO
+ *
+ * The platform-specific string to insert into the OpenThread version string.
+ *
+ */
+#define OPENTHREAD_CONFIG_PLATFORM_INFO "QPG6100"
+
+#endif // OPENTHREAD_CORE_QPG6100_CONFIG_H_
diff --git a/examples/platforms/qpg6100/platform.c b/examples/platforms/qpg6100/platform.c
new file mode 120000
index 0000000..e8cd210
--- /dev/null
+++ b/examples/platforms/qpg6100/platform.c
@@ -0,0 +1 @@
+../qpg6095/platform.c
\ No newline at end of file
diff --git a/examples/platforms/qpg6100/platform_qorvo.h b/examples/platforms/qpg6100/platform_qorvo.h
new file mode 120000
index 0000000..965c015
--- /dev/null
+++ b/examples/platforms/qpg6100/platform_qorvo.h
@@ -0,0 +1 @@
+../qpg6095/platform_qorvo.h
\ No newline at end of file
diff --git a/examples/platforms/qpg6100/radio.c b/examples/platforms/qpg6100/radio.c
new file mode 120000
index 0000000..211d358
--- /dev/null
+++ b/examples/platforms/qpg6100/radio.c
@@ -0,0 +1 @@
+../qpg6095/radio.c
\ No newline at end of file
diff --git a/examples/platforms/qpg6100/radio_qorvo.h b/examples/platforms/qpg6100/radio_qorvo.h
new file mode 120000
index 0000000..8af682a
--- /dev/null
+++ b/examples/platforms/qpg6100/radio_qorvo.h
@@ -0,0 +1 @@
+../qpg6095/radio_qorvo.h
\ No newline at end of file
diff --git a/examples/platforms/qpg6100/random_qorvo.h b/examples/platforms/qpg6100/random_qorvo.h
new file mode 120000
index 0000000..92af247
--- /dev/null
+++ b/examples/platforms/qpg6100/random_qorvo.h
@@ -0,0 +1 @@
+../qpg6095/random_qorvo.h
\ No newline at end of file
diff --git a/examples/platforms/qpg6100/settings.cpp b/examples/platforms/qpg6100/settings.cpp
new file mode 120000
index 0000000..1d0596d
--- /dev/null
+++ b/examples/platforms/qpg6100/settings.cpp
@@ -0,0 +1 @@
+../qpg6095/settings.cpp
\ No newline at end of file
diff --git a/examples/platforms/qpg6100/settings_qorvo.h b/examples/platforms/qpg6100/settings_qorvo.h
new file mode 120000
index 0000000..0b689fc
--- /dev/null
+++ b/examples/platforms/qpg6100/settings_qorvo.h
@@ -0,0 +1 @@
+../qpg6095/settings_qorvo.h
\ No newline at end of file
diff --git a/examples/platforms/qpg6100/uart.c b/examples/platforms/qpg6100/uart.c
new file mode 120000
index 0000000..dd4e551
--- /dev/null
+++ b/examples/platforms/qpg6100/uart.c
@@ -0,0 +1 @@
+../qpg6095/uart.c
\ No newline at end of file
diff --git a/examples/platforms/qpg6100/uart_qorvo.h b/examples/platforms/qpg6100/uart_qorvo.h
new file mode 120000
index 0000000..cad2bfd
--- /dev/null
+++ b/examples/platforms/qpg6100/uart_qorvo.h
@@ -0,0 +1 @@
+../qpg6095/uart_qorvo.h
\ No newline at end of file
diff --git a/examples/platforms/qpg7015m/CMakeLists.txt b/examples/platforms/qpg7015m/CMakeLists.txt
new file mode 100644
index 0000000..1f2b9f2
--- /dev/null
+++ b/examples/platforms/qpg7015m/CMakeLists.txt
@@ -0,0 +1,105 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+set(OT_PLATFORM_LIB "openthread-qpg7015m" PARENT_SCOPE)
+
+if(NOT OT_CONFIG)
+    set(OT_CONFIG "openthread-core-qpg7015m-config.h")
+    set(OT_CONFIG ${OT_CONFIG} PARENT_SCOPE)
+endif()
+
+list(APPEND OT_PLATFORM_DEFINES
+    "OPENTHREAD_CORE_CONFIG_PLATFORM_CHECK_FILE=\"openthread-core-qpg7015m-config-check.h\""
+)
+
+list(APPEND OT_PLATFORM_DEFINES
+    "_BSD_SOURCE=1"
+    "_DEFAULT_SOURCE=1"
+)
+
+set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE)
+
+set(OT_PUBLIC_INCLUDES ${OT_PUBLIC_INCLUDES} PARENT_SCOPE)
+
+if(OT_CFLAGS MATCHES "-pedantic-errors")
+    string(REPLACE "-pedantic-errors" "" OT_CFLAGS "${OT_CFLAGS}")
+endif()
+
+if(OT_CFLAGS MATCHES "-Wno-c\\+\\+14-compat")
+    string(REPLACE "-Wno-c++14-compat" "" OT_CFLAGS "${OT_CFLAGS}")
+endif()
+
+list(APPEND OT_PLATFORM_DEFINES "OPENTHREAD_PROJECT_CORE_CONFIG_FILE=\"${OT_CONFIG}\"")
+
+add_library(openthread-qpg7015m
+    alarm.c
+    diag.c     
+    entropy.c
+    flash.c
+    logging.c
+    misc.c
+    radio.c
+    system.c
+    uart-posix.c
+)
+
+set_target_properties(
+    openthread-qpg7015m
+    PROPERTIES
+        C_STANDARD 99
+        CXX_STANDARD 11
+)
+
+target_link_libraries(openthread-qpg7015m
+    PRIVATE
+        qpg7015m-driver
+        ${OT_MBEDTLS}
+        ot-config
+    PUBLIC
+        -lrt
+        -pthread
+        -Wl,--gc-sections
+        -Wl,-Map=$<TARGET_PROPERTY:NAME>.map
+)
+
+target_compile_definitions(openthread-qpg7015m
+    PUBLIC
+        ${OT_PLATFORM_DEFINES}
+)
+
+target_compile_options(openthread-qpg7015m
+    PRIVATE
+        ${OT_CFLAGS}
+)
+
+target_include_directories(openthread-qpg7015m
+    PRIVATE
+        ${OT_PUBLIC_INCLUDES}
+        ${PROJECT_SOURCE_DIR}/src/core
+        ${PROJECT_SOURCE_DIR}/examples/platforms
+)
diff --git a/examples/platforms/qpg7015m/Makefile.am b/examples/platforms/qpg7015m/Makefile.am
new file mode 100755
index 0000000..11c0a6b
--- /dev/null
+++ b/examples/platforms/qpg7015m/Makefile.am
@@ -0,0 +1,73 @@
+#
+#  Copyright (c) 2019, The OpenThread Authors.
+#  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.
+#
+
+include $(abs_top_nlbuild_autotools_dir)/automake/pre.am
+
+# Do not enable -Wcast-align for this platform
+override CFLAGS    := $(filter-out -Wcast-align,$(CFLAGS))
+override CXXFLAGS  := $(filter-out -Wcast-align,$(CXXFLAGS))
+
+lib_LIBRARIES                                   = libopenthread-qpg7015m.a
+
+libopenthread_qpg7015m_a_CPPFLAGS               = \
+    -I$(top_srcdir)/include                       \
+    -I$(top_srcdir)/src/core                      \
+    -I$(top_srcdir)/examples/platforms            \
+    -I$(top_srcdir)/examples/platforms/qpg7015m   \
+    -lrt                                          \
+    -pthread                                      \
+    $(NULL)
+
+PLATFORM_SOURCES                                = \
+    alarm.c                                       \
+    alarm_qorvo.h                                 \
+    diag.c                                        \
+    entropy.c                                     \
+    flash.c                                       \
+    logging.c                                     \
+    misc.c                                        \
+    openthread-core-qpg7015m-config.h             \
+    openthread-core-qpg7015m-config-check.h       \
+    platform_qorvo.h                              \
+    radio.c                                       \
+    radio_qorvo.h                                 \
+    random_qorvo.h                                \
+    system.c                                      \
+    uart-posix.c                                  \
+    uart_qorvo.h                                  \
+    $(NULL)
+
+libopenthread_qpg7015m_a_SOURCES                = \
+    $(PLATFORM_SOURCES)                           \
+    $(NULL)
+
+if OPENTHREAD_BUILD_COVERAGE
+CLEANFILES                                      = $(wildcard *.gcda *.gcno)
+endif # OPENTHREAD_BUILD_COVERAGE
+
+include $(abs_top_nlbuild_autotools_dir)/automake/post.am
diff --git a/tests/scripts/expect/v1_2-rcp.exp b/examples/platforms/qpg7015m/Makefile.platform.am
similarity index 67%
copy from tests/scripts/expect/v1_2-rcp.exp
copy to examples/platforms/qpg7015m/Makefile.platform.am
index 776d880..0b62f4b 100755
--- a/tests/scripts/expect/v1_2-rcp.exp
+++ b/examples/platforms/qpg7015m/Makefile.platform.am
@@ -1,6 +1,5 @@
-#!/usr/bin/expect -f
 #
-#  Copyright (c) 2020, The OpenThread Authors.
+#  Copyright (c) 2019, The OpenThread Authors.
 #  All rights reserved.
 #
 #  Redistribution and use in source and binary forms, with or without
@@ -27,39 +26,21 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
-source "tests/scripts/expect/_common.exp"
+#
+# qpg7015m platform-specific Makefile
+#
 
-spawn_node 1
-
-send "panid 0xface\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-wait_for "state" "leader"
-expect "Done"
-
-send "ipaddr mleid\n"
-expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-set addr $expect_out(1,string)
-expect "Done"
-
-spawn_node 2 mtd
-
-send "panid 0xface\n"
-expect "Done"
-send "mode -\n"
-expect "Done"
-send "csl period 5000\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-wait_for "state" "child"
-expect "Done"
-send "ping $addr\n"
-expect "16 bytes from $addr: icmp_seq=1"
-
-dispose_all
+LDADD_COMMON                                                                += \
+    $(top_builddir)/examples/platforms/qpg7015m/libopenthread-qpg7015m.a       \
+    $(NULL)
+if OPENTHREAD_ENABLE_FTD
+LDADD_COMMON                                                                += \
+    $(top_srcdir)/third_party/Qorvo/repo/qpg7015m/lib/libQorvoQPG7015M_ftd.a   \
+    $(NULL)
+else # OPENTHREAD_ENABLE_FTD
+if OPENTHREAD_ENABLE_MTD
+LDADD_COMMON                                                                += \
+    $(top_srcdir)/third_party/Qorvo/repo/qpg7015m/lib/libQorvoQPG7015M_mtd.a   \
+    $(NULL)
+endif # OPENTHREAD_ENABLE_MTD
+endif
diff --git a/examples/platforms/qpg7015m/README.md b/examples/platforms/qpg7015m/README.md
new file mode 100644
index 0000000..d9ac217
--- /dev/null
+++ b/examples/platforms/qpg7015m/README.md
@@ -0,0 +1,87 @@
+# OpenThread on Qorvo qpg7015m Example
+
+This directory contains example platform drivers for Qorvo qpg7015m on RPi.
+
+## Toolchain
+
+This example uses the GNU GCC toolchain on the Raspberry Pi. To build on the Pi:
+
+1. Download the repo to the Pi
+2. go to the subfolder in the openthread repo: third_party/nlbuild-autotools/repo/tools/packages and enter this command:
+
+```bash
+$ sudo /bin/bash build
+```
+
+Note that you may need to install additional packages to make this build work, depending on your actual RPi OS version. The build process will complain if additional packages are required.
+
+## Build Examples
+
+```bash
+$ cd <path-to-openthread>
+$ ./script/bootstrap
+$ ./bootstrap
+$ REFERENCE_DEVICE=1 CLI_LOGGING=1 COMMISSIONER=1 JOINER=1 DHCP6_CLIENT=1 DHCP6_SERVER=1 BORDER_ROUTER=1 make -f examples/Makefile-qpg7015m
+```
+
+After a successful build, the `elf` files are found in `<path-to-openthread>/output/qpg7015m/bin`.
+
+Building a variant which interfaces via a tcp socket is also possible. Replace the uart-posix.c with uart-socket.c in the Makefile.am from examples/platforms/qpg7015m/Makefile.am and rebuild. Now it should be possible to open a telnet to socket 9190 of the raspberry pi from a remote PC. This also easier testing with the official Thread Test Harness.
+
+## Cmake build
+
+Make sure arm-linux-gnueabihf-gcc compiler installed in `$PATH`
+
+```bash
+cd <path-to-openthread>
+./script/cmake-build qpg7015m
+```
+
+After a successful build, binary files will be generated:
+
+```
+./build/qpg7015m/examples/apps/ncp/ot-rcp
+./build/qpg7015m/examples/apps/cli/ot-cli-ftd
+```
+
+##
+
+## Interact
+
+1. Spawn the process:
+
+```bash
+$ cd <path-to-openthread>/output/qpg7015m/bin
+$ ./qpg7015m-ot-cli-ftd
+```
+
+2. Type `help` for list of commands.
+
+```bash
+> help
+help
+channel
+childtimeout
+contextreusedelay
+extaddr
+extpanid
+ipaddr
+keysequence
+leaderweight
+masterkey
+mode
+netdata register
+networkidtimeout
+networkname
+panid
+ping
+prefix
+releaserouterid
+rloc16
+route
+routerupgradethreshold
+scan
+start
+state
+stop
+```
diff --git a/examples/platforms/qpg7015m/alarm.c b/examples/platforms/qpg7015m/alarm.c
new file mode 120000
index 0000000..55ac139
--- /dev/null
+++ b/examples/platforms/qpg7015m/alarm.c
@@ -0,0 +1 @@
+../gp712/alarm.c
\ No newline at end of file
diff --git a/examples/platforms/qpg7015m/alarm_qorvo.h b/examples/platforms/qpg7015m/alarm_qorvo.h
new file mode 120000
index 0000000..5cbe463
--- /dev/null
+++ b/examples/platforms/qpg7015m/alarm_qorvo.h
@@ -0,0 +1 @@
+../gp712/alarm_qorvo.h
\ No newline at end of file
diff --git a/examples/platforms/qpg7015m/arm-linux-gnueabihf.cmake b/examples/platforms/qpg7015m/arm-linux-gnueabihf.cmake
new file mode 120000
index 0000000..18b8c9f
--- /dev/null
+++ b/examples/platforms/qpg7015m/arm-linux-gnueabihf.cmake
@@ -0,0 +1 @@
+../gp712/arm-linux-gnueabihf.cmake
\ No newline at end of file
diff --git a/examples/platforms/qpg7015m/diag.c b/examples/platforms/qpg7015m/diag.c
new file mode 120000
index 0000000..909d4a2
--- /dev/null
+++ b/examples/platforms/qpg7015m/diag.c
@@ -0,0 +1 @@
+../gp712/diag.c
\ No newline at end of file
diff --git a/examples/platforms/qpg7015m/entropy.c b/examples/platforms/qpg7015m/entropy.c
new file mode 120000
index 0000000..88ca2c0
--- /dev/null
+++ b/examples/platforms/qpg7015m/entropy.c
@@ -0,0 +1 @@
+../gp712/entropy.c
\ No newline at end of file
diff --git a/examples/platforms/qpg7015m/flash.c b/examples/platforms/qpg7015m/flash.c
new file mode 120000
index 0000000..daa0f37
--- /dev/null
+++ b/examples/platforms/qpg7015m/flash.c
@@ -0,0 +1 @@
+../gp712/flash.c
\ No newline at end of file
diff --git a/examples/platforms/qpg7015m/logging.c b/examples/platforms/qpg7015m/logging.c
new file mode 120000
index 0000000..bcd76a0
--- /dev/null
+++ b/examples/platforms/qpg7015m/logging.c
@@ -0,0 +1 @@
+../gp712/logging.c
\ No newline at end of file
diff --git a/examples/platforms/qpg7015m/misc.c b/examples/platforms/qpg7015m/misc.c
new file mode 120000
index 0000000..cf10dfa
--- /dev/null
+++ b/examples/platforms/qpg7015m/misc.c
@@ -0,0 +1 @@
+../gp712/misc.c
\ No newline at end of file
diff --git a/examples/platforms/qpg7015m/openthread-core-qpg7015m-config-check.h b/examples/platforms/qpg7015m/openthread-core-qpg7015m-config-check.h
new file mode 100644
index 0000000..c249326
--- /dev/null
+++ b/examples/platforms/qpg7015m/openthread-core-qpg7015m-config-check.h
@@ -0,0 +1,40 @@
+/*
+ *  Copyright (c) 2019, The OpenThread Authors.
+ *  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.
+ */
+
+#ifndef OPENTHREAD_CORE_QPG7015M_CONFIG_CHECK_H_
+#define OPENTHREAD_CORE_QPG7015M_CONFIG_CHECK_H_
+
+#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
+#error "Platform qpg7015m doesn't support configuration option: OPENTHREAD_CONFIG_TIME_SYNC_ENABLE"
+#endif
+
+#if OPENTHREAD_CONFIG_RADIO_915MHZ_OQPSK_SUPPORT
+#error "Platform qpg7015m doesn't support configuration option: OPENTHREAD_CONFIG_RADIO_915MHZ_OQPSK_SUPPORT"
+#endif
+
+#endif /* OPENTHREAD_CORE_QPG7015M_CONFIG_CHECK_H_ */
diff --git a/examples/platforms/qpg7015m/openthread-core-qpg7015m-config.h b/examples/platforms/qpg7015m/openthread-core-qpg7015m-config.h
new file mode 100755
index 0000000..74c466d
--- /dev/null
+++ b/examples/platforms/qpg7015m/openthread-core-qpg7015m-config.h
@@ -0,0 +1,136 @@
+/*
+ *  Copyright (c) 2019, The OpenThread Authors.
+ *  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
+ *   This file includes qpg7015m compile-time configuration constants for OpenThread.
+ */
+
+#ifndef OPENTHREAD_CORE_QPG7015M_CONFIG_H_
+#define OPENTHREAD_CORE_QPG7015M_CONFIG_H_
+
+/**
+ * @def OPENTHREAD_CONFIG_PLATFORM_INFO
+ *
+ * The platform-specific string to insert into the OpenThread version string.
+ *
+ */
+#define OPENTHREAD_CONFIG_PLATFORM_INFO "QPG7015M"
+
+/**
+ * @def OPENTHREAD_CONFIG_PLATFORM_INFO
+ *
+ * Define to 1 to enable otPlatFlash* APIs to support non-volatile storage.
+ *
+ * When defined to 1, the platform MUST implement the otPlatFlash* APIs
+ * instead of the otPlatSettings*
+ *
+ */
+#define OPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE 1
+
+/**
+ * @def OPENTHREAD_CONFIG_MAC_SOFTWARE_ACK_TIMEOUT_ENABLE
+ *
+ * Define to 1 if you want to enable software ACK timeout logic.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_MAC_SOFTWARE_ACK_TIMEOUT_ENABLE
+#define OPENTHREAD_CONFIG_MAC_SOFTWARE_ACK_TIMEOUT_ENABLE 0
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_MAC_SOFTWARE_RETRANSMIT_ENABLE
+ *
+ * Define to 1 if you want to enable software retransmission logic.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_MAC_SOFTWARE_RETRANSMIT_ENABLE
+#define OPENTHREAD_CONFIG_MAC_SOFTWARE_RETRANSMIT_ENABLE 1
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_MAC_SOFTWARE_CSMA_BACKOFF_ENABLE
+ *
+ * Define to 1 if you want to enable software CSMA-CA backoff logic.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_MAC_SOFTWARE_CSMA_BACKOFF_ENABLE
+#define OPENTHREAD_CONFIG_MAC_SOFTWARE_CSMA_BACKOFF_ENABLE 1
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE
+ *
+ * Define to 1 if you want to enable software transmission security logic.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE
+#define OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_TIMING_ENABLE
+ *
+ * Define to 1 to enable software transmission target time logic.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_TIMING_ENABLE
+#define OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_TIMING_ENABLE (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_DIAG_ENABLE
+ *
+ * Define as 1 to enable the diag feature.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_DIAG_ENABLE
+#define OPENTHREAD_CONFIG_DIAG_ENABLE 1
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_NCP_SPI_ENABLE
+ *
+ * Define as 1 to enable SPI NCP interface.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_NCP_SPI_ENABLE
+#define OPENTHREAD_CONFIG_NCP_SPI_ENABLE 0
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
+ *
+ * Define as 1 to enable UART NCP interface.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
+#define OPENTHREAD_CONFIG_NCP_HDLC_ENABLE 1
+#endif
+
+#endif // OPENTHREAD_CORE_QPG7015M_CONFIG_H_
diff --git a/examples/platforms/qpg7015m/platform_qorvo.h b/examples/platforms/qpg7015m/platform_qorvo.h
new file mode 120000
index 0000000..d8fa024
--- /dev/null
+++ b/examples/platforms/qpg7015m/platform_qorvo.h
@@ -0,0 +1 @@
+../gp712/platform_qorvo.h
\ No newline at end of file
diff --git a/examples/platforms/qpg7015m/radio.c b/examples/platforms/qpg7015m/radio.c
new file mode 120000
index 0000000..0f26eb0
--- /dev/null
+++ b/examples/platforms/qpg7015m/radio.c
@@ -0,0 +1 @@
+../gp712/radio.c
\ No newline at end of file
diff --git a/examples/platforms/qpg7015m/radio_qorvo.h b/examples/platforms/qpg7015m/radio_qorvo.h
new file mode 120000
index 0000000..4fb8f67
--- /dev/null
+++ b/examples/platforms/qpg7015m/radio_qorvo.h
@@ -0,0 +1 @@
+../gp712/radio_qorvo.h
\ No newline at end of file
diff --git a/examples/platforms/qpg7015m/random_qorvo.h b/examples/platforms/qpg7015m/random_qorvo.h
new file mode 120000
index 0000000..2cfcf18
--- /dev/null
+++ b/examples/platforms/qpg7015m/random_qorvo.h
@@ -0,0 +1 @@
+../gp712/random_qorvo.h
\ No newline at end of file
diff --git a/examples/platforms/qpg7015m/system.c b/examples/platforms/qpg7015m/system.c
new file mode 120000
index 0000000..3f7a267
--- /dev/null
+++ b/examples/platforms/qpg7015m/system.c
@@ -0,0 +1 @@
+../gp712/system.c
\ No newline at end of file
diff --git a/examples/platforms/qpg7015m/uart-posix.c b/examples/platforms/qpg7015m/uart-posix.c
new file mode 120000
index 0000000..05e781a
--- /dev/null
+++ b/examples/platforms/qpg7015m/uart-posix.c
@@ -0,0 +1 @@
+../gp712/uart-posix.c
\ No newline at end of file
diff --git a/examples/platforms/qpg7015m/uart_qorvo.h b/examples/platforms/qpg7015m/uart_qorvo.h
new file mode 120000
index 0000000..b58c124
--- /dev/null
+++ b/examples/platforms/qpg7015m/uart_qorvo.h
@@ -0,0 +1 @@
+../gp712/uart_qorvo.h
\ No newline at end of file
diff --git a/examples/platforms/samr21/README.md b/examples/platforms/samr21/README.md
index e5b9dfe..5e5a35a 100644
--- a/examples/platforms/samr21/README.md
+++ b/examples/platforms/samr21/README.md
@@ -195,7 +195,7 @@
 
 For a list of all available commands, visit [OpenThread CLI Reference README.md][cli].
 
-[cli]: https://github.com/openthread/openthread/blob/master/src/cli/README.md
+[cli]: https://github.com/openthread/openthread/blob/main/src/cli/README.md
 
 ## Other boards
 
diff --git a/examples/platforms/samr21/openthread-core-samr21-config.h b/examples/platforms/samr21/openthread-core-samr21-config.h
index 0397a42..54b22a9 100644
--- a/examples/platforms/samr21/openthread-core-samr21-config.h
+++ b/examples/platforms/samr21/openthread-core-samr21-config.h
@@ -75,11 +75,11 @@
 #define OPENTHREAD_CONFIG_DEFAULT_TRANSMIT_POWER 5
 
 /**
- * @def OPENTHREAD_CONFIG_NCP_UART_ENABLE
+ * @def OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
  *
- * Define to 1 to enable NCP UART support.
+ * Define to 1 to enable NCP HDLC support.
  *
  */
-#define OPENTHREAD_CONFIG_NCP_UART_ENABLE 1
+#define OPENTHREAD_CONFIG_NCP_HDLC_ENABLE 1
 
 #endif // OPENTHREAD_CORE_SAMR21_CONFIG_H_
diff --git a/examples/platforms/samr21/uart.c b/examples/platforms/samr21/uart.c
index 8f8aa1e..6a5ebc6 100644
--- a/examples/platforms/samr21/uart.c
+++ b/examples/platforms/samr21/uart.c
@@ -34,7 +34,7 @@
 
 #include "asf.h"
 
-#include <openthread/platform/uart.h>
+#include "utils/uart.h"
 
 enum
 {
diff --git a/examples/platforms/simulation/CMakeLists.txt b/examples/platforms/simulation/CMakeLists.txt
index 9352fba..fb670f4 100644
--- a/examples/platforms/simulation/CMakeLists.txt
+++ b/examples/platforms/simulation/CMakeLists.txt
@@ -52,8 +52,7 @@
     "_BSD_SOURCE=1"
     "_DEFAULT_SOURCE=1"
     "OPENTHREAD_EXAMPLES_SIMULATION=1"
-    "OPENTHREAD_POSIX=1"
-    "OPENTHREAD_CONFIG_NCP_UART_ENABLE=1"
+    "OPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1"
 )
 set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE)
 
diff --git a/examples/platforms/simulation/radio.c b/examples/platforms/simulation/radio.c
index 51182eb..a92841e 100644
--- a/examples/platforms/simulation/radio.c
+++ b/examples/platforms/simulation/radio.c
@@ -117,6 +117,7 @@
 static int8_t         sTxPower     = 0;
 static int8_t         sCcaEdThresh = -74;
 static int8_t         sLnaGain     = 0;
+static uint16_t       sRegionCode  = 0;
 
 enum
 {
@@ -678,7 +679,7 @@
 #endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT && OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
 
 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
-    if (sCslPeriod > 0)
+    if (sCslPeriod > 0 && !sTransmitFrame.mInfo.mTxInfo.mIsARetx)
     {
         otMacFrameSetCslIe(&sTransmitFrame, (uint16_t)sCslPeriod, getCslPhase());
     }
@@ -1236,3 +1237,23 @@
     return otLinkMetricsConfigureEnhAckProbing(aShortAddress, aExtAddress, aLinkMetrics);
 }
 #endif
+
+otError otPlatRadioSetRegion(otInstance *aInstance, uint16_t aRegionCode)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+
+    sRegionCode = aRegionCode;
+    return OT_ERROR_NONE;
+}
+
+otError otPlatRadioGetRegion(otInstance *aInstance, uint16_t *aRegionCode)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+    otError error = OT_ERROR_NONE;
+
+    VerifyOrExit(aRegionCode != NULL, error = OT_ERROR_INVALID_ARGS);
+
+    *aRegionCode = sRegionCode;
+exit:
+    return error;
+}
diff --git a/examples/platforms/simulation/spi-stubs.c b/examples/platforms/simulation/spi-stubs.c
index 7c0dd62..585bca7 100644
--- a/examples/platforms/simulation/spi-stubs.c
+++ b/examples/platforms/simulation/spi-stubs.c
@@ -33,7 +33,6 @@
 
 #include <openthread/config.h>
 #include <openthread/platform/spi-slave.h>
-#include <openthread/platform/uart.h>
 
 #if OPENTHREAD_CONFIG_NCP_SPI_ENABLE
 
diff --git a/examples/platforms/simulation/trel.c b/examples/platforms/simulation/trel.c
index a77a1c3..8e53962 100644
--- a/examples/platforms/simulation/trel.c
+++ b/examples/platforms/simulation/trel.c
@@ -46,35 +46,64 @@
 
 #define TREL_MAX_PENDING_TX 5
 
-typedef struct PendingTx
+OT_TOOL_PACKED_BEGIN
+struct PacketHeader
 {
-    uint8_t      mPacketBuffer[TREL_MAX_PACKET_SIZE];
-    uint16_t     mPacketLength;
+    otIp6Address mSrcIp6Address;
     otIp6Address mDestIp6Address;
-} PendingTx;
+    uint16_t     mPort;
+} OT_TOOL_PACKED_END;
 
-static uint8_t   sNumPendingTx = 0;
-static PendingTx sPendingTx[TREL_MAX_PENDING_TX];
+typedef struct PacketHeader PacketHeader;
 
-static int      sTxFd       = -1;
-static int      sRxFd       = -1;
-static uint16_t sPortOffset = 0;
-static bool     sEnabled    = true;
+OT_TOOL_PACKED_BEGIN
+struct Packet
+{
+    PacketHeader mHeader;
+    uint8_t      mPayload[TREL_MAX_PACKET_SIZE];
+    uint16_t     mLength; // Total packet length including the header.
+} OT_TOOL_PACKED_END;
+
+typedef struct Packet Packet;
+
+static uint8_t sNumPendingTx = 0;
+static Packet  sPendingTx[TREL_MAX_PENDING_TX];
+
+static int          sTxFd       = -1;
+static int          sRxFd       = -1;
+static uint16_t     sPortOffset = 0;
+static bool         sEnabled    = true;
+static otIp6Address sUnicastAddress;
+static otIp6Address sMulticastAddress;
+static uint16_t     sUdpPort;
 
 #if DEBUG_LOG
-static void dumpBuffer(const uint8_t *aBuffer, uint16_t aLength)
+static void dumpBuffer(const void *aBuffer, uint16_t aLength)
 {
+    const uint8_t *buffer = (const uint8_t *)aBuffer;
     fprintf(stderr, "[ (len:%d) ", aLength);
 
     while (aLength--)
     {
-        fprintf(stderr, "%02x ", *aBuffer++);
+        fprintf(stderr, "%02x ", *buffer++);
     }
 
     fprintf(stderr, "]");
 }
+
+static const char *ip6AddrToString(const void *aAddress)
+{
+    static char string[INET6_ADDRSTRLEN];
+
+    return inet_ntop(AF_INET6, aAddress, string, sizeof(string));
+}
 #endif
 
+static bool ip6AddrsAreEqual(const otIp6Address *aFirst, const otIp6Address *aSecond)
+{
+    return (memcmp(aFirst, aSecond, sizeof(otIp6Address)) == 0);
+}
+
 static void initFds(void)
 {
     int                fd;
@@ -148,7 +177,7 @@
     }
 }
 
-void sendPendingTxPackets(void)
+static void sendPendingTxPackets(void)
 {
     ssize_t            rval;
     struct sockaddr_in sockaddr;
@@ -163,12 +192,11 @@
     {
 #if DEBUG_LOG
         fprintf(stderr, "\n[trel-udp] Sending packet (num:%d)", i);
-        dumpBuffer(sPendingTx[i].mPacketBuffer, sPendingTx[i].mPacketLength);
+        dumpBuffer(&sPendingTx[i], sPendingTx[i].mLength);
         fprintf(stderr, "\n");
 #endif
 
-        rval = sendto(sTxFd, sPendingTx[i].mPacketBuffer, sPendingTx[i].mPacketLength, 0, (struct sockaddr *)&sockaddr,
-                      sizeof(sockaddr));
+        rval = sendto(sTxFd, &sPendingTx[i], sPendingTx[i].mLength, 0, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
 
         if (rval < 0)
         {
@@ -183,23 +211,41 @@
 //---------------------------------------------------------------------------------------------------------------------
 // otPlatTrel
 
-void otPlatTrelUdp6Init(otInstance *aInstance, const otIp6Address *aUnicastAddress, uint16_t aPort)
+void otPlatTrelUdp6Init(otInstance *aInstance, const otIp6Address *aUnicastAddress, uint16_t aUdpPort)
 {
     OT_UNUSED_VARIABLE(aInstance);
-    OT_UNUSED_VARIABLE(aUnicastAddress);
-    OT_UNUSED_VARIABLE(aPort);
+
+    sUnicastAddress = *aUnicastAddress;
+    sUdpPort        = aUdpPort;
+
+#if DEBUG_LOG
+    fprintf(stderr, "\n[trel-udp6] otPlatTrelUdp6Init(aUnicastAddress:%s, aUdpPort:%d)\n",
+            ip6AddrToString(aUnicastAddress), aUdpPort);
+#endif
 }
 
 void otPlatTrelUdp6UpdateAddress(otInstance *aInstance, const otIp6Address *aUnicastAddress)
 {
     OT_UNUSED_VARIABLE(aInstance);
-    OT_UNUSED_VARIABLE(aUnicastAddress);
+
+    sUnicastAddress = *aUnicastAddress;
+
+#if DEBUG_LOG
+    fprintf(stderr, "\n[trel-udp6] otPlatTrelUdp6UpdateAddress(aUnicastAddress:%s)\n",
+            ip6AddrToString(aUnicastAddress));
+#endif
 }
 
 void otPlatTrelUdp6SubscribeMulticastAddress(otInstance *aInstance, const otIp6Address *aMulticastAddress)
 {
     OT_UNUSED_VARIABLE(aInstance);
-    OT_UNUSED_VARIABLE(aMulticastAddress);
+
+    sMulticastAddress = *aMulticastAddress;
+
+#if DEBUG_LOG
+    fprintf(stderr, "\n[trel-udp6] otPlatTrelUdp6SubscribeMulticastAddress(aMulticastAddress:%s)\n",
+            ip6AddrToString(aMulticastAddress));
+#endif
 }
 
 otError otPlatTrelUdp6SendTo(otInstance *        aInstance,
@@ -208,16 +254,20 @@
                              const otIp6Address *aDestAddress)
 {
     otError error = OT_ERROR_NONE;
+    Packet *packet;
 
     OT_UNUSED_VARIABLE(aInstance);
 
     otEXPECT(sEnabled);
     otEXPECT_ACTION(sNumPendingTx < TREL_MAX_PENDING_TX, error = OT_ERROR_ABORT);
+    otEXPECT_ACTION(aLength <= TREL_MAX_PACKET_SIZE, error = OT_ERROR_ABORT);
 
-    memcpy(sPendingTx[sNumPendingTx].mPacketBuffer, aBuffer, aLength);
-    sPendingTx[sNumPendingTx].mPacketLength = aLength;
-    memcpy(&sPendingTx[sNumPendingTx].mDestIp6Address, aDestAddress, sizeof(otIp6Address));
-    sNumPendingTx++;
+    packet                          = &sPendingTx[sNumPendingTx++];
+    packet->mHeader.mSrcIp6Address  = sUnicastAddress;
+    packet->mHeader.mDestIp6Address = *aDestAddress;
+    packet->mHeader.mPort           = sUdpPort;
+    packet->mLength                 = aLength + sizeof(PacketHeader);
+    memcpy(packet->mPayload, aBuffer, aLength);
 
 exit:
     return error;
@@ -306,11 +356,10 @@
 
     if (FD_ISSET(sRxFd, aReadFdSet))
     {
-        uint8_t  rxPacketBuffer[TREL_MAX_PACKET_SIZE];
-        uint16_t rxPacketLength;
-        ssize_t  rval;
+        Packet  rxPacket;
+        ssize_t rval;
 
-        rval = recvfrom(sRxFd, (char *)rxPacketBuffer, sizeof(rxPacketBuffer), 0, NULL, NULL);
+        rval = recvfrom(sRxFd, (char *)&rxPacket, sizeof(rxPacket) - sizeof(rxPacket.mLength), 0, NULL, NULL);
 
         if (rval < 0)
         {
@@ -318,16 +367,21 @@
             exit(EXIT_FAILURE);
         }
 
-        rxPacketLength = (uint16_t)(rval);
+        rxPacket.mLength = (uint16_t)(rval);
 
 #if DEBUG_LOG
         fprintf(stderr, "\n[trel-udp6] recvdPacket()");
-        dumpBuffer(rxPacketBuffer, rxPacketLength);
+        fprintf(stderr, " src:%s", ip6AddrToString(&rxPacket.mHeader.mSrcIp6Address));
+        fprintf(stderr, " dst:%s", ip6AddrToString(&rxPacket.mHeader.mDestIp6Address));
+        fprintf(stderr, " port:%u ", rxPacket.mHeader.mPort);
+        dumpBuffer(&rxPacket, rxPacket.mLength);
         fprintf(stderr, "\n");
 #endif
-        if (sEnabled)
+        if (sEnabled && (rxPacket.mHeader.mPort == sUdpPort) &&
+            (ip6AddrsAreEqual(&rxPacket.mHeader.mDestIp6Address, &sUnicastAddress) ||
+             ip6AddrsAreEqual(&rxPacket.mHeader.mDestIp6Address, &sMulticastAddress)))
         {
-            otPlatTrelUdp6HandleReceived(aInstance, rxPacketBuffer, rxPacketLength);
+            otPlatTrelUdp6HandleReceived(aInstance, rxPacket.mPayload, rxPacket.mLength - sizeof(PacketHeader));
         }
     }
 }
diff --git a/examples/platforms/simulation/uart.c b/examples/platforms/simulation/uart.c
index 777bbed..3bbcfac 100644
--- a/examples/platforms/simulation/uart.c
+++ b/examples/platforms/simulation/uart.c
@@ -39,9 +39,9 @@
 #include <unistd.h>
 
 #include <openthread/platform/debug_uart.h>
-#include <openthread/platform/uart.h>
 
 #include "utils/code_utils.h"
+#include "utils/uart.h"
 
 #if OPENTHREAD_SIMULATION_VIRTUAL_TIME_UART == 0
 
diff --git a/examples/platforms/simulation/virtual_time/platform-sim.c b/examples/platforms/simulation/virtual_time/platform-sim.c
index dfe7599..819a460 100644
--- a/examples/platforms/simulation/virtual_time/platform-sim.c
+++ b/examples/platforms/simulation/virtual_time/platform-sim.c
@@ -48,7 +48,8 @@
 
 #include <openthread/tasklet.h>
 #include <openthread/platform/alarm-milli.h>
-#include <openthread/platform/uart.h>
+
+#include "utils/uart.h"
 
 uint32_t gNodeId = 1;
 
@@ -168,7 +169,7 @@
 
 otError otPlatUartFlush(void)
 {
-    return OT_ERROR_NOT_IMPLEMENTED;
+    return OT_ERROR_NONE;
 }
 #endif // OPENTHREAD_SIMULATION_VIRTUAL_TIME_UART
 
diff --git a/examples/platforms/utils/Makefile.am b/examples/platforms/utils/Makefile.am
index a8cf2e4..53cd3db 100644
--- a/examples/platforms/utils/Makefile.am
+++ b/examples/platforms/utils/Makefile.am
@@ -29,19 +29,32 @@
 include $(abs_top_nlbuild_autotools_dir)/automake/pre.am
 
 lib_LIBRARIES                           = libopenthread-platform-utils.a
+noinst_LTLIBRARIES                      = libutils-link-metrics.la
 
-libopenthread_platform_utils_a_CPPFLAGS                 = \
+COMMON_FLAGS                                            = \
     -I$(top_srcdir)/include                               \
     -I$(top_srcdir)/examples/platforms                    \
     -I$(top_srcdir)/src/core                              \
     -I$(top_srcdir)/third_party/jlink/SEGGER_RTT_V640/RTT \
     $(NULL)
 
+libutils_link_metrics_la_CPPFLAGS                       = \
+    $(COMMON_FLAGS)                                       \
+    -fno-threadsafe-statics                               \
+    $(NULL)
+
+libutils_link_metrics_la_SOURCES                        = \
+    link_metrics.cpp                                      \
+    link_metrics.h                                        \
+    $(NULL)
+
+libopenthread_platform_utils_a_CPPFLAGS                 = \
+    $(COMMON_FLAGS)                                       \
+    $(NULL)
+
 libopenthread_platform_utils_a_SOURCES  = \
     code_utils.h                          \
     debug_uart.c                          \
-    link_metrics.cpp                      \
-    link_metrics.h                        \
     logging_rtt.c                         \
     logging_rtt.h                         \
     mac_frame.cpp                         \
@@ -51,6 +64,11 @@
     settings_ram.c                        \
     soft_source_match_table.c             \
     soft_source_match_table.h             \
+    uart.h                                \
+    $(NULL)
+
+libopenthread_platform_utils_a_LIBADD  = \
+    libutils-link-metrics.la             \
     $(NULL)
 
 include $(abs_top_nlbuild_autotools_dir)/automake/post.am
diff --git a/examples/platforms/utils/link_metrics.cpp b/examples/platforms/utils/link_metrics.cpp
index f73f131..883ed59 100644
--- a/examples/platforms/utils/link_metrics.cpp
+++ b/examples/platforms/utils/link_metrics.cpp
@@ -114,6 +114,18 @@
     }
 
     /**
+     * This method gets the length of Link Metrics Data.
+     *
+     * @returns  The number of bytes for the data.
+     *
+     */
+    uint8_t GetEnhAckDataLen() const
+    {
+        return static_cast<uint8_t>(mLinkMetrics.mLqi) + static_cast<uint8_t>(mLinkMetrics.mLinkMargin) +
+               static_cast<uint8_t>(mLinkMetrics.mRssi);
+    }
+
+    /**
      * This method gets the metrics configured for the Enhanced-ACK Based Probing.
      *
      * @returns  The metrics configured.
@@ -236,4 +248,16 @@
 exit:
     return bytes;
 }
+
+uint8_t otLinkMetricsEnhAckGetDataLen(const otMacAddress *aMacAddress)
+{
+    uint8_t              len      = 0;
+    LinkMetricsDataInfo *dataInfo = GetLinkMetricsInfoByMacAddress(aMacAddress);
+
+    VerifyOrExit(dataInfo != nullptr);
+    len = dataInfo->GetEnhAckDataLen();
+
+exit:
+    return len;
+}
 #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
diff --git a/examples/platforms/utils/link_metrics.h b/examples/platforms/utils/link_metrics.h
index 1582b87..099d64b 100644
--- a/examples/platforms/utils/link_metrics.h
+++ b/examples/platforms/utils/link_metrics.h
@@ -97,6 +97,16 @@
  */
 uint8_t otLinkMetricsEnhAckGenData(const otMacAddress *aMacAddress, uint8_t aLqi, int8_t aRssi, uint8_t *aData);
 
+/**
+ * This method returns the data length of Enhanced-ACK Based Probing for a specific Initiator.
+ *
+ * @param[in]  aMacAddress    The Mac address of the Initiator.
+ *
+ * @returns  The size of data. `0` if it's not configured for the Initiator.
+ *
+ */
+uint8_t otLinkMetricsEnhAckGetDataLen(const otMacAddress *aMacAddress);
+
 #ifdef __cplusplus
 } // extern "C"
 #endif
diff --git a/examples/platforms/utils/mac_frame.cpp b/examples/platforms/utils/mac_frame.cpp
index 24c0dea..eab1398 100644
--- a/examples/platforms/utils/mac_frame.cpp
+++ b/examples/platforms/utils/mac_frame.cpp
@@ -91,6 +91,26 @@
     return static_cast<const Mac::Frame *>(aFrame)->GetAckRequest();
 }
 
+static void GetOtMacAddress(const Mac::Address &aInAddress, otMacAddress *aOutAddress)
+{
+    switch (aInAddress.GetType())
+    {
+    case Mac::Address::kTypeNone:
+        aOutAddress->mType = OT_MAC_ADDRESS_TYPE_NONE;
+        break;
+
+    case Mac::Address::kTypeShort:
+        aOutAddress->mType                  = OT_MAC_ADDRESS_TYPE_SHORT;
+        aOutAddress->mAddress.mShortAddress = aInAddress.GetShort();
+        break;
+
+    case Mac::Address::kTypeExtended:
+        aOutAddress->mType                = OT_MAC_ADDRESS_TYPE_EXTENDED;
+        aOutAddress->mAddress.mExtAddress = aInAddress.GetExtended();
+        break;
+    }
+}
+
 otError otMacFrameGetSrcAddr(const otRadioFrame *aFrame, otMacAddress *aMacAddress)
 {
     otError      error;
@@ -99,22 +119,21 @@
     error = static_cast<const Mac::Frame *>(aFrame)->GetSrcAddr(address);
     SuccessOrExit(error);
 
-    switch (address.GetType())
-    {
-    case Mac::Address::kTypeNone:
-        aMacAddress->mType = OT_MAC_ADDRESS_TYPE_NONE;
-        break;
+    GetOtMacAddress(address, aMacAddress);
 
-    case Mac::Address::kTypeShort:
-        aMacAddress->mType                  = OT_MAC_ADDRESS_TYPE_SHORT;
-        aMacAddress->mAddress.mShortAddress = address.GetShort();
-        break;
+exit:
+    return error;
+}
 
-    case Mac::Address::kTypeExtended:
-        aMacAddress->mType                = OT_MAC_ADDRESS_TYPE_EXTENDED;
-        aMacAddress->mAddress.mExtAddress = address.GetExtended();
-        break;
-    }
+otError otMacFrameGetDstAddr(const otRadioFrame *aFrame, otMacAddress *aMacAddress)
+{
+    otError      error;
+    Mac::Address address;
+
+    error = static_cast<const Mac::Frame *>(aFrame)->GetDstAddr(address);
+    SuccessOrExit(error);
+
+    GetOtMacAddress(address, aMacAddress);
 
 exit:
     return error;
@@ -211,7 +230,7 @@
 {
     assert(aDest != nullptr);
 
-    reinterpret_cast<Mac::HeaderIe *>(aDest)->SetId(Mac::Frame::kHeaderIeCsl);
+    reinterpret_cast<Mac::HeaderIe *>(aDest)->SetId(Mac::CslIe::kHeaderIeId);
     reinterpret_cast<Mac::HeaderIe *>(aDest)->SetLength(sizeof(Mac::CslIe));
 
     return sizeof(Mac::HeaderIe) + sizeof(Mac::CslIe);
@@ -225,7 +244,7 @@
 
     assert(aDest != nullptr);
 
-    reinterpret_cast<Mac::HeaderIe *>(aDest)->SetId(Mac::Frame::kHeaderIeVendor);
+    reinterpret_cast<Mac::HeaderIe *>(aDest)->SetId(Mac::ThreadIe::kHeaderIeId);
     reinterpret_cast<Mac::HeaderIe *>(aDest)->SetLength(len);
 
     aDest += sizeof(Mac::HeaderIe);
diff --git a/examples/platforms/utils/mac_frame.h b/examples/platforms/utils/mac_frame.h
index 5aef724..1d6c39a 100644
--- a/examples/platforms/utils/mac_frame.h
+++ b/examples/platforms/utils/mac_frame.h
@@ -154,6 +154,18 @@
 otError otMacFrameGetSrcAddr(const otRadioFrame *aFrame, otMacAddress *aMacAddress);
 
 /**
+ * Get destination MAC address.
+ *
+ * @param[in]   aFrame          A pointer to the frame.
+ * @param[out]  aMacAddress     A pointer to MAC address.
+ *
+ * @retval  OT_ERROR_NONE   Successfully got the destination MAC address.
+ * @retval  OT_ERROR_PARSE  Failed to parse the destination MAC address.
+ *
+ */
+otError otMacFrameGetDstAddr(const otRadioFrame *aFrame, otMacAddress *aMacAddress);
+
+/**
  * Get the sequence of @p aFrame.
  *
  * @param[in]   aFrame          A pointer to the frame.
diff --git a/include/openthread/platform/uart.h b/examples/platforms/utils/uart.h
similarity index 100%
rename from include/openthread/platform/uart.h
rename to examples/platforms/utils/uart.h
diff --git a/include/Makefile.am b/include/Makefile.am
index e5554d3..427133e 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -43,15 +43,17 @@
     openthread/channel_monitor.h          \
     openthread/child_supervision.h        \
     openthread/cli.h                      \
-    openthread/coap_secure.h              \
     openthread/coap.h                     \
+    openthread/coap_secure.h              \
     openthread/commissioner.h             \
+    openthread/config.h                   \
     openthread/crypto.h                   \
     openthread/dataset.h                  \
     openthread/dataset_ftd.h              \
     openthread/dataset_updater.h          \
     openthread/diag.h                     \
     openthread/dns.h                      \
+    openthread/dns_client.h               \
     openthread/entropy.h                  \
     openthread/error.h                    \
     openthread/heap.h                     \
@@ -70,10 +72,13 @@
     openthread/netdata.h                  \
     openthread/netdiag.h                  \
     openthread/network_time.h             \
+    openthread/ping_sender.h              \
     openthread/random_crypto.h            \
     openthread/random_noncrypto.h         \
     openthread/server.h                   \
     openthread/sntp.h                     \
+    openthread/srp_client.h               \
+    openthread/srp_server.h               \
     openthread/tasklet.h                  \
     openthread/thread.h                   \
     openthread/thread_ftd.h               \
@@ -86,22 +91,23 @@
 ot_platform_headers                     = \
     openthread/platform/alarm-micro.h     \
     openthread/platform/alarm-milli.h     \
+    openthread/platform/debug_uart.h      \
     openthread/platform/diag.h            \
-    openthread/platform/flash.h           \
     openthread/platform/entropy.h         \
-    openthread/platform/memory.h          \
-    openthread/platform/misc.h            \
+    openthread/platform/flash.h           \
+    openthread/platform/infra_if.h        \
     openthread/platform/logging.h         \
+    openthread/platform/memory.h          \
+    openthread/platform/messagepool.h     \
+    openthread/platform/misc.h            \
     openthread/platform/otns.h            \
     openthread/platform/radio.h           \
-    openthread/platform/time.h            \
-    openthread/platform/uart.h            \
-    openthread/platform/udp.h             \
-    openthread/platform/spi-slave.h       \
     openthread/platform/settings.h        \
-    openthread/platform/messagepool.h     \
+    openthread/platform/spi-slave.h       \
+    openthread/platform/time.h            \
     openthread/platform/toolchain.h       \
     openthread/platform/trel-udp6.h       \
+    openthread/platform/udp.h             \
     $(NULL)
 
 ot_platformdir = $(includedir)/openthread/platform
diff --git a/include/openthread-config-android.h b/include/openthread-config-android.h
index 40e6d9b..0783033 100644
--- a/include/openthread-config-android.h
+++ b/include/openthread-config-android.h
@@ -26,6 +26,10 @@
  *  POSSIBILITY OF SUCH DAMAGE.
  */
 
+#ifdef OPENTHREAD_CONFIG_ANDROID_VERSION_HEADER_ENABLE
+#include <openthread-config-android-version.h>
+#endif
+
 /* Define to 1 to enable the border agent feature. */
 #define OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE 1
 
@@ -53,8 +57,8 @@
 /* Define to 1 if you want to use legacy network support */
 #define OPENTHREAD_CONFIG_LEGACY_ENABLE 0
 
-/* Define to 1 to enable the NCP UART interface. */
-#define OPENTHREAD_CONFIG_NCP_UART_ENABLE 0
+/* Define to 1 to enable the NCP HDLC interface. */
+#define OPENTHREAD_CONFIG_NCP_HDLC_ENABLE 0
 
 /* Define to 1 to enable posix platform. */
 #define OPENTHREAD_PLATFORM_POSIX 0
diff --git a/include/openthread/BUILD.gn b/include/openthread/BUILD.gn
index 47b6b73..767f670 100644
--- a/include/openthread/BUILD.gn
+++ b/include/openthread/BUILD.gn
@@ -67,12 +67,14 @@
     "coap.h",
     "coap_secure.h",
     "commissioner.h",
+    "config.h",
     "crypto.h",
     "dataset.h",
     "dataset_ftd.h",
     "dataset_updater.h",
     "diag.h",
     "dns.h",
+    "dns_client.h",
     "entropy.h",
     "error.h",
     "heap.h",
@@ -82,6 +84,7 @@
     "jam_detection.h",
     "joiner.h",
     "link.h",
+    "link_metrics.h",
     "link_raw.h",
     "logging.h",
     "message.h",
@@ -90,12 +93,14 @@
     "netdata.h",
     "netdiag.h",
     "network_time.h",
+    "ping_sender.h",
     "platform/alarm-micro.h",
     "platform/alarm-milli.h",
     "platform/debug_uart.h",
     "platform/diag.h",
     "platform/entropy.h",
     "platform/flash.h",
+    "platform/infra_if.h",
     "platform/logging.h",
     "platform/memory.h",
     "platform/messagepool.h",
@@ -107,12 +112,13 @@
     "platform/time.h",
     "platform/toolchain.h",
     "platform/trel-udp6.h",
-    "platform/uart.h",
     "platform/udp.h",
     "random_crypto.h",
     "random_noncrypto.h",
     "server.h",
     "sntp.h",
+    "srp_client.h",
+    "srp_server.h",
     "tasklet.h",
     "thread.h",
     "thread_ftd.h",
diff --git a/include/openthread/border_router.h b/include/openthread/border_router.h
index e5e413d..c1e4891 100644
--- a/include/openthread/border_router.h
+++ b/include/openthread/border_router.h
@@ -53,6 +53,45 @@
  */
 
 /**
+ * This method initializes the Border Routing Manager on given infrastructure interface.
+ *
+ * @note  This method MUST be called before any other otBorderRouting* APIs.
+ *
+ * @param[in]  aInstance                 A pointer to an OpenThread instance.
+ * @param[in]  aInfraIfIndex             The infrastructure interface index.
+ * @param[in]  aInfraIfIsRunning         A boolean that indicates whether the infrastructure
+ *                                       interface is running.
+ * @param[in]  aInfraIfLinkLocalAddress  A pointer to the IPv6 link-local address of the infrastructure
+ *                                       interface. NULL if the IPv6 link-local address is missing.
+ *
+ * @retval  OT_ERROR_NONE           Successfully started the Border Routing Manager on given infrastructure.
+ * @retval  OT_ERROR_INVALID_STATE  The Border Routing Manager has already been initialized.
+ * @retval  OT_ERROR_INVALID_ARGS   The index or the IPv6 link-local address of the infra interface is not valid.
+ * @retval  OT_ERROR_FAILED         Internal failure. Usually due to failure in generating random prefixes.
+ *
+ * @sa otPlatInfraIfStateChanged.
+ *
+ */
+otError otBorderRoutingInit(otInstance *        aInstance,
+                            uint32_t            aInfraIfIndex,
+                            bool                aInfraIfIsRunning,
+                            const otIp6Address *aInfraIfLinkLocalAddress);
+
+/**
+ * This method enables/disables the Border Routing Manager.
+ *
+ * @note  The Border Routing Manager is disabled by default.
+ *
+ * @param[in]  aInstance  A pointer to an OpenThread instance.
+ * @param[in]  aEnabled   A boolean to enable/disable the routing manager.
+ *
+ * @retval  OT_ERROR_INVALID_STATE  The Border Routing Manager is not initialized yet.
+ * @retval  OT_ERROR_NONE           Successfully enabled/disabled the Border Routing Manager.
+ *
+ */
+otError otBorderRoutingSetEnabled(otInstance *aInstance, bool aEnabled);
+
+/**
  * This method provides a full or stable copy of the local Thread Network Data.
  *
  * @param[in]     aInstance    A pointer to an OpenThread instance.
diff --git a/include/openthread/cli.h b/include/openthread/cli.h
index 5a8f54c..56c9868 100644
--- a/include/openthread/cli.h
+++ b/include/openthread/cli.h
@@ -74,37 +74,28 @@
  * @param[in]  aBufLength  A length of the output data stored in the buffer.
  * @param[out] aContext    A user context pointer.
  *
- * @returns                Number of bytes processed by the callback.
+ * @returns                Number of bytes written by the callback.
  *
  */
-typedef int (*otCliConsoleOutputCallback)(const char *aBuf, uint16_t aBufLength, void *aContext);
+typedef int (*otCliOutputCallback)(void *aContext, const char *aFormat, va_list aArguments);
 
 /**
- * Initialize the CLI CONSOLE module.
+ * Initialize the CLI module.
  *
  * @param[in]  aInstance   The OpenThread instance structure.
- * @param[in]  aCallback   A callback method called to process console output.
+ * @param[in]  aCallback   A callback method called to process CLI output.
  * @param[in]  aContext    A user context pointer.
  *
  */
-void otCliConsoleInit(otInstance *aInstance, otCliConsoleOutputCallback aCallback, void *aContext);
+void otCliInit(otInstance *aInstance, otCliOutputCallback aCallback, void *aContext);
 
 /**
  * This method is called to feed in a console input line.
  *
- * @param[in]  aBuf        A pointer to a buffer with an input.
- * @param[in]  aBufLength  A length of the input data stored in the buffer.
+ * @param[in]  aBuf        A pointer to a null-terminated string.
  *
  */
-void otCliConsoleInputLine(char *aBuf, uint16_t aBufLength);
-
-/**
- * Initialize the CLI UART module.
- *
- * @param[in]  aInstance  The OpenThread instance structure.
- *
- */
-void otCliUartInit(otInstance *aInstance);
+void otCliInputLine(char *aBuf);
 
 /**
  * Set a user command table.
@@ -135,15 +126,6 @@
 void otCliOutputFormat(const char *aFmt, ...);
 
 /**
- * Write string to the CLI console
- *
- * @param[in]  aString  A pointer to the string, which may not be null-terminated.
- * @param[in]  aLength  Number of bytes.
- *
- */
-void otCliOutput(const char *aString, uint16_t aLength);
-
-/**
  * Write error code to the CLI console
  *
  * If the @p aError is `OT_ERROR_PENDING` nothing will be outputted.
diff --git a/include/openthread/coap.h b/include/openthread/coap.h
index 5089df8..2a2df22 100644
--- a/include/openthread/coap.h
+++ b/include/openthread/coap.h
@@ -144,26 +144,13 @@
     OT_COAP_OPTION_LOCATION_QUERY = 20, ///< Location-Query
     OT_COAP_OPTION_BLOCK2         = 23, ///< Block2 (RFC7959)
     OT_COAP_OPTION_BLOCK1         = 27, ///< Block1 (RFC7959)
+    OT_COAP_OPTION_SIZE2          = 28, ///< Size2 (RFC7959)
     OT_COAP_OPTION_PROXY_URI      = 35, ///< Proxy-Uri
     OT_COAP_OPTION_PROXY_SCHEME   = 39, ///< Proxy-Scheme
     OT_COAP_OPTION_SIZE1          = 60, ///< Size1
 } otCoapOptionType;
 
 /**
- * CoAP Block Size Exponents
- */
-typedef enum otCoapBlockSize
-{
-    OT_COAP_BLOCK_SIZE_16   = 0,
-    OT_COAP_BLOCK_SIZE_32   = 1,
-    OT_COAP_BLOCK_SIZE_64   = 2,
-    OT_COAP_BLOCK_SIZE_128  = 3,
-    OT_COAP_BLOCK_SIZE_256  = 4,
-    OT_COAP_BLOCK_SIZE_512  = 5,
-    OT_COAP_BLOCK_SIZE_1024 = 6,
-} otCoapBlockSize;
-
-/**
  * This structure represents a CoAP option.
  *
  */
@@ -328,6 +315,20 @@
 } otCoapOptionContentFormat;
 
 /**
+ * CoAP Block Size Exponents
+ */
+typedef enum otCoapBlockSzx
+{
+    OT_COAP_OPTION_BLOCK_SZX_16   = 0,
+    OT_COAP_OPTION_BLOCK_SZX_32   = 1,
+    OT_COAP_OPTION_BLOCK_SZX_64   = 2,
+    OT_COAP_OPTION_BLOCK_SZX_128  = 3,
+    OT_COAP_OPTION_BLOCK_SZX_256  = 4,
+    OT_COAP_OPTION_BLOCK_SZX_512  = 5,
+    OT_COAP_OPTION_BLOCK_SZX_1024 = 6
+} otCoapBlockSzx;
+
+/**
  * This function pointer is called when a CoAP response is received or on the request timeout.
  *
  * @param[in]  aContext      A pointer to application-specific context.
@@ -356,6 +357,58 @@
 typedef void (*otCoapRequestHandler)(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
 
 /**
+ * This function pointer is called when a CoAP message with an block-wise transfer option is received.
+ *
+ * This function is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE configuration
+ * is enabled.
+ *
+ * @param[in]  aContext     A pointer to application-specific context.
+ * @param[in]  aBlock       A pointer to the block segment.
+ * @param[in]  aPosition    The position of @p aBlock in a sequence in bytes.
+ * @param[in]  aBlockLength The length of the block segment in bytes.
+ * @param[in]  aMore        Flag if more block segments are following.
+ * @param[in]  aTotalLength The total length in bytes of the transfered information (indicated by a Size1 or Size2
+ *                          option).
+ *
+ * @retval  OT_ERROR_NONE               Block segment was stored successfully.
+ * @retval  OT_ERROR_NO_BUFS            No more memory to store blocks.
+ * @retval  OT_ERROR_NO_FRAME_RECEIVED  Block segment missing.
+ *
+ */
+typedef otError (*otCoapBlockwiseReceiveHook)(void *         aContext,
+                                              const uint8_t *aBlock,
+                                              uint32_t       aPosition,
+                                              uint16_t       aBlockLength,
+                                              bool           aMore,
+                                              uint32_t       aTotalLength);
+
+/**
+ * This function pointer is called before the next block in a block-wise transfer is sent.
+ *
+ * This function is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE configuration
+ * is enabled.
+ *
+ * @param[in]       aContext     A pointer to application-specific context.
+ * @param[inout]    aBlock       A pointer to where the block segment can be written to.
+ * @param[in]       aPosition    The position in a sequence from which to obtain the block segment.
+ * @param[inout]    aBlockLength On entry, the maximum block segment length in bytes.
+ * @param[out]      aMore        A pointer to the flag if more block segments will follow.
+ *
+ * @warning By changing the value of aBlockLength, the block size of the whole exchange is
+ *          renegotiated. It is recommended to do this after the first block has been received as
+ *          later changes could cause problems with other CoAP implementations.
+ *
+ * @retval  OT_ERROR_NONE           No error occurred.
+ * @retval  OT_ERROR_INVALID_ARGS   Block at @p aPosition does not exist.
+ *
+ */
+typedef otError (*otCoapBlockwiseTransmitHook)(void *    aContext,
+                                               uint8_t * aBlock,
+                                               uint32_t  aPosition,
+                                               uint16_t *aBlockLength,
+                                               bool *    aMore);
+
+/**
  * This structure represents a CoAP resource.
  *
  */
@@ -368,6 +421,30 @@
 } otCoapResource;
 
 /**
+ * This structure represents a CoAP resource with block-wise transfer.
+ *
+ */
+typedef struct otCoapBlockwiseResource
+{
+    const char *         mUriPath; ///< The URI Path string
+    otCoapRequestHandler mHandler; ///< The callback for handling a received request
+
+    /** The callback for handling incoming block-wise transfer.
+     *  This callback is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+     *  configuration is enabled.
+     */
+    otCoapBlockwiseReceiveHook mReceiveHook;
+
+    /** The callback for handling outgoing block-wise transfer.
+     *  This callback is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+     *  configuration is enabled.
+     */
+    otCoapBlockwiseTransmitHook     mTransmitHook;
+    void *                          mContext; ///< Application-specific context
+    struct otCoapBlockwiseResource *mNext;    ///< The next CoAP resource in the list
+} otCoapBlockwiseResource;
+
+/**
  * This structure represents the CoAP transmission parameters.
  *
  * @note mAckTimeout * ((2 ** (mMaxRetransmit + 1)) - 1) * (mAckRandomFactorNumerator / mAckRandomFactorDenominator)
@@ -539,7 +616,7 @@
  * @returns The actual size exponent value.
  *
  */
-uint16_t otCoapBlockSizeFromExponent(otCoapBlockSize aSize);
+uint16_t otCoapBlockSizeFromExponent(otCoapBlockSzx aSize);
 
 /**
  * This function appends a Block2 option
@@ -554,7 +631,7 @@
  * @retval OT_ERROR_NO_BUFS       The option length exceeds the buffer size.
  *
  */
-otError otCoapMessageAppendBlock2Option(otMessage *aMessage, uint32_t aNum, bool aMore, otCoapBlockSize aSize);
+otError otCoapMessageAppendBlock2Option(otMessage *aMessage, uint32_t aNum, bool aMore, otCoapBlockSzx aSize);
 
 /**
  * This function appends a Block1 option
@@ -569,7 +646,7 @@
  * @retval OT_ERROR_NO_BUFS       The option length exceeds the buffer size.
  *
  */
-otError otCoapMessageAppendBlock1Option(otMessage *aMessage, uint32_t aNum, bool aMore, otCoapBlockSize aSize);
+otError otCoapMessageAppendBlock1Option(otMessage *aMessage, uint32_t aNum, bool aMore, otCoapBlockSzx aSize);
 
 /**
  * This function appends a Proxy-Uri option.
@@ -805,6 +882,73 @@
                                         const otCoapTxParameters *aTxParameters);
 
 /**
+ * This function sends a CoAP request block-wise with custom transmission parameters.
+ *
+ * This function is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE configuration
+ * is enabled.
+ *
+ * If a response for a request is expected, respective function and context information should be provided.
+ * If the response is expected to be block-wise, a respective hook function should be provided.
+ * If no response is expected, these arguments should be NULL pointers.
+ *
+ * @param[in]  aInstance        A pointer to an OpenThread instance.
+ * @param[in]  aMessage         A pointer to the message to send.
+ * @param[in]  aMessageInfo     A pointer to the message info associated with @p aMessage.
+ * @param[in]  aHandler         A function pointer that shall be called on response reception or timeout.
+ * @param[in]  aContext         A pointer to arbitrary context information. May be NULL if not used.
+ * @param[in]  aTxParameters    A pointer to transmission parameters for this request. Use NULL for defaults.
+ * @param[in]  aTransmitHook    A pointer to a hook function for outgoing block-wise transfer.
+ * @param[in]  aReceiveHook     A pointer to a hook function for incoming block-wise transfer.
+ *
+ * @retval OT_ERROR_NONE    Successfully sent CoAP message.
+ * @retval OT_ERROR_NO_BUFS Failed to allocate retransmission data.
+ *
+ */
+otError otCoapSendRequestBlockWiseWithParameters(otInstance *                aInstance,
+                                                 otMessage *                 aMessage,
+                                                 const otMessageInfo *       aMessageInfo,
+                                                 otCoapResponseHandler       aHandler,
+                                                 void *                      aContext,
+                                                 const otCoapTxParameters *  aTxParameters,
+                                                 otCoapBlockwiseTransmitHook aTransmitHook,
+                                                 otCoapBlockwiseReceiveHook  aReceiveHook);
+
+/**
+ * This function sends a CoAP request block-wise.
+ *
+ * This function is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE configuration
+ * is enabled.
+ *
+ * If a response for a request is expected, respective function and context information should be provided.
+ * If the response is expected to be block-wise, a respective hook function should be provided.
+ * If no response is expected, these arguments should be NULL pointers.
+ *
+ * @param[in]  aInstance     A pointer to an OpenThread instance.
+ * @param[in]  aMessage      A pointer to the message to send.
+ * @param[in]  aMessageInfo  A pointer to the message info associated with @p aMessage.
+ * @param[in]  aHandler      A function pointer that shall be called on response reception or timeout.
+ * @param[in]  aContext      A pointer to arbitrary context information. May be NULL if not used.
+ * @param[in]  aTransmitHook A pointer to a hook function for outgoing block-wise transfer.
+ * @param[in]  aReceiveHook  A pointer to a hook function for incoming block-wise transfer.
+ *
+ * @retval OT_ERROR_NONE    Successfully sent CoAP message.
+ * @retval OT_ERROR_NO_BUFS Failed to allocate retransmission data.
+ *
+ */
+static inline otError otCoapSendRequestBlockWise(otInstance *                aInstance,
+                                                 otMessage *                 aMessage,
+                                                 const otMessageInfo *       aMessageInfo,
+                                                 otCoapResponseHandler       aHandler,
+                                                 void *                      aContext,
+                                                 otCoapBlockwiseTransmitHook aTransmitHook,
+                                                 otCoapBlockwiseReceiveHook  aReceiveHook)
+{
+    // NOLINTNEXTLINE(modernize-use-nullptr)
+    return otCoapSendRequestBlockWiseWithParameters(aInstance, aMessage, aMessageInfo, aHandler, aContext, NULL,
+                                                    aTransmitHook, aReceiveHook);
+}
+
+/**
  * This function sends a CoAP request.
  *
  * If a response for a request is expected, respective function and context information should be provided.
@@ -871,6 +1015,24 @@
 void otCoapRemoveResource(otInstance *aInstance, otCoapResource *aResource);
 
 /**
+ * This function adds a block-wise resource to the CoAP server.
+ *
+ * @param[in]  aInstance  A pointer to an OpenThread instance.
+ * @param[in]  aResource  A pointer to the resource.
+ *
+ */
+void otCoapAddBlockWiseResource(otInstance *aInstance, otCoapBlockwiseResource *aResource);
+
+/**
+ * This function removes a block-wise resource from the CoAP server.
+ *
+ * @param[in]  aInstance  A pointer to an OpenThread instance.
+ * @param[in]  aResource  A pointer to the resource.
+ *
+ */
+void otCoapRemoveBlockWiseResource(otInstance *aInstance, otCoapBlockwiseResource *aResource);
+
+/**
  * This function sets the default handler for unhandled CoAP requests.
  *
  * @param[in]  aInstance  A pointer to an OpenThread instance.
@@ -898,6 +1060,56 @@
                                          const otCoapTxParameters *aTxParameters);
 
 /**
+ * This function sends a CoAP response block-wise from the server with custom transmission parameters.
+ *
+ * This function is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE configuration
+ * is enabled.
+ *
+ * @param[in]  aInstance        A pointer to an OpenThread instance.
+ * @param[in]  aMessage         A pointer to the CoAP response to send.
+ * @param[in]  aMessageInfo     A pointer to the message info associated with @p aMessage.
+ * @param[in]  aTxParameters    A pointer to transmission parameters for this response. Use NULL for defaults.
+ * @param[in]  aContext         A pointer to arbitrary context information. May be NULL if not used.
+ * @param[in]  aTransmitHook    A pointer to a hook function for outgoing block-wise transfer.
+ *
+ * @retval OT_ERROR_NONE     Successfully enqueued the CoAP response message.
+ * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to send the CoAP response.
+ *
+ */
+otError otCoapSendResponseBlockWiseWithParameters(otInstance *                aInstance,
+                                                  otMessage *                 aMessage,
+                                                  const otMessageInfo *       aMessageInfo,
+                                                  const otCoapTxParameters *  aTxParameters,
+                                                  void *                      aContext,
+                                                  otCoapBlockwiseTransmitHook aTransmitHook);
+
+/**
+ * This function sends a CoAP response block-wise from the server.
+ *
+ * This function is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE configuration
+ * is enabled.
+ *
+ * @param[in]  aInstance     A pointer to an OpenThread instance.
+ * @param[in]  aMessage      A pointer to the CoAP response to send.
+ * @param[in]  aMessageInfo  A pointer to the message info associated with @p aMessage.
+ * @param[in]  aContext      A pointer to arbitrary context information. May be NULL if not used.
+ * @param[in]  aTransmitHook A pointer to a hook function for outgoing block-wise transfer.
+ *
+ * @retval OT_ERROR_NONE     Successfully enqueued the CoAP response message.
+ * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to send the CoAP response.
+ *
+ */
+static inline otError otCoapSendResponseBlockWise(otInstance *                aInstance,
+                                                  otMessage *                 aMessage,
+                                                  const otMessageInfo *       aMessageInfo,
+                                                  void *                      aContext,
+                                                  otCoapBlockwiseTransmitHook aTransmitHook)
+{
+    // NOLINTNEXTLINE(modernize-use-nullptr)
+    return otCoapSendResponseBlockWiseWithParameters(aInstance, aMessage, aMessageInfo, NULL, aContext, aTransmitHook);
+}
+
+/**
  * This function sends a CoAP response from the server.
  *
  * @param[in]  aInstance     A pointer to an OpenThread instance.
diff --git a/include/openthread/coap_secure.h b/include/openthread/coap_secure.h
index 11caec5..ee17716 100644
--- a/include/openthread/coap_secure.h
+++ b/include/openthread/coap_secure.h
@@ -230,6 +230,35 @@
 bool otCoapSecureIsConnectionActive(otInstance *aInstance);
 
 /**
+ * This method sends a CoAP request block-wise over secure DTLS connection.
+ *
+ * This function is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE configuration
+ * is enabled.
+ *
+ * If a response for a request is expected, respective function and context information should be provided.
+ * If no response is expected, these arguments should be NULL pointers.
+ * If Message Id was not set in the header (equal to 0), this function will assign unique Message Id to the message.
+ *
+ * @param[in]  aInstance     A pointer to an OpenThread instance.
+ * @param[in]  aMessage      A reference to the message to send.
+ * @param[in]  aHandler      A function pointer that shall be called on response reception or time-out.
+ * @param[in]  aContext      A pointer to arbitrary context information.
+ * @param[in]  aTransmitHook A function pointer that is called on Block1 response reception.
+ * @param[in]  aReceiveHook  A function pointer that is called on Block2 response reception.
+ *
+ * @retval OT_ERROR_NONE           Successfully sent CoAP message.
+ * @retval OT_ERROR_NO_BUFS        Failed to allocate retransmission data.
+ * @retval OT_ERROR_INVALID_STATE  DTLS connection was not initialized.
+ *
+ */
+otError otCoapSecureSendRequestBlockWise(otInstance *                aInstance,
+                                         otMessage *                 aMessage,
+                                         otCoapResponseHandler       aHandler,
+                                         void *                      aContext,
+                                         otCoapBlockwiseTransmitHook aTransmitHook,
+                                         otCoapBlockwiseReceiveHook  aReceiveHook);
+
+/**
  * This method sends a CoAP request over secure DTLS connection.
  *
  * If a response for a request is expected, respective function and context information should be provided.
@@ -270,6 +299,24 @@
 void otCoapSecureRemoveResource(otInstance *aInstance, otCoapResource *aResource);
 
 /**
+ * This function adds a block-wise resource to the CoAP Secure server.
+ *
+ * @param[in]  aInstance  A pointer to an OpenThread instance.
+ * @param[in]  aResource  A pointer to the resource.
+ *
+ */
+void otCoapSecureAddBlockWiseResource(otInstance *aInstance, otCoapBlockwiseResource *aResource);
+
+/**
+ * This function removes a block-wise resource from the CoAP Secure server.
+ *
+ * @param[in]  aInstance  A pointer to an OpenThread instance.
+ * @param[in]  aResource  A pointer to the resource.
+ *
+ */
+void otCoapSecureRemoveBlockWiseResource(otInstance *aInstance, otCoapBlockwiseResource *aResource);
+
+/**
  * This function sets the default handler for unhandled CoAP Secure requests.
  *
  * @param[in]  aInstance  A pointer to an OpenThread instance.
@@ -293,6 +340,28 @@
                                             void *                          aContext);
 
 /**
+ * This function sends a CoAP response block-wise from the CoAP Secure server.
+ *
+ * This function is available when OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE configuration
+ * is enabled.
+ *
+ * @param[in]  aInstance     A pointer to an OpenThread instance.
+ * @param[in]  aMessage      A pointer to the CoAP response to send.
+ * @param[in]  aMessageInfo  A pointer to the message info associated with @p aMessage.
+ * @param[in]  aContext      A pointer to arbitrary context information. May be NULL if not used.
+ * @param[in]  aTransmitHook A function pointer that is called on Block1 request reception.
+ *
+ * @retval OT_ERROR_NONE     Successfully enqueued the CoAP response message.
+ * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to send the CoAP response.
+ *
+ */
+otError otCoapSecureSendResponseBlockWise(otInstance *                aInstance,
+                                          otMessage *                 aMessage,
+                                          const otMessageInfo *       aMessageInfo,
+                                          void *                      aContext,
+                                          otCoapBlockwiseTransmitHook aTransmitHook);
+
+/**
  * This function sends a CoAP response from the CoAP Secure server.
  *
  * @param[in]  aInstance     A pointer to an OpenThread instance.
diff --git a/include/openthread/dataset.h b/include/openthread/dataset.h
index 04d805d..8ec38d2 100644
--- a/include/openthread/dataset.h
+++ b/include/openthread/dataset.h
@@ -44,7 +44,7 @@
 #endif
 
 /**
- * @addtogroup api-thread-general
+ * @addtogroup api-operational-dataset
  *
  * @{
  *
diff --git a/include/openthread/dataset_ftd.h b/include/openthread/dataset_ftd.h
index 4e28aae..f1ae621 100644
--- a/include/openthread/dataset_ftd.h
+++ b/include/openthread/dataset_ftd.h
@@ -43,7 +43,7 @@
 #endif
 
 /**
- * @addtogroup api-thread-general
+ * @addtogroup api-operational-dataset
  *
  * @{
  *
diff --git a/include/openthread/dataset_updater.h b/include/openthread/dataset_updater.h
index 5706863..627ddb3 100644
--- a/include/openthread/dataset_updater.h
+++ b/include/openthread/dataset_updater.h
@@ -43,7 +43,7 @@
 #endif
 
 /**
- * @addtogroup api-dataset-updater
+ * @addtogroup api-operational-dataset
  *
  * @brief
  *   This module includes functions for Dataset Updater.
@@ -80,7 +80,6 @@
  * @param[in]  aDataset                A pointer to the Dataset containing the fields to change.
  * @param[in]  aCallback               A callback to indicate when Dataset update request finishes.
  * @param[in]  aContext                An arbitrary context passed to callback.
- * @param[in]  aRetryWaitInterval      The wait time after sending Pending dataset before retrying (interval in ms).
  *
  * @retval OT_ERROR_NONE           Dataset update started successfully (@p aCallback will be invoked on completion).
  * @retval OT_ERROR_INVALID_STATE  Device is disabled (MLE is disabled).
@@ -92,8 +91,7 @@
 otError otDatasetUpdaterRequestUpdate(otInstance *                aInstance,
                                       const otOperationalDataset *aDataset,
                                       otDatasetUpdaterCallback    aCallback,
-                                      void *                      aContext,
-                                      uint32_t                    aReryWaitInterval);
+                                      void *                      aContext);
 
 /**
  * This function cancels an ongoing (if any) Operational Dataset update request.
diff --git a/include/openthread/dns.h b/include/openthread/dns.h
index a964781..f34322b 100644
--- a/include/openthread/dns.h
+++ b/include/openthread/dns.h
@@ -29,14 +29,15 @@
 /**
  * @file
  * @brief
- *  This file defines the top-level dns functions for the OpenThread library.
+ *  This file defines the top-level DNS functions for the OpenThread library.
  */
 
 #ifndef OPENTHREAD_DNS_H_
 #define OPENTHREAD_DNS_H_
 
-#include <openthread/ip6.h>
-#include <openthread/message.h>
+#include <stdint.h>
+
+#include <openthread/error.h>
 
 #ifdef __cplusplus
 extern "C" {
@@ -52,60 +53,92 @@
  *
  */
 
-#define OT_DNS_MAX_HOSTNAME_LENGTH 62 ///< Maximum allowed hostname length (maximum label size - 1 for compression).
+#define OT_DNS_MAX_NAME_SIZE 255 ///< Maximum name string size (includes null char at the end of string).
 
-#define OT_DNS_DEFAULT_SERVER_IP "2001:4860:4860::8888" ///< Defines default DNS Server address - Google DNS.
-#define OT_DNS_DEFAULT_SERVER_PORT 53                   ///< Defines default DNS Server port.
+#define OT_DNS_MAX_LABEL_SIZE 64 ///< Maximum label string size (include null char at the end of string).
+
+#define OT_DNS_TXT_KEY_MIN_LENGTH 1 ///< Minimum length of TXT record key string (RFC 6763 - section 6.4).
+
+#define OT_DNS_TXT_KEY_MAX_LENGTH 9 ///< Recommended maximum length of TXT record key string (RFC 6763 - section 6.4).
 
 /**
- * This structure implements DNS Query parameters.
+ * This structure represents a TXT record entry representing a key/value pair (RFC 6763 - section 6.3).
+ *
+ * The string buffers pointed to by `mKey` and `mValue` MUST persist and remain unchanged after an instance of such
+ * structure is passed to OpenThread (as part of `otSrpClientService` instance).
+ *
+ * An array of `otDnsTxtEntry` entries are used in `otSrpClientService` to specify the full TXT record (a list of
+ * entries).
  *
  */
-typedef struct otDnsQuery
+typedef struct otDnsTxtEntry
 {
-    const char *         mHostname;    ///< Identifies hostname to be found. It shall not change during resolving.
-    const otMessageInfo *mMessageInfo; ///< A reference to the message info related with DNS Server.
-    bool                 mNoRecursion; ///< If cleared, it directs name server to pursue the query recursively.
-} otDnsQuery;
+    /**
+     * The TXT record key string.
+     *
+     * If `mKey` is not NULL, then it MUST be a null-terminated C string. The entry is treated as key/value pair with
+     * `mValue` buffer providing the value.
+     *   - The entry is encoded as follows:
+     *        - A single string length byte followed by "key=value" format (without the quotation marks).
+              - In this case, the overall encoded length must be 255 bytes or less.
+     *   - If `mValue` is NULL, then key is treated as a boolean attribute and encoded as "key" (with no `=`).
+     *   - If `mValue` is not NULL but `mValueLength` is zero, then it is treated as empty value and encoded as "key=".
+     *
+     * If `mKey` is NULL, then `mValue` buffer is treated as an already encoded TXT-DATA and is appended as is in the
+     * DNS message.
+     *
+     */
+    const char *   mKey;
+    const uint8_t *mValue;       ///< The TXT record value or already encoded TXT-DATA (depending on `mKey`).
+    uint16_t       mValueLength; ///< Number of bytes in `mValue` buffer.
+} otDnsTxtEntry;
 
 /**
- * This function pointer is called when a DNS response is received.
+ * This structure represents an iterator for TXT record entires (key/value pairs).
  *
- * @param[in]  aContext   A pointer to application-specific context.
- * @param[in]  aHostname  Identifies hostname related with DNS response.
- * @param[in]  aAddress   A pointer to the IPv6 address received in DNS response. May be null.
- * @param[in]  aTtl       Specifies the maximum time in seconds that the resource record may be cached.
- * @param[in]  aResult    A result of the DNS transaction.
- *
- * @retval  OT_ERROR_NONE              A response was received successfully and IPv6 address is provided
- *                                     in @p aAddress.
- * @retval  OT_ERROR_ABORT             A DNS transaction was aborted by stack.
- * @retval  OT_ERROR_RESPONSE_TIMEOUT  No DNS response has been received within timeout.
- * @retval  OT_ERROR_NOT_FOUND         A response was received but no IPv6 address has been found.
- * @retval  OT_ERROR_FAILED            A response was received but status code is different than success.
+ * The data fields in this structure are intended for use by OpenThread core and caller should not read or change them.
  *
  */
-typedef void (*otDnsResponseHandler)(void *              aContext,
-                                     const char *        aHostname,
-                                     const otIp6Address *aAddress,
-                                     uint32_t            aTtl,
-                                     otError             aResult);
+typedef struct otDnsTxtEntryIterator
+{
+    const void *mPtr;
+    uint16_t    mData[2];
+    char        mChar[OT_DNS_TXT_KEY_MAX_LENGTH + 1];
+} otDnsTxtEntryIterator;
 
 /**
- * This function sends a DNS query for AAAA (IPv6) record.
+ * This function initializes a TXT record iterator.
  *
- * This function is available only if feature `OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE` is enabled.
+ * The buffer pointer @p aTxtData and its content MUST persist and remain unchanged while @p aIterator object
+ * is being used.
  *
- * @param[in]  aInstance   A pointer to an OpenThread instance.
- * @param[in]  aQuery      A pointer to specify DNS query parameters.
- * @param[in]  aHandler    A function pointer that shall be called on response reception or time-out.
- * @param[in]  aContext    A pointer to arbitrary context information.
+ * @param[in] aIterator       A pointer to the iterator to initialize (MUST NOT be NULL).
+ * @param[in] aTxtData        A pointer to buffer containing the encoded TXT data.
+ * @param[in] aTxtDataLength  The length (number of bytes) of @p aTxtData.
  *
  */
-otError otDnsClientQuery(otInstance *         aInstance,
-                         const otDnsQuery *   aQuery,
-                         otDnsResponseHandler aHandler,
-                         void *               aContext);
+void otDnsInitTxtEntryIterator(otDnsTxtEntryIterator *aIterator, const uint8_t *aTxtData, uint16_t aTxtDataLength);
+
+/**
+ * This function parses the TXT data from an iterator and gets the next TXT record entry (key/value pair).
+ *
+ * The @p aIterator MUST be initialized using `otDnsInitTxtEntryIterator()` before calling this function and the TXT
+ * data buffer used to initialize the iterator MUST persist and remain unchanged. Otherwise the behavior of this
+ * function is undefined.
+ *
+ * If the parsed key string length is smaller than or equal to `OT_DNS_TXT_KEY_MAX_LENGTH` (recommended max key length)
+ * the key string is returned in `mKey` in @p aEntry. But if the key is longer, then `mKey` is set to NULL and the
+ * entire encoded TXT entry string is returned in `mValue` and `mValueLength`.
+ *
+ * @param[in]  aIterator   A pointer to the iterator (MUST NOT be NULL).
+ * @param[out] aEntry      A pointer to a `otDnsTxtEntry` structure to output the parsed/read entry (MUST NOT be NULL).
+ *
+ * @retval OT_ERROR_NONE       The next entry was parsed successfully. @p aEntry is updated.
+ * @retval OT_ERROR_NOT_FOUND  No more entries in the TXT data.
+ * @retval OT_ERROR_PARSE      The TXT data from @p aIterator is not well-formed.
+ *
+ */
+otError otDnsGetNextTxtEntry(otDnsTxtEntryIterator *aIterator, otDnsTxtEntry *aEntry);
 
 /**
  * @}
diff --git a/include/openthread/dns_client.h b/include/openthread/dns_client.h
new file mode 100644
index 0000000..04450ce
--- /dev/null
+++ b/include/openthread/dns_client.h
@@ -0,0 +1,537 @@
+/*
+ *  Copyright (c) 2017-2021, The OpenThread Authors.
+ *  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
+ * @brief
+ *  This file defines the top-level DNS functions for the OpenThread library.
+ */
+
+#ifndef OPENTHREAD_DNS_CLIENT_H_
+#define OPENTHREAD_DNS_CLIENT_H_
+
+#include <openthread/dns.h>
+#include <openthread/instance.h>
+#include <openthread/ip6.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @addtogroup api-dns
+ *
+ * @brief
+ *   This module includes functions that control DNS communication.
+ *
+ *   The functions in this module are available only if feature `OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE` is enabled.
+ *
+ * @{
+ *
+ */
+
+/**
+ * This enumeration type represents the "Recursion Desired" (RD) flag in an `otDnsQueryConfig`.
+ *
+ */
+typedef enum
+{
+    OT_DNS_FLAG_UNSPECIFIED       = 0, ///< Indicates the flag is not specified.
+    OT_DNS_FLAG_RECURSION_DESIRED = 1, ///< Indicates DNS name server can resolve the query recursively.
+    OT_DNS_FLAG_NO_RECURSION      = 2, ///< Indicates DNS name server can not resolve the query recursively.
+} otDnsRecursionFlag;
+
+/**
+ * This structure represents a DNS query configuration.
+ *
+ * Any of the fields in this structure can be set to zero to indicate that it is not specified. How the unspecified
+ * fields are treated is determined by the function which uses the instance of `otDnsQueryConfig`.
+ *
+ */
+typedef struct otDnsQueryConfig
+{
+    otSockAddr         mServerSockAddr;  ///< Server address (IPv6 address/port). All zero or zero port for unspecified.
+    uint32_t           mResponseTimeout; ///< Wait time (in msec) to rx response. Zero indicates unspecified value.
+    uint8_t            mMaxTxAttempts;   ///< Maximum tx attempts before reporting failure. Zero for unspecified value.
+    otDnsRecursionFlag mRecursionFlag;   ///< Indicates whether the server can resolve the query recursively or not.
+} otDnsQueryConfig;
+
+/**
+ * This function gets the current default query config used by DNS client.
+ *
+ * When OpenThread stack starts, the default DNS query config is determined from a set of OT config options such as
+ * `OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_IP6_ADDRESS`, `_DEFAULT_SERVER_PORT`, `_DEFAULT_RESPONSE_TIMEOUT`, etc.
+ * (see `config/dns_clinet.h` for all related config options).
+ *
+ * @param[in]  aInstance        A pointer to an OpenThread instance.
+ *
+ * @returns A pointer to the current default config being used by DNS client.
+ *
+ */
+const otDnsQueryConfig *otDnsClientGetDefaultConfig(otInstance *aInstance);
+
+/**
+ * This function sets the default query config on DNS client.
+ *
+ * @note Any ongoing query will continue to use the config from when it was started. The new default config will be
+ * used for any future DNS queries.
+ *
+ * The @p aConfig can be NULL. In this case the default config will be set to the defaults from OT config options
+ * `OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_{}`. This resets the default query config back to to the config when the
+ * OpenThread stack starts.
+ *
+ * In a non-NULL @p aConfig, caller can choose to leave some of the fields in `otDnsQueryConfig` instance unspecified
+ * (value zero). The unspecified fields are replaced by the corresponding OT config option definitions
+ * `OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_{}` to form the default query config.
+ *
+ * @param[in]  aInstance   A pointer to an OpenThread instance.
+ * @param[in]  aConfig     A pointer to the new query config to use as default.
+ *
+ */
+void otDnsClientSetDefaultConfig(otInstance *aInstance, const otDnsQueryConfig *aConfig);
+
+/**
+ * This type is an opaque representation of a response to an address resolution DNS query.
+ *
+ * Pointers to instance of this type are provided from callback `otDnsAddressCallback`.
+ *
+ */
+typedef struct otDnsAddressResponse otDnsAddressResponse;
+
+/**
+ * This function pointer is called when a DNS response is received for an address resolution query.
+ *
+ * Within this callback the user can use `otDnsAddressResponseGet{Item}()` functions along with the @p aResponse
+ * pointer to get more info about the response.
+ *
+ * The @p aResponse pointer can only be used within this callback and after returning from this function it will not
+ * stay valid, so the user MUST NOT retain the @p aResponse pointer for later use.
+ *
+ * @param[in]  aError     The result of the DNS transaction.
+ * @param[in]  aResponse  A pointer to the response (it is always non-NULL).
+ * @param[in]  aContext   A pointer to application-specific context.
+ *
+ * The @p aError can have the following:
+ *
+ *  - OT_ERROR_NONE              A response was received successfully.
+ *  - OT_ERROR_ABORT             A DNS transaction was aborted by stack.
+ *  - OT_ERROR_RESPONSE_TIMEOUT  No DNS response has been received within timeout.
+ *
+ * If the server rejects the address resolution request the error code from server is mapped as follow:
+ *
+ *  - (0)  NOERROR   Success (no error condition)                    -> OT_ERROR_NONE
+ *  - (1)  FORMERR   Server unable to interpret due to format error  -> OT_ERROR_PARSE
+ *  - (2)  SERVFAIL  Server encountered an internal failure          -> OT_ERROR_FAILED
+ *  - (3)  NXDOMAIN  Name that ought to exist, does not exist        -> OT_ERROR_NOT_FOUND
+ *  - (4)  NOTIMP    Server does not support the query type (OpCode) -> OT_ERROR_NOT_IMPLEMENTED
+ *  - (5)  REFUSED   Server refused for policy/security reasons      -> OT_ERROR_SECURITY
+ *  - (6)  YXDOMAIN  Some name that ought not to exist, does exist   -> OT_ERROR_DUPLICATED
+ *  - (7)  YXRRSET   Some RRset that ought not to exist, does exist  -> OT_ERROR_DUPLICATED
+ *  - (8)  NXRRSET   Some RRset that ought to exist, does not exist  -> OT_ERROR_NOT_FOUND
+ *  - (9)  NOTAUTH   Service is not authoritative for zone           -> OT_ERROR_SECURITY
+ *  - (10) NOTZONE   A name is not in the zone                       -> OT_ERROR_PARSE
+ *  - (20) BADNAME   Bad name                                        -> OT_ERROR_PARSE
+ *  - (21) BADALG    Bad algorithm                                   -> OT_ERROR_SECURITY
+ *  - (22) BADTRUN   Bad truncation                                  -> OT_ERROR_PARSE
+ *  - Other response codes                                           -> OT_ERROR_FAILED
+ *
+ */
+typedef void (*otDnsAddressCallback)(otError aError, const otDnsAddressResponse *aResponse, void *aContext);
+
+/**
+ * This function sends an address resolution DNS query for AAAA (IPv6) record(s) for a given host name.
+ *
+ * The @p aConfig can be NULL. In this case the default config (from `otDnsClientGetDefaultConfig()`) will be used as
+ * the config for this query. In a non-NULL @p aConfig, some of the fields can be left unspecified (value zero). The
+ * unspecified fields are then replaced by the values from the default config.
+ *
+ * @param[in]  aInstance        A pointer to an OpenThread instance.
+ * @param[in]  aHostName        The host name for which to query the address (MUST NOT be NULL).
+ * @param[in]  aCallback        A function pointer that shall be called on response reception or time-out.
+ * @param[in]  aContext         A pointer to arbitrary context information.
+ * @param[in]  aConfig          A pointer to the config to use for this query.
+ *
+ * @retval OT_ERROR_NONE          Query sent successfully. @p aCallback will be invoked to report the status.
+ * @retval OT_ERROR_NO_BUFS       Insufficient buffer to prepare and send query.
+ *
+ */
+otError otDnsClientResolveAddress(otInstance *            aInstance,
+                                  const char *            aHostName,
+                                  otDnsAddressCallback    aCallback,
+                                  void *                  aContext,
+                                  const otDnsQueryConfig *aConfig);
+
+/**
+ * This function gets the full host name associated with an address resolution DNS response.
+ *
+ * This function MUST only be used from `otDnsAddressCallback`.
+ *
+ * @param[in]  aResponse         A pointer to the response.
+ * @param[out] aNameBuffer       A buffer to char array to output the full host name (MUST NOT be NULL).
+ * @param[in]  aNameBufferSize   The size of @p aNameBuffer.
+ *
+ * @retval OT_ERROR_NONE     The full host name was read successfully.
+ * @retval OT_ERROR_NO_BUFS  The name does not fit in @p aNameBuffer.
+ *
+ */
+otError otDnsAddressResponseGetHostName(const otDnsAddressResponse *aResponse,
+                                        char *                      aNameBuffer,
+                                        uint16_t                    aNameBufferSize);
+
+/**
+ * This function gets an IPv6 address associated with an address resolution DNS response.
+ *
+ * This function MUST only be used from `otDnsAddressCallback`.
+ *
+ * The response may include multiple IPv6 address records. @p aIndex can be used to iterate through the list of
+ * addresses. Index zero gets the first address and so on. When we reach end of the list, `OT_ERROR_NOT_FOUND` is
+ * returned.
+ *
+ * @param[in]  aResponse     A pointer to the response.
+ * @param[in]  aIndex        The address record index to retrieve.
+ * @param[out] aAddress      A pointer to a IPv6 address to output the address (MUST NOT be NULL).
+ * @param[out] aTtl          A pointer to an `uint32_t` to output TTL for the address. It can be NULL if caller does not
+ *                           want to get the TTL.
+ *
+ * @retval OT_ERROR_NONE       The address was read successfully.
+ * @retval OT_ERROR_NOT_FOUND  No address record in @p aResponse at @p aIndex.
+ * @retval OT_ERROR_PARSE      Could not parse the records in the @p aResponse.
+ *
+ */
+otError otDnsAddressResponseGetAddress(const otDnsAddressResponse *aResponse,
+                                       uint16_t                    aIndex,
+                                       otIp6Address *              aAddress,
+                                       uint32_t *                  aTtl);
+
+/**
+ * This type is an opaque representation of a response to a browse (service instance enumeration) DNS query.
+ *
+ * Pointers to instance of this type are provided from callback `otDnsBrowseCallback`.
+ *
+ */
+typedef struct otDnsBrowseResponse otDnsBrowseResponse;
+
+/**
+ * This function pointer is called when a DNS response is received for a browse (service instance enumeration) query.
+ *
+ * Within this callback the user can use `otDnsBrowseResponseGet{Item}()` functions along with the @p aResponse
+ * pointer to get more info about the response.
+ *
+ * The @p aResponse pointer can only be used within this callback and after returning from this function it will not
+ * stay valid, so the user MUST NOT retain the @p aResponse pointer for later use.
+ *
+ * @param[in]  aError     The result of the DNS transaction.
+ * @param[in]  aResponse  A pointer to the response (it is always non-NULL).
+ * @param[in]  aContext   A pointer to application-specific context.
+ *
+ * For the full list of possible values for @p aError, please see `otDnsAddressCallback()`.
+ *
+ */
+typedef void (*otDnsBrowseCallback)(otError aError, const otDnsBrowseResponse *aResponse, void *aContext);
+
+/**
+ * This structure provides info for a DNS service instance.
+ *
+ */
+typedef struct otDnsServiceInfo
+{
+    uint32_t     mTtl;                ///< Service record TTL (in seconds).
+    uint16_t     mPort;               ///< Service port number.
+    uint16_t     mPriority;           ///< Service priority.
+    uint16_t     mWeight;             ///< Service weight.
+    char *       mHostNameBuffer;     ///< Buffer to output the service host name (can be NULL if not needed).
+    uint16_t     mHostNameBufferSize; ///< Size of `mHostNameBuffer`.
+    otIp6Address mHostAddress;        ///< The host IPv6 address. Set to all zero if not available.
+    uint32_t     mHostAddressTtl;     ///< The host address TTL.
+    uint8_t *    mTxtData;            ///< Buffer to output TXT data (can be NULL if not needed).
+    uint16_t     mTxtDataSize;        ///< On input, size of `mTxtData` buffer. On output `mTxtData` length.
+    uint32_t     mTxtDataTtl;         ///< The TXT data TTL.
+} otDnsServiceInfo;
+
+/**
+ * This function sends a DNS browse (service instance enumeration) query for a given service name.
+ *
+ * This function is available when `OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE` is enabled.
+ *
+ * The @p aConfig can be NULL. In this case the default config (from `otDnsClientGetDefaultConfig()`) will be used as
+ * the config for this query. In a non-NULL @p aConfig, some of the fields can be left unspecified (value zero). The
+ * unspecified fields are then replaced by the values from the default config.
+ *
+ * @param[in]  aInstance        A pointer to an OpenThread instance.
+ * @param[in]  aServiceName     The service name to query for (MUST NOT be NULL).
+ * @param[in]  aCallback        A function pointer that shall be called on response reception or time-out.
+ * @param[in]  aContext         A pointer to arbitrary context information.
+ * @param[in]  aConfig          A pointer to the config to use for this query.
+ *
+ * @retval OT_ERROR_NONE        Query sent successfully. @p aCallback will be invoked to report the status.
+ * @retval OT_ERROR_NO_BUFS     Insufficient buffer to prepare and send query.
+ *
+ */
+otError otDnsClientBrowse(otInstance *            aInstance,
+                          const char *            aServiceName,
+                          otDnsBrowseCallback     aCallback,
+                          void *                  aContext,
+                          const otDnsQueryConfig *aConfig);
+
+/**
+ * This function gets the service name associated with a DNS browse (service instance enumeration) response.
+ *
+ * This function MUST only be used from `otDnsBrowseCallback`.
+ *
+ * @param[in]  aResponse         A pointer to the response.
+ * @param[out] aNameBuffer       A buffer to char array to output the service name (MUST NOT be NULL).
+ * @param[in]  aNameBufferSize   The size of @p aNameBuffer.
+ *
+ * @retval OT_ERROR_NONE     The service name was read successfully.
+ * @retval OT_ERROR_NO_BUFS  The name does not fit in @p aNameBuffer.
+ *
+ */
+otError otDnsBrowseResponseGetServiceName(const otDnsBrowseResponse *aResponse,
+                                          char *                     aNameBuffer,
+                                          uint16_t                   aNameBufferSize);
+
+/**
+ * This function gets a service instance associated with a DNS browse (service instance enumeration) response.
+ *
+ * This function MUST only be used from `otDnsBrowseCallback`.
+ *
+ * The response may include multiple service instance records. @p aIndex can be used to iterate through the list. Index
+ * zero gives the the first record. When we reach end of the list, `OT_ERROR_NOT_FOUND` is returned.
+ *
+ * Note that this function gets the service instance label and not the full service instance name which is of the form
+ * `<Instance>.<Service>.<Domain>`.
+ *
+ * @param[in]  aResponse          A pointer to the response.
+ * @param[in]  aIndex             The service instance record index to retrieve.
+ * @param[out] aLabelBuffer       A buffer to char array to output the service instance label (MUST NOT be NULL).
+ * @param[in]  aLabelBufferSize   The size of @p aLabelBuffer.
+ *
+ * @retval OT_ERROR_NONE          The service instance was read successfully.
+ * @retval OT_ERROR_NO_BUFS       The name does not fit in @p aNameBuffer.
+ * @retval OT_ERROR_NOT_FOUND     No service instance record in @p aResponse at @p aIndex.
+ * @retval OT_ERROR_PARSE         Could not parse the records in the @p aResponse.
+ *
+ */
+otError otDnsBrowseResponseGetServiceInstance(const otDnsBrowseResponse *aResponse,
+                                              uint16_t                   aIndex,
+                                              char *                     aLabelBuffer,
+                                              uint8_t                    aLabelBufferSize);
+
+/**
+ * This function gets info for a service instance from a DNS browse (service instance enumeration) response.
+ *
+ * This function MUST only be used from `otDnsBrowseCallback`.
+ *
+ * A browse DNS response should include the SRV, TXT, and AAAA records for the service instances that are enumerated
+ * (note that it is a SHOULD and not a MUST requirement). This function tries to retrieve this info for a given service
+ * instance when available.
+ *
+ * - If no matching SRV record is found in @p aResponse, `OT_ERROR_NOT_FOUND` is returned.
+ * - If a matching SRV record is found in @p aResponse, @p aServiceInfo is updated and `OT_ERROR_NONE` is returned.
+ * - If no matching TXT record is found in @p aResponse, `mTxtDataSize` in @p aServiceInfo is set to zero.
+ * - If no matching AAAA record is found in @p aResponse, `mHostAddress is set to all zero or unspecified address.
+ * - If there are multiple AAAA records for the host name in @p aResponse, `mHostAddress` is set to the first one. The
+ *   other addresses can be retrieved using `otDnsBrowseResponseGetHostAddress()`.
+ *
+ * @param[in]  aResponse          A pointer to the response.
+ * @param[in]  aInstanceLabel     The service instance label (MUST NOT be NULL).
+ * @param[out] aServiceInfo       A `ServiceInfo` to output the service instance information (MUST NOT be NULL).
+ *
+ * @retval OT_ERROR_NONE          The service instance info was read. @p aServiceInfo is updated.
+ * @retval OT_ERROR_NOT_FOUND     Could not find a matching SRV record for @p aInstanceLabel.
+ * @retval OT_ERROR_NO_BUFS       The host name and/or TXT data could not fit in the given buffers.
+ * @retval OT_ERROR_PARSE         Could not parse the records in the @p aResponse.
+ *
+ */
+otError otDnsBrowseResponseGetServiceInfo(const otDnsBrowseResponse *aResponse,
+                                          const char *               aInstanceLabel,
+                                          otDnsServiceInfo *         aServiceInfo);
+
+/**
+ * This function gets the host IPv6 address from a DNS browse (service instance enumeration) response.
+ *
+ * This function MUST only be used from `otDnsBrowseCallback`.
+ *
+ * The response can include zero or more IPv6 address records. @p aIndex can be used to iterate through the list of
+ * addresses. Index zero gets the first address and so on. When we reach end of the list, `OT_ERROR_NOT_FOUND` is
+ * returned.
+ *
+ * @param[in]  aResponse     A pointer to the response.
+ * @param[in]  aHostName     The host name to get the address (MUST NOT be NULL).
+ * @param[in]  aIndex        The address record index to retrieve.
+ * @param[out] aAddress      A pointer to a IPv6 address to output the address (MUST NOT be NULL).
+ * @param[out] aTtl          A pointer to an `uint32_t` to output TTL for the address. It can be NULL if caller does
+ *                           not want to get the TTL.
+ *
+ * @retval OT_ERROR_NONE       The address was read successfully.
+ * @retval OT_ERROR_NOT_FOUND  No address record for @p aHostname in @p aResponse at @p aIndex.
+ * @retval OT_ERROR_PARSE      Could not parse the records in the @p aResponse.
+ *
+ */
+otError otDnsBrowseResponseGetHostAddress(const otDnsBrowseResponse *aResponse,
+                                          const char *               aHostName,
+                                          uint16_t                   aIndex,
+                                          otIp6Address *             aAddress,
+                                          uint32_t *                 aTtl);
+
+/**
+ * This type is an opaque representation of a response to a service instance resolution DNS query.
+ *
+ * Pointers to instance of this type are provided from callback `otDnsAddressCallback`.
+ *
+ */
+typedef struct otDnsServiceResponse otDnsServiceResponse;
+
+/**
+ * This function pointer is called when a DNS response is received for a service instance resolution query.
+ *
+ * Within this callback the user can use `otDnsServiceResponseGet{Item}()` functions along with the @p aResponse
+ * pointer to get more info about the response.
+ *
+ * The @p aResponse pointer can only be used within this callback and after returning from this function it will not
+ * stay valid, so the user MUST NOT retain the @p aResponse pointer for later use.
+ *
+ * @param[in]  aError     The result of the DNS transaction.
+ * @param[in]  aResponse  A pointer to the response (it is always non-NULL).
+ * @param[in]  aContext   A pointer to application-specific context.
+ *
+ * For the full list of possible values for @p aError, please see `otDnsAddressCallback()`.
+ *
+ */
+typedef void (*otDnsServiceCallback)(otError aError, const otDnsServiceResponse *aResponse, void *aContext);
+
+/**
+ * This function sends a DNS service instance resolution query for a given service instance.
+ *
+ * This function is available when `OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE` is enabled.
+ *
+ * The @p aConfig can be NULL. In this case the default config (from `otDnsClientGetDefaultConfig()`) will be used as
+ * the config for this query. In a non-NULL @p aConfig, some of the fields can be left unspecified (value zero). The
+ * unspecified fields are then replaced by the values from the default config.
+ *
+ * @param[in]  aInstance          A pointer to an OpenThread instance.
+ * @param[in]  aInstanceLabel     The service instance label.
+ * @param[in]  aServiceName       The service name (together with @p aInstanceLabel form full instance name).
+ * @param[in]  aCallback          A function pointer that shall be called on response reception or time-out.
+ * @param[in]  aContext           A pointer to arbitrary context information.
+ * @param[in]  aConfig            A pointer to the config to use for this query.
+ *
+ * @retval OT_ERROR_NONE          Query sent successfully. @p aCallback will be invoked to report the status.
+ * @retval OT_ERROR_NO_BUFS       Insufficient buffer to prepare and send query.
+ * @retval OT_ERROR_INVALID_ARGS  @p aInstanceLabel is NULL.
+ *
+ */
+otError otDnsClientResolveService(otInstance *            aInstance,
+                                  const char *            aInstanceLabel,
+                                  const char *            aServiceName,
+                                  otDnsServiceCallback    aCallback,
+                                  void *                  aContext,
+                                  const otDnsQueryConfig *aConfig);
+
+/**
+ * This function gets the service instance name associated with a DNS service instance resolution response.
+ *
+ * This function MUST only be used from `otDnsServiceCallback`.
+ *
+ * @param[in]  aResponse         A pointer to the response.
+ * @param[out] aLabelBuffer      A buffer to char array to output the service instance label (MUST NOT be NULL).
+ * @param[in]  aLabelBufferSize  The size of @p aLabelBuffer.
+ * @param[out] aNameBuffer       A buffer to char array to output the rest of service name (can be NULL if user is
+ *                               not interested in getting the name.
+ * @param[in]  aNameBufferSize   The size of @p aNameBuffer.
+ *
+ * @retval OT_ERROR_NONE     The service name was read successfully.
+ * @retval OT_ERROR_NO_BUFS  Either the label or name does not fit in the given buffers.
+ *
+ */
+otError otDnsServiceResponseGetServiceName(const otDnsServiceResponse *aResponse,
+                                           char *                      aLabelBuffer,
+                                           uint8_t                     aLabelBufferSize,
+                                           char *                      aNameBuffer,
+                                           uint16_t                    aNameBufferSize);
+
+/**
+ * This function gets info for a service instance from a DNS service instance resolution response.
+ *
+ * This function MUST only be used from `otDnsServiceCallback`.
+ *
+ * - If no matching SRV record is found in @p aResponse, `OT_ERROR_NOT_FOUND` is returned.
+ * - If a matching SRV record is found in @p aResponse, @p aServiceInfo is updated and `OT_ERROR_NONE` is returned.
+ * - If no matching TXT record is found in @p aResponse, `mTxtDataSize` in @p aServiceInfo is set to zero.
+ * - If no matching AAAA record is found in @p aResponse, `mHostAddress is set to all zero or unspecified address.
+ * - If there are multiple AAAA records for the host name in @p aResponse, `mHostAddress` is set to the first one. The
+ *   other addresses can be retrieved using `otDnsServiceResponseGetHostAddress()`.
+ *
+ * @param[in]  aResponse          A pointer to the response.
+ * @param[out] aServiceInfo       A `ServiceInfo` to output the service instance information (MUST NOT be NULL).
+ *
+ * @retval OT_ERROR_NONE          The service instance info was read. @p aServiceInfo is updated.
+ * @retval OT_ERROR_NOT_FOUND     Could not find a matching SRV record in @p aResponse.
+ * @retval OT_ERROR_NO_BUFS       The host name and/or TXT data could not fit in the given buffers.
+ * @retval OT_ERROR_PARSE         Could not parse the records in the @p aResponse.
+ *
+ */
+otError otDnsServiceResponseGetServiceInfo(const otDnsServiceResponse *aResponse, otDnsServiceInfo *aServiceInfo);
+
+/**
+ * This function gets the host IPv6 address from a DNS service instance resolution response.
+ *
+ * This function MUST only be used from `otDnsServiceCallback`.
+ *
+ * The response can include zero or more IPv6 address records. @p aIndex can be used to iterate through the list of
+ * addresses. Index zero gets the first address and so on. When we reach end of the list, `OT_ERROR_NOT_FOUND` is
+ * returned.
+ *
+ * @param[in]  aResponse     A pointer to the response.
+ * @param[in]  aHostName     The host name to get the address (MUST NOT be NULL).
+ * @param[in]  aIndex        The address record index to retrieve.
+ * @param[out] aAddress      A pointer to a IPv6 address to output the address (MUST NOT be NULL).
+ * @param[out] aTtl          A pointer to an `uint32_t` to output TTL for the address. It can be NULL if caller does
+ *                           not want to get the TTL.
+ *
+ * @retval OT_ERROR_NONE       The address was read successfully.
+ * @retval OT_ERROR_NOT_FOUND  No address record for @p aHostname in @p aResponse at @p aIndex.
+ * @retval OT_ERROR_PARSE      Could not parse the records in the @p aResponse.
+ *
+ */
+otError otDnsServiceResponseGetHostAddress(const otDnsServiceResponse *aResponse,
+                                           const char *                aHostName,
+                                           uint16_t                    aIndex,
+                                           otIp6Address *              aAddress,
+                                           uint32_t *                  aTtl);
+
+/**
+ * @}
+ *
+ */
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // OPENTHREAD_DNS_CLIENT_H_
diff --git a/include/openthread/heap.h b/include/openthread/heap.h
index ca93be3..b3484b8 100644
--- a/include/openthread/heap.h
+++ b/include/openthread/heap.h
@@ -51,27 +51,6 @@
  *
  */
 
-/**
- * Function pointer used to set external CAlloc function for OpenThread.
- *
- * @param[in]   aCount  Number of allocate units.
- * @param[in]   aSize   Unit size in bytes.
- *
- * @returns A pointer to the allocated memory.
- *
- * @retval  NULL    Indicates not enough memory.
- *
- */
-typedef void *(*otHeapCAllocFn)(size_t aCount, size_t aSize);
-
-/**
- * Function pointer used to set external Free function for OpenThread.
- *
- * @param[in]   aPointer    A pointer to the memory to free.
- *
- */
-typedef void (*otHeapFreeFn)(void *aPointer);
-
 // This is a temporary API and would be removed after moving heap to platform.
 // TODO: Remove after moving heap to platform.
 /**
@@ -89,18 +68,6 @@
 void otHeapFree(void *aPointer);
 
 /**
- * This function sets the external heap CAlloc and Free
- * functions to be used by the OpenThread stack.
- *
- * This function must be used before invoking instance initialization.
- *
- * @param[in]  aCAlloc  A pointer to external CAlloc function.
- * @param[in]  aFree    A pointer to external Free function.
- *
- */
-void otHeapSetCAllocFree(otHeapCAllocFn aCAlloc, otHeapFreeFn aFree);
-
-/**
  * @}
  *
  */
diff --git a/include/openthread/icmp6.h b/include/openthread/icmp6.h
index ca8c624..5a02c76 100644
--- a/include/openthread/icmp6.h
+++ b/include/openthread/icmp6.h
@@ -64,6 +64,8 @@
     OT_ICMP6_TYPE_PARAMETER_PROBLEM = 4,   ///< Parameter Problem
     OT_ICMP6_TYPE_ECHO_REQUEST      = 128, ///< Echo Request
     OT_ICMP6_TYPE_ECHO_REPLY        = 129, ///< Echo Reply
+    OT_ICMP6_TYPE_ROUTER_SOLICIT    = 133, ///< Router Solicitation
+    OT_ICMP6_TYPE_ROUTER_ADVERT     = 134, ///< Router Advertisement
 } otIcmp6Type;
 
 /**
diff --git a/include/openthread/instance.h b/include/openthread/instance.h
index 7e3d3bb..1b73e29 100644
--- a/include/openthread/instance.h
+++ b/include/openthread/instance.h
@@ -53,7 +53,7 @@
  * @note This number versions both OpenThread platform and user APIs.
  *
  */
-#define OPENTHREAD_API_VERSION (57)
+#define OPENTHREAD_API_VERSION (87)
 
 /**
  * @addtogroup api-instance
diff --git a/include/openthread/message.h b/include/openthread/message.h
index ba35279..9c59b52 100644
--- a/include/openthread/message.h
+++ b/include/openthread/message.h
@@ -53,12 +53,10 @@
  */
 
 /**
- * This structure points to an OpenThread message buffer.
+ * This type is an opaque representation of an OpenThread message buffer.
+ *
  */
-typedef struct otMessage
-{
-    struct otMessage *mNext; ///< A pointer to the next Message buffer.
-} otMessage;
+typedef struct otMessage otMessage;
 
 /**
  * This structure represents the message buffer information.
diff --git a/include/openthread/ncp.h b/include/openthread/ncp.h
index b5f07fe..7289672 100644
--- a/include/openthread/ncp.h
+++ b/include/openthread/ncp.h
@@ -55,12 +55,47 @@
  */
 
 /**
- * Initialize the NCP.
+ * This function pointer is called to send HDLC encoded NCP data.
+ *
+ * @param[in]  aBuf        A pointer to a buffer with an output.
+ * @param[in]  aBufLength  A length of the output data stored in the buffer.
+ *
+ * @returns                Number of bytes processed by the callback.
+ *
+ */
+typedef int (*otNcpHdlcSendCallback)(const uint8_t *aBuf, uint16_t aBufLength);
+
+/**
+ * This function is called after NCP send finished.
+ *
+ */
+void otNcpHdlcSendDone(void);
+
+/**
+ * This function is called after HDLC encoded NCP data received.
+ *
+ * @param[in]  aBuf        A pointer to a buffer.
+ * @param[in]  aBufLength  The length of the data stored in the buffer.
+ *
+ */
+void otNcpHdlcReceive(const uint8_t *aBuf, uint16_t aBufLength);
+
+/**
+ * Initialize the NCP based on HDLC framing.
+ *
+ * @param[in]  aInstance        The OpenThread instance structure.
+ * @param[in]  aSendCallback    The function pointer used to send NCP data.
+ *
+ */
+void otNcpHdlcInit(otInstance *aInstance, otNcpHdlcSendCallback aSendCallback);
+
+/**
+ * Initialize the NCP based on SPI framing.
  *
  * @param[in]  aInstance  The OpenThread instance structure.
  *
  */
-void otNcpInit(otInstance *aInstance);
+void otNcpSpiInit(otInstance *aInstance);
 
 /**
  * @brief Send data to the host via a specific stream.
diff --git a/include/openthread/netdiag.h b/include/openthread/netdiag.h
index 376780f..2d291a0 100644
--- a/include/openthread/netdiag.h
+++ b/include/openthread/netdiag.h
@@ -297,34 +297,26 @@
                                                void *               aContext);
 
 /**
- * This function registers a callback to provide received raw Network Diagnostic Get response payload.
+ * Send a Network Diagnostic Get request.
  *
  * @param[in]  aInstance         A pointer to an OpenThread instance.
+ * @param[in]  aDestination      A pointer to destination address.
+ * @param[in]  aTlvTypes         An array of Network Diagnostic TLV types.
+ * @param[in]  aCount            Number of types in aTlvTypes.
  * @param[in]  aCallback         A pointer to a function that is called when Network Diagnostic Get response
  *                               is received or NULL to disable the callback.
  * @param[in]  aCallbackContext  A pointer to application-specific context.
  *
- */
-void otThreadSetReceiveDiagnosticGetCallback(otInstance *                   aInstance,
-                                             otReceiveDiagnosticGetCallback aCallback,
-                                             void *                         aCallbackContext);
-
-/**
- * Send a Network Diagnostic Get request.
- *
- * @param[in]  aInstance      A pointer to an OpenThread instance.
- * @param[in]  aDestination   A pointer to destination address.
- * @param[in]  aTlvTypes      An array of Network Diagnostic TLV types.
- * @param[in]  aCount         Number of types in aTlvTypes.
- *
  * @retval OT_ERROR_NONE    Successfully queued the DIAG_GET.req.
  * @retval OT_ERROR_NO_BUFS Insufficient message buffers available to send DIAG_GET.req.
  *
  */
-otError otThreadSendDiagnosticGet(otInstance *        aInstance,
-                                  const otIp6Address *aDestination,
-                                  const uint8_t       aTlvTypes[],
-                                  uint8_t             aCount);
+otError otThreadSendDiagnosticGet(otInstance *                   aInstance,
+                                  const otIp6Address *           aDestination,
+                                  const uint8_t                  aTlvTypes[],
+                                  uint8_t                        aCount,
+                                  otReceiveDiagnosticGetCallback aCallback,
+                                  void *                         aCallbackContext);
 
 /**
  * Send a Network Diagnostic Reset request.
diff --git a/include/openthread/ping_sender.h b/include/openthread/ping_sender.h
new file mode 100644
index 0000000..61f4b05
--- /dev/null
+++ b/include/openthread/ping_sender.h
@@ -0,0 +1,127 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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
+ * @brief
+ *   This file includes the OpenThread API for ping sender module.
+ */
+
+#ifndef OPENTHREAD_PING_SENDER_H_
+#define OPENTHREAD_PING_SENDER_H_
+
+#include <stdint.h>
+
+#include <openthread/error.h>
+#include <openthread/instance.h>
+#include <openthread/ip6.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @addtogroup api-ping-sender
+ *
+ * @brief
+ *   This file includes the OpenThread API for the ping sender module.
+ *
+ * @{
+ *
+ */
+
+/**
+ * This structure represents a ping reply.
+ *
+ */
+typedef struct otPingSenderReply
+{
+    otIp6Address mSenderAddress;  ///< Sender IPv6 address (address from which ping reply was received).
+    uint32_t     mRoundTripTime;  ///< Round trip time in msec.
+    uint16_t     mSize;           ///< Data size (number of bytes) in reply (excluding IPv6 and ICMP6 headers).
+    uint16_t     mSequenceNumber; ///< Sequence number.
+    uint8_t      mHopLimit;       ///< Hop limit.
+} otPingSenderReply;
+
+/**
+ * This function pointer type specifies the callback to notify receipt of a ping reply.
+ *
+ * @param[in] aReply      A pointer to a `otPingSenderReply` containing info about the received ping reply.
+ * @param[in] aContext    A pointer to application-specific context.
+ *
+ */
+typedef void (*otPingSenderCallback)(const otPingSenderReply *aReply, void *aContext);
+
+/**
+ * This structure represents a ping request configuration.
+ *
+ */
+typedef struct otPingSenderConfig
+{
+    otIp6Address         mDestination;       ///< Destination address to ping.
+    otPingSenderCallback mCallback;          ///< Callback function to report replies (can be NULL if not needed).
+    void *               mCallbackContext;   ///< A pointer to the callback application-specific context.
+    uint16_t             mSize;              ///< Data size (# of bytes) excludes IPv6/ICMPv6 header. Zero for default.
+    uint16_t             mCount;             ///< Number of ping messages to send. Zero to use default.
+    uint32_t             mInterval;          ///< Ping tx interval in milliseconds. Zero to use default.
+    uint8_t              mHopLimit;          ///< Hop limit (used if `mAllowZeroHopLimit` is false). Zero for default.
+    bool                 mAllowZeroHopLimit; ///< Indicates whether hop limit is zero.
+} otPingSenderConfig;
+
+/**
+ * This function starts a ping.
+ *
+ * @param[in] aInstance            A pointer to an OpenThread instance.
+ * @param[in] aConfig              The ping config to use.
+ *
+ * @retval OT_ERROR_NONE           The ping started successfully.
+ * @retval OT_ERROR_BUSY           Could not start since busy with a previous ongoing ping request.
+ * @retval OT_ERROR_INVALID_ARGS   The @p aConfig contains invalid parameters (e.g., ping interval is too long).
+
+ *
+ */
+otError otPingSenderPing(otInstance *aInstance, const otPingSenderConfig *aConfig);
+
+/**
+ * This function stops an ongoing ping.
+ *
+ * @param[in] aInstance            A pointer to an OpenThread instance.
+ *
+ */
+void otPingSenderStop(otInstance *aInstance);
+
+/**
+ * @}
+ *
+ */
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // OPENTHREAD_PING_SENDER_H_
diff --git a/include/openthread/platform/infra_if.h b/include/openthread/platform/infra_if.h
new file mode 100644
index 0000000..d052eb2
--- /dev/null
+++ b/include/openthread/platform/infra_if.h
@@ -0,0 +1,120 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ * @brief
+ *   This file includes the platform abstraction for the infrastructure network interface.
+ *
+ */
+
+#ifndef OPENTHREAD_PLATFORM_INFRA_IF_H_
+#define OPENTHREAD_PLATFORM_INFRA_IF_H_
+
+#include <stdint.h>
+
+#include <openthread/error.h>
+#include <openthread/instance.h>
+#include <openthread/ip6.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * This method sends an ICMPv6 Neighbor Discovery message on given infrastructure interface.
+ *
+ * See RFC 4861: https://tools.ietf.org/html/rfc4861.
+ *
+ * @param[in]  aInfraIfIndex  The index of the infrastructure interface this message is sent to.
+ * @param[in]  aDestAddress   The destination address this message is sent to.
+ * @param[in]  aBuffer        The ICMPv6 message buffer.
+ * @param[in]  aBufferLength  The length of the message buffer.
+ *
+ * @note  Per RFC 4861, the implementation should send the message with IPv6 link-local source address
+ *        of interface @p aInfraIfIndex and IP Hop Limit 255.
+ *
+ * @retval OT_ERROR_NONE    Successfully sent the ICMPv6 message.
+ * @retval OT_ERROR_FAILED  Failed to send the ICMPv6 message.
+ *
+ */
+otError otPlatInfraIfSendIcmp6Nd(uint32_t            aInfraIfIndex,
+                                 const otIp6Address *aDestAddress,
+                                 const uint8_t *     aBuffer,
+                                 uint16_t            aBufferLength);
+
+/**
+ * The infra interface driver calls this method to notify OpenThread
+ * that an ICMPv6 Neighbor Discovery message is received.
+ *
+ * See RFC 4861: https://tools.ietf.org/html/rfc4861.
+ *
+ * @param[in]  aInstance      The OpenThread instance structure.
+ * @param[in]  aInfraIfIndex  The index of the infrastructure interface on which the ICMPv6 message is received.
+ * @param[in]  aSrcAddress    The source address this message is received from.
+ * @param[in]  aBuffer        The ICMPv6 message buffer.
+ * @param[in]  aBufferLength  The length of the ICMPv6 message buffer.
+ *
+ * @note  Per RFC 4861, the caller should enforce that the source address MUST be a IPv6 link-local
+ *        address and the IP Hop Limit MUST be 255.
+ *
+ */
+extern void otPlatInfraIfRecvIcmp6Nd(otInstance *        aInstance,
+                                     uint32_t            aInfraIfIndex,
+                                     const otIp6Address *aSrcAddress,
+                                     const uint8_t *     aBuffer,
+                                     uint16_t            aBufferLength);
+
+/**
+ * The infra interface driver calls this method to notify OpenThread
+ * of the interface state changes.
+ *
+ * @param[in]  aInstance          The OpenThread instance structure.
+ * @param[in]  aInfraIfIndex      The index of the infrastructure interface.
+ * @param[in]  aIsRunning         A boolean that indicates whether the infrastructure
+ *                                interface is running.
+ * @param[in]  aLinkLocalAddress  A pointer to the IPv6 link-local address of the infrastructure
+ *                                interface. NULL if the IPv6 link-local address is lost.
+ *
+ * @retval  OT_ERROR_NONE           Successfully updated the infra interface status.
+ * @retval  OT_ERROR_INVALID_STATE  The Routing Manager is not initialized.
+ * @retval  OT_ERROR_INVALID_ARGS   The @p aInfraIfIndex doesn't match the infra interface the
+ *                                  Routing Manager are initialized with, or the @p aLinkLocalAddress
+ *                                  is not a valid IPv6 link-local address.
+ *
+ */
+extern otError otPlatInfraIfStateChanged(otInstance *        aInstance,
+                                         uint32_t            aInfraIfIndex,
+                                         bool                aIsRunning,
+                                         const otIp6Address *aLinkLocalAddress);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // OPENTHREAD_PLATFORM_INFRA_IF_H_
diff --git a/include/openthread/platform/logging.h b/include/openthread/platform/logging.h
index 2867660..bfa1eee 100644
--- a/include/openthread/platform/logging.h
+++ b/include/openthread/platform/logging.h
@@ -137,6 +137,9 @@
     OT_LOG_REGION_BBR      = 17, ///< Backbone Router (available since Thread 1.2)
     OT_LOG_REGION_MLR      = 18, ///< Multicast Listener Registration (available since Thread 1.2)
     OT_LOG_REGION_DUA      = 19, ///< Domain Unicast Address (available since Thread 1.2)
+    OT_LOG_REGION_BR       = 20, ///< Border Router
+    OT_LOG_REGION_SRP      = 21, ///< Service Registration Protocol (SRP)
+    OT_LOG_REGION_DNS      = 22, ///< DNS
 } otLogRegion;
 
 /**
diff --git a/include/openthread/platform/memory.h b/include/openthread/platform/memory.h
index b1de90a..ba569ea 100644
--- a/include/openthread/platform/memory.h
+++ b/include/openthread/platform/memory.h
@@ -61,7 +61,7 @@
  *   memory each and returns a pointer to the allocated memory. The allocated memory is filled with bytes
  *   of value zero."
  *
- * This function is available and can ONLY be used only when support for multiple OpenThread instances is enabled.
+ * This function is required for OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE.
  *
  * @param[in] aNum   The number of blocks to allocate
  * @param[in] aSize  The size of each block to allocate
@@ -74,7 +74,7 @@
 /**
  * Frees memory that was dynamically allocated.
  *
- * This function is available and can ONLY be used only when support for multiple OpenThread instances is enabled.
+ * This function is required for OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE.
  *
  * @param[in] aPtr  A pointer the memory blocks to free. The pointer may be NULL.
  */
diff --git a/include/openthread/platform/messagepool.h b/include/openthread/platform/messagepool.h
index 3391c4a..6f369c7 100644
--- a/include/openthread/platform/messagepool.h
+++ b/include/openthread/platform/messagepool.h
@@ -54,11 +54,22 @@
 #endif
 
 /**
+ * This struct represents an OpenThread message buffer.
+ *
+ */
+typedef struct otMessageBuffer
+{
+    struct otMessageBuffer *mNext; ///< Pointer to the next buffer.
+} otMessageBuffer;
+
+/**
  * Initialize the platform implemented message pool.
  *
+ * This function is used when `OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT` is enabled.
+ *
  * @param[in] aInstance            A pointer to the OpenThread instance.
  * @param[in] aMinNumFreeBuffers   An uint16 containing the minimum number of free buffers desired by OpenThread.
- * @param[in] aBufferSize          The size in bytes of a Buffer object.
+ * @param[in] aBufferSize          The size in bytes of a buffer object.
  *
  */
 void otPlatMessagePoolInit(otInstance *aInstance, uint16_t aMinNumFreeBuffers, size_t aBufferSize);
@@ -66,25 +77,33 @@
 /**
  * Allocate a buffer from the platform managed buffer pool.
  *
+ * This function is used when `OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT` is enabled.
+ *
+ * The returned buffer instance MUST have at least `aBufferSize` bytes (as specified in `otPlatMessagePoolInit()`).
+ *
  * @param[in] aInstance            A pointer to the OpenThread instance.
  *
- * @returns A pointer to the Buffer or NULL if no Buffers are available.
+ * @returns A pointer to the buffer or NULL if no buffers are available.
  *
  */
-otMessage *otPlatMessagePoolNew(otInstance *aInstance);
+otMessageBuffer *otPlatMessagePoolNew(otInstance *aInstance);
 
 /**
- * This function is used to free a Buffer back to the platform managed buffer pool.
+ * This function is used to free a buffer back to the platform managed buffer pool.
+ *
+ * This function is used when `OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT` is enabled.
  *
  * @param[in]  aInstance  A pointer to the OpenThread instance.
- * @param[in]  aBuffer    The Buffer to free.
+ * @param[in]  aBuffer    The buffer to free.
  *
  */
-void otPlatMessagePoolFree(otInstance *aInstance, otMessage *aBuffer);
+void otPlatMessagePoolFree(otInstance *aInstance, otMessageBuffer *aBuffer);
 
 /**
  * Get the number of free buffers.
  *
+ * This function is used when `OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT` is enabled.
+ *
  * @param[in]  aInstance  A pointer to the OpenThread instance.
  *
  * @returns The number of buffers currently free and available to OpenThread.
diff --git a/include/openthread/platform/radio.h b/include/openthread/platform/radio.h
index c245bc7..325394f 100644
--- a/include/openthread/platform/radio.h
+++ b/include/openthread/platform/radio.h
@@ -985,6 +985,37 @@
 otError otPlatRadioSetChannelMaxTransmitPower(otInstance *aInstance, uint8_t aChannel, int8_t aMaxPower);
 
 /**
+ * Set the region code.
+ *
+ * The radio region format is the 2-bytes ascii representation of the
+ * ISO 3166 alpha-2 code.
+ *
+ * @param[in]  aInstance    The OpenThread instance structure.
+ * @param[in]  aRegionCode  The radio region.
+ *
+ * @retval  OT_ERROR_FAILED           Other platform specific errors.
+ * @retval  OT_ERROR_NONE             Successfully set region code.
+ *
+ */
+otError otPlatRadioSetRegion(otInstance *aInstance, uint16_t aRegionCode);
+
+/**
+ * Get the region code.
+ *
+ * The radio region format is the 2-bytes ascii representation of the
+ * ISO 3166 alpha-2 code.
+
+ * @param[in]  aInstance    The OpenThread instance structure.
+ * @param[out] aRegionCode  The radio region.
+ *
+ * @retval  OT_ERROR_INVALID_ARGS     @p aRegionCode is nullptr.
+ * @retval  OT_ERROR_FAILED           Other platform specific errors.
+ * @retval  OT_ERROR_NONE             Successfully got region code.
+ *
+ */
+otError otPlatRadioGetRegion(otInstance *aInstance, uint16_t *aRegionCode);
+
+/**
  * Enable/disable or update Enhanced-ACK Based Probing in radio for a specific Initiator.
  *
  * After Enhanced-ACK Based Probing is configured by a specific Probing Initiator, the Enhanced-ACK sent to that
diff --git a/include/openthread/platform/settings.h b/include/openthread/platform/settings.h
index 4a4208a..cb51993 100644
--- a/include/openthread/platform/settings.h
+++ b/include/openthread/platform/settings.h
@@ -52,6 +52,28 @@
  */
 
 /**
+ * This enumeration defines the keys of settings.
+ *
+ * Note: When adding a new setings key, if the settings corresponding to the key contains security sensitive
+ *       information, the developer MUST add the key to the array `kCriticalKeys`.
+ *
+ */
+enum
+{
+    OT_SETTINGS_KEY_ACTIVE_DATASET       = 0x0001, ///< Active Operational Dataset.
+    OT_SETTINGS_KEY_PENDING_DATASET      = 0x0002, ///< Pending Operational Dataset.
+    OT_SETTINGS_KEY_NETWORK_INFO         = 0x0003, ///< Thread network information.
+    OT_SETTINGS_KEY_PARENT_INFO          = 0x0004, ///< Parent information.
+    OT_SETTINGS_KEY_CHILD_INFO           = 0x0005, ///< Child information.
+    OT_SETTINGS_KEY_RESERVED             = 0x0006, ///< Reserved (previously auto-start).
+    OT_SETTINGS_KEY_SLAAC_IID_SECRET_KEY = 0x0007, ///< SLAAC key to generate semantically opaque IID.
+    OT_SETTINGS_KEY_DAD_INFO             = 0x0008, ///< Duplicate Address Detection (DAD) information.
+    OT_SETTINGS_KEY_OMR_PREFIX           = 0x0009, ///< Off-mesh routable (OMR) prefix.
+    OT_SETTINGS_KEY_ON_LINK_PREFIX       = 0x000a, ///< On-link prefix for infrastructure link.
+    OT_SETTINGS_KEY_SRP_ECDSA_KEY        = 0x000b, ///< SRP client ECDSA public/private key pair.
+};
+
+/**
  * Performs any initialization for the settings subsystem, if necessary.
  *
  * @param[in]  aInstance The OpenThread instance structure.
@@ -67,6 +89,18 @@
  */
 void otPlatSettingsDeinit(otInstance *aInstance);
 
+/**
+ * This function sets the critical keys that should be stored in the secure area.
+ *
+ * Note that the memory pointed by @p aKeys MUST not be released before @p aInstance is destroyed.
+ *
+ * @param[in]  aInstance    The OpenThread instance structure.
+ * @param[in]  aKeys        A pointer to an array containing the list of critical keys.
+ * @param[in]  aKeysLength  The number of entries in the @p aKeys array.
+ *
+ */
+void otPlatSettingsSetCriticalKeys(otInstance *aInstance, const uint16_t *aKeys, uint16_t aKeysLength);
+
 /// Fetches the value of a setting
 /** This function fetches the value of the setting identified
  *  by aKey and write it to the memory pointed to by aValue.
diff --git a/include/openthread/platform/toolchain.h b/include/openthread/platform/toolchain.h
index c1f3a00..5610985 100644
--- a/include/openthread/platform/toolchain.h
+++ b/include/openthread/platform/toolchain.h
@@ -258,6 +258,25 @@
 #endif
 
 /**
+ * @def OT_FALL_THROUGH
+ *
+ * Suppress fall through warning in specific compiler.
+ *
+ */
+#if defined(__cplusplus) && (__cplusplus >= 201703L)
+#define OT_FALL_THROUGH [[fallthrough]]
+#elif defined(__clang__)
+#define OT_FALL_THROUGH [[clang::fallthrough]]
+#elif defined(__GNUC__) && (__GNUC__ >= 7)
+#define OT_FALL_THROUGH __attribute__((fallthrough))
+#else
+#define OT_FALL_THROUGH \
+    do                  \
+    {                   \
+    } while (false) /* fallthrough */
+#endif
+
+/**
  * @}
  *
  */
diff --git a/include/openthread/srp_client.h b/include/openthread/srp_client.h
new file mode 100644
index 0000000..2f7b9df
--- /dev/null
+++ b/include/openthread/srp_client.h
@@ -0,0 +1,573 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ * @brief
+ *  This file defines the OpenThread SRP (Service Registration Protocol) client APIs.
+ */
+
+#ifndef OPENTHREAD_SRP_CLIENT_H_
+#define OPENTHREAD_SRP_CLIENT_H_
+
+#include <openthread/dns.h>
+#include <openthread/ip6.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @addtogroup api-srp
+ *
+ * @brief
+ *   This module includes functions that control SRP client behavior.
+ *
+ * @{
+ *
+ */
+
+/**
+ * This enumeration specifies an SRP client item (service or host info) state.
+ *
+ */
+typedef enum
+{
+    OT_SRP_CLIENT_ITEM_STATE_TO_ADD,     ///< Item to be added/registered.
+    OT_SRP_CLIENT_ITEM_STATE_ADDING,     ///< Item is being added/registered.
+    OT_SRP_CLIENT_ITEM_STATE_TO_REFRESH, ///< Item to be refreshed (re-register to renew lease).
+    OT_SRP_CLIENT_ITEM_STATE_REFRESHING, ///< Item is being refreshed.
+    OT_SRP_CLIENT_ITEM_STATE_TO_REMOVE,  ///< Item to be removed.
+    OT_SRP_CLIENT_ITEM_STATE_REMOVING,   ///< Item is being removed.
+    OT_SRP_CLIENT_ITEM_STATE_REGISTERED, ///< Item is registered with server.
+    OT_SRP_CLIENT_ITEM_STATE_REMOVED,    ///< Item is removed.
+} otSrpClientItemState;
+
+/**
+ * This structure represents an SRP client host info.
+ *
+ */
+typedef struct otSrpClientHostInfo
+{
+    const char *         mName;         ///< Host name (label) string (NULL if not yet set).
+    const otIp6Address * mAddresses;    ///< Pointer to an array of host IPv6 addresses (NULL if not yet set).
+    uint8_t              mNumAddresses; ///< Number of IPv6 addresses in `mAddresses` array.
+    otSrpClientItemState mState;        ///< Host info state.
+} otSrpClientHostInfo;
+
+/**
+ * This structure represents an SRP client service.
+ *
+ * The values in this structure, including the string buffers for the names and the TXT record entries, MUST persist
+ * and stay constant after an instance of this structure is passed to OpenThread from `otSrpClientAddService()` or
+ * `otSrpClientRemoveService()`.
+ *
+ */
+typedef struct otSrpClientService
+{
+    const char *         mName;          ///< The service name labels (e.g., "_chip._udp", not the full domain name).
+    const char *         mInstanceName;  ///< The service instance name label (not the full name).
+    const otDnsTxtEntry *mTxtEntries;    ///< Array of TXT entries (number of entries is given by `mNumTxtEntries`).
+    uint16_t             mPort;          ///< The service port number.
+    uint16_t             mPriority;      ///< The service priority.
+    uint16_t             mWeight;        ///< The service weight.
+    uint8_t              mNumTxtEntries; ///< Number of entries in the `mTxtEntries` array.
+
+    /**
+     * @note The following fields are used/managed by OT core only. Their values do not matter and are ignored when an
+     * instance of `otSrpClientService` is passed in `otSrpClientAddService()` or `otSrpClientRemoveService()`. The
+     * user should not modify these fields.
+     *
+     */
+
+    otSrpClientItemState       mState; ///< Service state (managed by OT core).
+    uint32_t                   mData;  ///< Internal data (used by OT core).
+    struct otSrpClientService *mNext;  ///< Pointer to next entry in a linked-list (managed by OT core).
+} otSrpClientService;
+
+/**
+ * This function pointer type defines the callback used by SRP client to notify user of changes/events/errors.
+ *
+ * This callback is invoked on a successful registration of an update (i.e., add/remove of host-info and/or some
+ * service(s)) with the SRP server, or if there is a failure or error (e.g., server rejects a update request or client
+ * times out waiting for response, etc).
+ *
+ * In case of a successful reregistration of an update, `aError` parameter would be `OT_ERROR_NONE` and the host info
+ * and the full list of services is provided as input parameters to the callback. Note that host info and services each
+ * track its own state in the corresponding `mState` member variable of the related data structure (the state
+ * indicating whether the host-info/service is registered or removed or still being added/removed, etc).
+ *
+ * The list of removed services is passed as its own linked-list `aRemovedServices` in the callback. Note that when the
+ * callback is invoked, the SRP client (OpenThread implementation) is done with the removed service instances listed in
+ * `aRemovedServices` and no longer tracks/stores them (i.e., if from the callback we call `otSrpClientGetServices()`
+ * the removed services will not be present in the returned list). Providing a separate list of removed services in
+ * the callback helps indicate to user which items are now removed and allow user to re-claim/reuse the instances.
+ *
+ * If the server rejects an SRP update request, the DNS response code (RFC 2136) is mapped to the following errors:
+ *
+ *  - (0)  NOERROR   Success (no error condition)                    -> OT_ERROR_NONE
+ *  - (1)  FORMERR   Server unable to interpret due to format error  -> OT_ERROR_PARSE
+ *  - (2)  SERVFAIL  Server encountered an internal failure          -> OT_ERROR_FAILED
+ *  - (3)  NXDOMAIN  Name that ought to exist, does not exist        -> OT_ERROR_NOT_FOUND
+ *  - (4)  NOTIMP    Server does not support the query type (OpCode) -> OT_ERROR_NOT_IMPLEMENTED
+ *  - (5)  REFUSED   Server refused for policy/security reasons      -> OT_ERROR_SECURITY
+ *  - (6)  YXDOMAIN  Some name that ought not to exist, does exist   -> OT_ERROR_DUPLICATED
+ *  - (7)  YXRRSET   Some RRset that ought not to exist, does exist  -> OT_ERROR_DUPLICATED
+ *  - (8)  NXRRSET   Some RRset that ought to exist, does not exist  -> OT_ERROR_NOT_FOUND
+ *  - (9)  NOTAUTH   Service is not authoritative for zone           -> OT_ERROR_SECURITY
+ *  - (10) NOTZONE   A name is not in the zone                       -> OT_ERROR_PARSE
+ *  - (20) BADNAME   Bad name                                        -> OT_ERROR_PARSE
+ *  - (21) BADALG    Bad algorithm                                   -> OT_ERROR_SECURITY
+ *  - (22) BADTRUN   Bad truncation                                  -> OT_ERROR_PARSE
+ *  - Other response codes                                           -> OT_ERROR_FAILED
+ *
+ * The following errors are also possible:
+ *
+ *  - OT_ERROR_RESPONSE_TIMEOUT : Timed out waiting for response from server (client would continue to retry).
+ *  - OT_ERROR_INVALID_ARGS     : The provided service structure is invalid (e.g., bad service name or `otDnsTxtEntry`).
+ *  - OT_ERROR_NO_BUFS          : Insufficient buffer to prepare or send the update message.
+ *
+ * Note that in case of any failure, the client continues the operation, i.e. it prepares and (re)transmits the SRP
+ * update message to the server, after some wait interval. The retry wait interval starts from the minimum value and
+ * is increased by the growth factor every failure up to the max value (please see configuration parameter
+ * `OPENTHREAD_CONFIG_SRP_CLIENT_MIN_RETRY_WAIT_INTERVAL` and the related ones for more details).
+ *
+ * @param[in] aError            The error (see above).
+ * @param[in] aHostInfo         A pointer to host info.
+ * @param[in] aService          The head of linked-list containing all services (excluding the ones removed). NULL if
+ *                              the list is empty.
+ * @param[in] aRemovedServices  The head of linked-list containing all removed services. NULL if the list is empty.
+ * @param[in] aContext          A pointer to an arbitrary context (provided when callback was registered).
+ *
+ */
+typedef void (*otSrpClientCallback)(otError                    aError,
+                                    const otSrpClientHostInfo *aHostInfo,
+                                    const otSrpClientService * aServices,
+                                    const otSrpClientService * aRemovedServices,
+                                    void *                     aContext);
+
+/**
+ * This function pointer type defines the callback used by SRP client to notify user when it is auto-started or stopped.
+ *
+ * This is only used when auto-start feature `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE` is enabled.
+ *
+ * This callback is invoked when auto-start mode is enabled and the SRP client is either automatically started or
+ * stopped.
+ *
+ * @param[in] aSeverSockAddress    A non-NULL pointer indicates SRP sever was started and pointer will give the
+ *                                 selected server socket address. A NULL pointer indicates SRP sever was stopped.
+ * @param[in] aContext             A pointer to an arbitrary context (provided when callback was registered).
+ *
+ */
+typedef void (*otSrpClientAutoStartCallback)(const otSockAddr *aServerSockAddr, void *aContext);
+
+/**
+ * This function starts the SRP client operation.
+ *
+ * SRP client will prepare and send "SRP Update" message to the SRP server once all the following conditions are met:
+ *
+ *  - The SRP client is started - `otSrpClientStart()` is called.
+ *  - Host name is set - `otSrpClientSetHostName()` is called.
+ *  - At least one host IPv6 address is set - `otSrpClientSetHostName()` is called.
+ *  - At least one service is added - `otSrpClientAddService()` is called.
+ *
+ * It does not matter in which order these functions are called. When all conditions are met, the SRP client will
+ * wait for a short delay before preparing an "SRP Update" message and sending it to server. This delay allows for user
+ * to add multiple services and/or IPv6 addresses before the first SRP Update message is sent (ensuring a single SRP
+ * Update is sent containing all the info). The config `OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_DELAY` specifies the
+ * delay interval.
+ *
+ * @param[in] aInstance        A pointer to the OpenThread instance.
+ * @param[in] aServerSockAddr  The socket address (IPv6 address and port number) of the SRP server.
+ *
+ * @retval OT_ERROR_NONE       SRP client operation started successfully or it is already running with same server
+ *                             socket address and callback.
+ * @retval OT_ERROR_BUSY       SRP client is busy running with a different socket address.
+ * @retval OT_ERROR_FAILED     Failed to open/connect the client's UDP socket.
+ *
+ */
+otError otSrpClientStart(otInstance *aInstance, const otSockAddr *aServerSockAddr);
+
+/**
+ * This function stops the SRP client operation.
+ *
+ * This function stops any further interactions with the SRP server. Note that it does not remove or clear host info
+ * and/or list of services. It marks all services to be added/removed again once the client is (re)started.
+ *
+ * @param[in] aInstance       A pointer to the OpenThread instance.
+ *
+ */
+void otSrpClientStop(otInstance *aInstance);
+
+/**
+ * This function indicates whether the SRP client is running or not.
+ *
+ * @param[in] aInstance       A pointer to the OpenThread instance.
+ *
+ * @returns TRUE if the SRP client is running, FALSE otherwise.
+ *
+ */
+bool otSrpClientIsRunning(otInstance *aInstance);
+
+/**
+ * This function gets the socket address (IPv6 address and port number) of the SRP server which is being used by SRP
+ * client.
+ *
+ * If the client is not running, the address is unspecified (all zero) with zero port number.
+ *
+ * @param[in] aInstance       A pointer to the OpenThread instance.
+ *
+ * @returns A pointer to the SRP server's socket address (is always non-NULL).
+ *
+ */
+const otSockAddr *otSrpClientGetServerAddress(otInstance *aInstance);
+
+/**
+ * This function sets the callback to notify caller of events/changes from SRP client.
+ *
+ * The SRP client allows a single callback to be registered. So consecutive calls to this function will overwrite any
+ * previously set callback functions.
+ *
+ * @param[in] aInstance   A pointer to the OpenThread instance.
+ * @param[in] aCallback   The callback to notify of events and changes. Can be NULL if not needed.
+ * @param[in] aContext    An arbitrary context used with @p aCallback.
+ *
+ */
+void otSrpClientSetCallback(otInstance *aInstance, otSrpClientCallback aCallback, void *aContext);
+
+/**
+ * This function enables the auto-start mode.
+ *
+ * This is only available when auto-start feature `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE` is enabled.
+ *
+ * Config option `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE` specifies the default auto-start mode (whether
+ * it is enabled or disabled at the start of OT stack).
+ *
+ * When auto-start is enabled, the SRP client will monitor the Thread Network Data for SRP Server Service entries
+ * and automatically start and stop the client when an SRP server is detected.
+ *
+ * If multiple SRP servers are found, a random one will be selected. If the selected SRP server is no longer
+ * detected (not longer present in the Thread Network Data), the SRP client will be stopped and then it may switch
+ * to another SRP server (if available).
+ *
+ * When the SRP client is explicitly started through a successful call to `otSrpClientStart()`, the given SRP server
+ * address in `otSrpClientStart()` will continue to be used regardless of the state of auto-start mode and whether the
+ * same SRP server address is discovered or not in the Thread Network Data. In this case, only an explicit
+ * `otSrpClientStop()` call will stop the client.
+ *
+ * @param[in] aInstance   A pointer to the OpenThread instance.
+ * @param[in] aCallback   A callback to notify when client is auto-started/stopped. Can be NULL if not needed.
+ * @param[in] aContext    A context to be passed when invoking @p aCallback.
+ *
+ */
+void otSrpClientEnableAutoStartMode(otInstance *aInstance, otSrpClientAutoStartCallback aCallback, void *aContext);
+
+/**
+ * This function disables the auto-start mode.
+ *
+ * This is only available when auto-start feature `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE` is enabled.
+ *
+ * Disabling the auto-start mode will not stop the client if it is already running but the client stops monitoring
+ * the Thread Network Data to verify that the selected SRP server is still present in it.
+ *
+ * Note that a call to `otSrpClientStop()` will also disable the auto-start mode.
+ *
+ * @param[in] aInstance   A pointer to the OpenThread instance.
+ *
+ */
+void otSrpClientDisableAutoStartMode(otInstance *aInstance);
+
+/**
+ * This function indicates the current state of auto-start mode (enabled or disabled).
+ *
+ * This is only available when auto-start feature `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE` is enabled.
+ *
+ * @param[in] aInstance   A pointer to the OpenThread instance.
+ *
+ * @returns TRUE if the auto-start mode is enabled, FALSE otherwise.
+ *
+ */
+bool otSrpClientIsAutoStartModeEnabled(otInstance *aInstance);
+
+/**
+ * This function gets the lease interval used in SRP update requests.
+ *
+ * Note that this is the lease duration requested by the SRP client. The server may choose to accept a different lease
+ * interval.
+ *
+ * @param[in] aInstance        A pointer to the OpenThread instance.
+ *
+ * @returns The lease interval (in seconds).
+ *
+ */
+uint32_t otSrpClientGetLeaseInterval(otInstance *aInstance);
+
+/**
+ * This function sets the lease interval used in SRP update requests.
+ *
+ * Changing the lease interval does not impact the accepted lease interval of already registered services/host-info.
+ * It only affects any future SRP update messages (i.e., adding new services and/or refreshes of the existing services).
+ *
+ * @param[in] aInstance   A pointer to the OpenThread instance.
+ * @param[in] aInterval   The lease interval (in seconds). If zero, the default value specified by
+ *                        `OPENTHREAD_CONFIG_SRP_CLIENT_DEFAULT_LEASE` would be used.
+ *
+ */
+void otSrpClientSetLeaseInterval(otInstance *aInstance, uint32_t aInterval);
+
+/**
+ * This function gets the key lease interval used in SRP update requests.
+ *
+ * Note that this is the lease duration requested by the SRP client. The server may choose to accept a different lease
+ * interval.
+ *
+ * @param[in] aInstance        A pointer to the OpenThread instance.
+ *
+ * @returns The key lease interval (in seconds).
+ *
+ */
+uint32_t otSrpClientGetKeyLeaseInterval(otInstance *aInstance);
+
+/**
+ * This function sets the key lease interval used in SRP update requests.
+ *
+ * Changing the lease interval does not impact the accepted lease interval of already registered services/host-info.
+ * It only affects any future SRP update messages (i.e., adding new services and/or refreshes of existing services).
+ *
+ * @param[in] aInstance    A pointer to the OpenThread instance.
+ * @param[in] aInterval    The key lease interval (in seconds). If zero, the default value specified by
+ *                         `OPENTHREAD_CONFIG_SRP_CLIENT_DEFAULT_KEY_LEASE` would be used.
+ *
+ */
+void otSrpClientSetKeyLeaseInterval(otInstance *aInstance, uint32_t aInterval);
+
+/**
+ * This function gets the host info.
+ *
+ * @param[in] aInstance        A pointer to the OpenThread instance.
+ *
+ * @returns A pointer to host info structure.
+ *
+ */
+const otSrpClientHostInfo *otSrpClientGetHostInfo(otInstance *aInstance);
+
+/**
+ * This function sets the host name label.
+ *
+ * After a successful call to this function, `otSrpClientCallback` will be called to report the status of host info
+ * registration with SRP server.
+ *
+ * The name string buffer pointed to by @p aName MUST persist and stay unchanged after returning from this function.
+ * OpenThread will keep the pointer to the string.
+ *
+ * The host name can be set before client is started or after start but before host info is registered with server
+ * (host info should be in either `STATE_TO_ADD` or `STATE_REMOVED`).
+ *
+ * @param[in] aInstance   A pointer to the OpenThread instance.
+ * @param[in] aName       A pointer to host name label string (MUST NOT be NULL). Pointer to the string buffer MUST
+ *                        persist and remain valid and constant after return from this function.
+ *
+ * @retval OT_ERROR_NONE            The host name label was set successfully.
+ * @retval OT_ERROR_INVALID_ARGS    The @p aName is NULL.
+ * @retval OT_ERROR_INVALID_STATE   The host name is already set and registered with the server.
+ *
+ */
+otError otSrpClientSetHostName(otInstance *aInstance, const char *aName);
+
+/**
+ * This function sets/updates the list of host IPv6 address.
+ *
+ * Host IPv6 addresses can be set/changed before start or during operation of SRP client (e.g. to add/remove or change
+ * a previously registered host address), except when the host info is being removed (client is busy handling a remove
+ * request from an earlier call to `otSrpClientRemoveHostAndServices()` and host info still being in  either
+ * `STATE_TO_REMOVE` or `STATE_REMOVING` states).
+ *
+ * The host IPv6 address array pointed to by @p aAddresses MUST persist and remain unchanged after returning from this
+ * function (with `OT_ERROR_NONE`). OpenThread will save the pointer to the array.
+ *
+ * After a successful call to this function, `otSrpClientCallback` will be called to report the status of the address
+ * registration with SRP server.
+ *
+ * @param[in] aInstance           A pointer to the OpenThread instance.
+ * @param[in] aAddresses          A pointer to the an array containing the host IPv6 addresses.
+ * @param[in] aNumAddresses       The number of addresses in the @p aAddresses array.
+ *
+ * @retval OT_ERROR_NONE            The host IPv6 address list change started successfully. The `otSrpClientCallback`
+ *                                  will be called to report the status of registering addresses with server.
+ * @retval OT_ERROR_INVALID_ARGS    The address list is invalid (e.g., must contain at least one address).
+ * @retval OT_ERROR_INVALID_STATE   Host is being removed and therefore cannot change host address.
+ *
+ */
+otError otSrpClientSetHostAddresses(otInstance *aInstance, const otIp6Address *aIp6Addresses, uint8_t aNumAddresses);
+
+/**
+ * This function adds a service to be registered with server.
+ *
+ * After a successful call to this function, `otSrpClientCallback` will be called to report the status of the service
+ * addition/registration with SRP server.
+ *
+ * The `otSrpClientService` instance being pointed to by @p aService MUST persist and remain unchanged after returning
+ * from this function (with `OT_ERROR_NONE`). OpenThread will save the pointer to the service instance.
+ *
+ * The `otSrpClientService` instance is not longer tracked by OpenThread and can be reclaimed only when
+ *
+ *  -  It is removed explicitly by a call to `otSrpClientRemoveService()` or removed along with other services by a
+ *     call to `otSrpClientRemoveHostAndServices() and only after the `otSrpClientCallback` is called indicating the
+ *     service was removed. Or,
+ *  -  A call to `otSrpClientClearHostAndServices()` which removes the host and all related services immediately.
+ *
+ * @param[in] aInstance        A pointer to the OpenThread instance.
+ * @param[in] aService         A pointer to a `otSrpClientService` instance to add.
+
+ * @retval OT_ERROR_NONE          The addition of service started successfully. The `otSrpClientCallback` will be
+ *                                called to report the status.
+ * @retval OT_ERROR_ALREADY       The same service is already in the list.
+ * @retval OT_ERROR_INVALID_ARGS  The service structure is invalid (e.g., bad service name or `otDnsTxtEntry`).
+ *
+ */
+otError otSrpClientAddService(otInstance *aInstance, otSrpClientService *aService);
+
+/**
+ * This function requests a service to be unregistered with server.
+ *
+ * After a successful call to this function, `otSrpClientCallback` will be called to report the status of remove
+ * request with SRP server.
+
+ * The `otSrpClientService` instance being pointed to by @p aService MUST persist and remain unchanged after returning
+ * from this function (with `OT_ERROR_NONE`). OpenThread will keep the service instance during the remove process.
+ * Only after the `otSrpClientCallback` is called indicating the service instance is removed from SRP client
+ * service list and can be be freed/reused.
+ *
+ * @param[in] aInstance        A pointer to the OpenThread instance.
+ * @param[in] aService         A pointer to a `otSrpClientService` instance to remove.
+ *
+ * @retval OT_ERROR_NONE       The removal of service started successfully. The `otSrpClientCallback` will be called to
+ *                             report the status.
+ * @retval OT_ERROR_NOT_FOUND  The service could not be found in the list.
+ *
+ */
+otError otSrpClientRemoveService(otInstance *aInstance, otSrpClientService *aService);
+
+/**
+ * This function gets the list of services being managed by client.
+ *
+ * @param[in] aInstance        A pointer to the OpenThread instance.
+ *
+ * @returns A pointer to the head of linked-list of all services or NULL if the list is empty.
+ *
+ */
+const otSrpClientService *otSrpClientGetServices(otInstance *aInstance);
+
+/**
+ * This function starts the remove process of the host info and all services.
+ *
+ * After returning from this function, `otSrpClientCallback` will be called to report the status of remove request with
+ * SRP server.
+ *
+ * If the host info is to be permanently removed from server, @p aRemoveKeyLease should be set to `true` which removes
+ * the key lease associated with host on server. Otherwise, the key lease record is kept as before, which ensures
+ * that the server holds the host name in reserve for when the client is once again able to provide and register its
+ * service(s).
+ *
+ * @param[in] aInstance        A pointer to the OpenThread instance.
+ * @param[in] aRemoveKeyLease  A boolean indicating whether or not the host key lease should also be removed.
+ *
+ * @retval OT_ERROR_NONE       The removal of host info and services started successfully. The `otSrpClientCallback`
+ *                             will be called to report the status.
+ * @retval OT_ERROR_ALREADY    The host info is already removed.
+ *
+ */
+otError otSrpClientRemoveHostAndServices(otInstance *aInstance, bool aRemoveKeyLease);
+
+/**
+ * This function clears all host info and all the services.
+ *
+ * Unlike `otSrpClientRemoveHostAndServices()` which sends an update message to server to remove/unregister all the
+ * info, this function clears all the info immediately without any interaction with server.
+ *
+ * @param[in] aInstance        A pointer to the OpenThread instance.
+ *
+ */
+void otSrpClientClearHostAndServices(otInstance *aInstance);
+
+/**
+ * This function gets the domain name being used by SRP client.
+ *
+ * This function requires `OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE` to be enabled.
+ *
+ * If domain name is not set, "default.service.arpa" will be used.
+ *
+ * @param[in] aInstance        A pointer to the OpenThread instance.
+ *
+ * @returns The domain name string.
+ *
+ */
+const char *otSrpClientGetDomainName(otInstance *aInstance);
+
+/**
+ * This function sets the domain name to be used by SRP client.
+ *
+ * This function requires `OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE` to be enabled.
+ *
+ * If not set "default.service.arpa" will be used.
+ *
+ * The name string buffer pointed to by @p aName MUST persist and stay unchanged after returning from this function.
+ * OpenThread will keep the pointer to the string.
+ *
+ * The domain name can be set before client is started or after start but before host info is registered with server
+ * (host info should be in either `STATE_TO_ADD` or `STATE_TO_REMOVE`).
+ *
+ * @param[in] aInstance     A pointer to the OpenThread instance.
+ * @param[in] aName         A pointer to the domain name string. If NULL sets it to default "default.service.arpa".
+ *
+ * @retval OT_ERROR_NONE            The domain name label was set successfully.
+ * @retval OT_ERROR_INVALID_STATE   The host info is already registered with server.
+ *
+ */
+otError otSrpClientSetDomainName(otInstance *aInstance, const char *aName);
+
+/**
+ * This function converts a `otSrpClientItemState` to a string.
+ *
+ * @param[in] aItemState  An item state.
+ *
+ * @returns A string representation of @p aItemState.
+ *
+ */
+const char *otSrpClientItemStateToString(otSrpClientItemState aItemState);
+
+/**
+ * @}
+ *
+ */
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // OPENTHREAD_SRP_CLIENT_H_
diff --git a/include/openthread/srp_server.h b/include/openthread/srp_server.h
new file mode 100644
index 0000000..0105621
--- /dev/null
+++ b/include/openthread/srp_server.h
@@ -0,0 +1,336 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ * @brief
+ *  This file defines the API for server of the Service Registration Protocol (SRP).
+ */
+
+#ifndef OPENTHREAD_SRP_SERVER_H_
+#define OPENTHREAD_SRP_SERVER_H_
+
+#include <stdint.h>
+
+#include <openthread/dns.h>
+#include <openthread/instance.h>
+#include <openthread/ip6.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @addtogroup api-srp
+ *
+ * @brief
+ *  This module includes functions of the Service Registration Protocol.
+ *
+ * @{
+ *
+ */
+
+/**
+ * This opaque type represents a SRP service host.
+ *
+ */
+typedef void otSrpServerHost;
+
+/**
+ * This opaque type represents a SRP service.
+ *
+ */
+typedef void otSrpServerService;
+
+/**
+ * This method returns the domain authorized to the SRP server.
+ *
+ * If the domain if not set by SetDomain, "default.service.arpa." will be returned.
+ * A trailing dot is always appended even if the domain is set without it.
+ *
+ * @param[in]  aInstance  A pointer to an OpenThread instance.
+ *
+ * @returns A pointer to the dot-joined domain string.
+ *
+ */
+const char *otSrpServerGetDomain(otInstance *aInstance);
+
+/**
+ * This method sets the domain on the SRP server.
+ *
+ * A trailing dot will be appended to @p aDomain if it is not already there.
+ * This method should only be called before the SRP server is enabled.
+ *
+ * @param[in]  aInstance  A pointer to an OpenThread instance.
+ * @param[in]  aDomain    The domain to be set. MUST NOT be nullptr.
+ *
+ * @retval  OT_ERROR_NONE           Successfully set the domain to @p aDomain.
+ * @retval  OT_ERROR_INVALID_STATE  The SRP server is already enabled and the Domain cannot be changed.
+ * @retval  OT_ERROR_INVALID_ARGS   The argument @p aDomain is not a valid DNS domain name.
+ * @retval  OT_ERROR_NO_BUFS        There is no memory to store content of @p aDomain.
+ *
+ */
+otError otSrpServerSetDomain(otInstance *aInstance, const char *aDomain);
+
+/**
+ * This method enables/disables the SRP server.
+ *
+ * @param[in]  aInstance  A pointer to an OpenThread instance.
+ * @param[in]  aEnabled   A boolean to enable/disable the SRP server.
+ *
+ */
+void otSrpServerSetEnabled(otInstance *aInstance, bool aEnabled);
+
+/**
+ * This method sets LEASE & KEY-LEASE range that is acceptable by the SRP server.
+ *
+ * When a non-zero LEASE time is requested from a client, the granted value will be
+ * limited in range [aMinLease, aMaxLease]; and a non-zero KEY-LEASE will be granted
+ * in range [aMinKeyLease, aMaxKeyLease]. For zero LEASE or KEY-LEASE time, zero will
+ * be granted.
+ *
+ * @param[in]  aInstance     A pointer to an OpenThread instance.
+ * @param[in]  aMinLease     The minimum LEASE interval in seconds.
+ * @param[in]  aMaxLease     The maximum LEASE interval in seconds.
+ * @param[in]  aMinKeyLease  The minimum KEY-LEASE interval in seconds.
+ * @param[in]  aMaxKeyLease  The maximum KEY-LEASE interval in seconds.
+ *
+ * @retval  OT_ERROR_NONE          Successfully set the LEASE and KEY-LEASE ranges.
+ * @retval  OT_ERROR_INVALID_ARGS  The LEASE or KEY-LEASE range is not valid.
+ *
+ */
+otError otSrpServerSetLeaseRange(otInstance *aInstance,
+                                 uint32_t    aMinLease,
+                                 uint32_t    aMaxLease,
+                                 uint32_t    aMinKeyLease,
+                                 uint32_t    aMaxKeyLease);
+
+/**
+ * This method handles SRP service updates.
+ *
+ * This function is called by the SRP server to notify that a SRP host and possibly SRP services
+ * are being updated. It is important that the SRP updates are not commited until the handler
+ * returns the result by calling otSrpServerHandleServiceUpdateResult or times out after @p aTimeout.
+ *
+ * A SRP service observer should always call otSrpServerHandleServiceUpdateResult with error code
+ * OT_ERROR_NONE immediately after receiving the update events.
+ *
+ * A more generic handler may perform validations on the SRP host/services and rejects the SRP updates
+ * if any validation fails. For example, an Advertising Proxy should advertise (or remove) the host and
+ * services on a multicast-capable link and returns specific error code if any failure occurs.
+ *
+ * @param[in]  aHost     A pointer to the otSrpServerHost object which contains the SRP updates.
+ *                       The pointer should be passed back to otSrpServerHandleServiceUpdateResult, but
+ *                       the content MUST not be accessed after this method returns. The handler
+ *                       should publish/un-publish the host and each service points to this host
+ *                       with below rules:
+ *                         1. If the host is not deleted (indicated by `otSrpServerHostIsDeleted`),
+ *                            then it should be published or updated with mDNS. Otherwise, the host
+ *                            should be un-published (remove AAAA RRs).
+ *                         2. For each service points to this host, it must be un-published if the host
+ *                            is to be un-published. Otherwise, the handler should publish or update the
+ *                            service when it is not deleted (indicated by `otSrpServerServiceIsDeleted`)
+ *                            and un-publish it when deleted.
+ * @param[in]  aTimeout  The maximum time in milliseconds for the handler to process the service event.
+ * @param[in]  aContext  A pointer to application-specific context.
+ *
+ * @sa otSrpServerSetServiceUpdateHandler
+ * @sa otSrpServerHandleServiceUpdateResult
+ *
+ */
+typedef void (*otSrpServerServiceUpdateHandler)(const otSrpServerHost *aHost, uint32_t aTimeout, void *aContext);
+
+/**
+ * This method sets the SRP service updates handler on SRP server.
+ *
+ * @param[in]  aInstance        A pointer to an OpenThread instance.
+ * @param[in]  aServiceHandler  A pointer to a service handler. Use NULL to remove the handler.
+ * @param[in]  aContext         A pointer to arbitrary context information.
+ *                              May be NULL if not used.
+ *
+ */
+void otSrpServerSetServiceUpdateHandler(otInstance *                    aInstance,
+                                        otSrpServerServiceUpdateHandler aServiceHandler,
+                                        void *                          aContext);
+
+/**
+ * This method reports the result of processing a SRP update to the SRP server.
+ *
+ * The Service Update Handler should call this function to return the result of its
+ * processing of a SRP update.
+ *
+ * @param[in]  aInstance  A pointer to an OpenThread instance.
+ * @param[in]  aHost      A pointer to the Host object which represents a SRP update.
+ * @param[in]  aError     An error to be returned to the SRP server. Use OT_ERROR_DUPLICATED
+ *                        to represent DNS name conflicts.
+ *
+ */
+void otSrpServerHandleServiceUpdateResult(otInstance *aInstance, const otSrpServerHost *aHost, otError aError);
+
+/**
+ * This method returns the next registered host on the SRP server.
+ *
+ * @param[in]  aInstance  A pointer to an OpenThread instance.
+ * @param[in]  aHost      A pointer to current host; use NULL to get the first host.
+ *
+ * @returns  A pointer to the registered host. NULL, if no more hosts can be found.
+ *
+ */
+const otSrpServerHost *otSrpServerGetNextHost(otInstance *aInstance, const otSrpServerHost *aHost);
+
+/**
+ * This method tells if the SRP service host has been deleted.
+ *
+ * A SRP service host can be deleted but retains its name for future uses.
+ * In this case, the host instance is not removed from the SRP server/registry.
+ *
+ * @param[in]  aHost  A pointer to the SRP service host.
+ *
+ * @returns  TRUE if the host has been deleted, FALSE if not.
+ *
+ */
+bool otSrpServerHostIsDeleted(const otSrpServerHost *aHost);
+
+/**
+ * This method returns the full name of the host.
+ *
+ * @param[in]  aHost  A pointer to the SRP service host.
+ *
+ * @returns  A pointer to the null-terminated host name string.
+ *
+ */
+const char *otSrpServerHostGetFullName(const otSrpServerHost *aHost);
+
+/**
+ * This method returns the addresses of given host.
+ *
+ * @param[in]   aHost          A pointer to the SRP service host.
+ * @param[out]  aAddressesNum  A pointer to where we should output the number of the addresses to.
+ *
+ * @returns  A pointer to the array of IPv6 Address.
+ *
+ */
+const otIp6Address *otSrpServerHostGetAddresses(const otSrpServerHost *aHost, uint8_t *aAddressesNum);
+
+/**
+ * This method returns the next service of given host.
+ *
+ * @param[in]  aHost     A pointer to the SRP service host.
+ * @param[in]  aService  A pointer to current SRP service instance; use NULL to get the first service.
+ *
+ * @returns  A pointer to the next service or NULL if there is no more services.
+ *
+ */
+const otSrpServerService *otSrpServerHostGetNextService(const otSrpServerHost *   aHost,
+                                                        const otSrpServerService *aService);
+
+/**
+ * This method tells if the SRP service has been deleted.
+ *
+ * A SRP service can be deleted but retains its name for future uses.
+ * In this case, the service instance is not removed from the SRP server/registry.
+ * It is guaranteed that all services are deleted if the host is deleted.
+ *
+ * @param[in]  aService  A pointer to the SRP service.
+ *
+ * @returns  TRUE if the service has been deleted, FALSE if not.
+ *
+ */
+bool otSrpServerServiceIsDeleted(const otSrpServerService *aService);
+
+/**
+ * This method returns the full name of the service.
+ *
+ * @param[in]  aService  A pointer to the SRP service.
+ *
+ * @returns  A pointer to the null-terminated service name string.
+ *
+ */
+const char *otSrpServerServiceGetFullName(const otSrpServerService *aService);
+
+/**
+ * This method returns the port of the service instance.
+ *
+ * @param[in]  aService  A pointer to the SRP service.
+ *
+ * @returns  The port of the service.
+ *
+ */
+uint16_t otSrpServerServiceGetPort(const otSrpServerService *aService);
+
+/**
+ * This method returns the weight of the service instance.
+ *
+ * @param[in]  aService  A pointer to the SRP service.
+ *
+ * @returns  The weight of the service.
+ *
+ */
+uint16_t otSrpServerServiceGetWeight(const otSrpServerService *aService);
+
+/**
+ * This method returns the priority of the service instance.
+ *
+ * @param[in]  aService  A pointer to the SRP service.
+ *
+ * @returns  The priority of the service.
+ *
+ */
+uint16_t otSrpServerServiceGetPriority(const otSrpServerService *aService);
+
+/**
+ * This function returns the TXT record data of the service instance.
+ *
+ * @param[in]  aService        A pointer to the SRP service.
+ * @param[out] aDataLength     A pointer to return the TXT record data length. MUST NOT be NULL.
+ *
+ * @returns A pointer to the buffer containing the TXT record data (the TXT data length is returned in @p aDataLength).
+ *
+ */
+const uint8_t *otSrpServerServiceGetTxtData(const otSrpServerService *aService, uint16_t *aDataLength);
+
+/**
+ * This method returns the host which the service instance reside on.
+ *
+ * @param[in]  aService  A pointer to the SRP service.
+ *
+ * @returns  A pointer to the host instance.
+ *
+ */
+const otSrpServerHost *otSrpServerServiceGetHost(const otSrpServerService *aService);
+
+/**
+ * @}
+ *
+ */
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // OPENTHREAD_SRP_SERVER_H_
diff --git a/ot_version.gni b/ot_version.gni
index 853e369..53b8058 100644
--- a/ot_version.gni
+++ b/ot_version.gni
@@ -1,5 +1,5 @@
 
 # Note: This file is added only to support soft-transition
 # it will be removed afterwards
-openthread_version = "before_update_on_20210324"
+openthread_version = "after_update_on_20210324"
 
diff --git a/script/bootstrap b/script/bootstrap
index 845c7b0..1fcd832 100755
--- a/script/bootstrap
+++ b/script/bootstrap
@@ -33,6 +33,23 @@
 
 set -euxo pipefail
 
+install_packages_pretty_format()
+{
+    echo 'Installing pretty tools useful for code contributions...'
+
+    # add clang-format and clang-tidy for pretty
+    sudo apt-get --no-install-recommends install -y clang-format-9 clang-tidy-9 || echo 'WARNING: could not install clang-format-9 and clang-tidy-9, which is useful if you plan to contribute C/C++ code to the OpenThread project.'
+
+    # add yapf for pretty
+    python3 -m pip install yapf==0.29.0 || echo 'WARNING: could not install yapf, which is useful if you plan to contribute python code to the OpenThread project.'
+
+    # add mdv for local size report
+    python3 -m pip install mdv || echo 'WARNING: could not install mdv, which is required to post markdown size report for OpenThread.'
+
+    # add shfmt for shell pretty, try brew only because snap does not support home directory not being /home and doesn't work in docker.
+    command -v shfmt || brew install shfmt || echo 'WARNING: could not install shfmt, which is useful if you plan to contribute shell scripts to the OpenThread project.'
+}
+
 install_packages_apt()
 {
     echo 'Installing toolchain dependencies...'
@@ -51,24 +68,14 @@
         sudo apt-get --no-install-recommends install -y ca-certificates wget
         (cd /tmp \
             && wget -c https://developer.arm.com/-/media/Files/downloads/gnu-rm/9-2020q2/gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 \
-            && tar xjf gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 \
+            && tar xjf gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 -C /opt \
             && rm gcc-arm-none-eabi-9-2020-q2-update-x86_64-linux.tar.bz2 \
-            && sudo ln -s /opt/gcc-arm-none-eabi-9-2020-q2-update/bin/* /usr/local/bin/.)
+            && sudo ln -s -f /opt/gcc-arm-none-eabi-9-2020-q2-update/bin/* /usr/local/bin/.)
     fi
 
-    echo 'Installing pretty tools useful for code contributions...'
-
-    # add clang-format and clang-tidy for pretty
-    sudo apt-get --no-install-recommends install -y clang-format-9 clang-tidy-9 || echo 'WARNING: could not install clang-format-9 and clang-tidy-9, which is useful if you plan to contribute C/C++ code to the OpenThread project.'
-
-    # add yapf for pretty
-    python3 -m pip install yapf==0.29.0 || echo 'WARNING: could not install yapf, which is useful if you plan to contribute python code to the OpenThread project.'
-
-    # add mdv for local size report
-    python3 -m pip install mdv || echo 'WARNING: could not install mdv, which is useful if you plan to contribute markdown to the OpenThread project.'
-
-    # add shfmt for shell pretty, try brew only because snap does not support home directory not being /home and doesn't work in docker.
-    command -v shfmt || brew install shfmt || echo 'WARNING: could not install shfmt, which is useful if you plan to contribute shell scripts to the OpenThread project.'
+    if [ "$PLATFORM" != "Raspbian" ]; then
+        install_packages_pretty_format
+    fi
 }
 
 install_packages_opkg()
diff --git a/script/check-android-build b/script/check-android-build
index 5acc038..8587fde 100755
--- a/script/check-android-build
+++ b/script/check-android-build
@@ -47,8 +47,8 @@
 
 main()
 {
-    USE_OTBR_DAEMON=1 check_targets ot-cli ot-ctl ot-ncp
-    check_targets ot-cli ot-ncp spi-hdlc-adapter
+    OPENTHREAD_ENABLE_ANDROID_MK=1 ANDROID_NDK=1 USE_OTBR_DAEMON=1 check_targets ot-cli ot-ctl
+    OPENTHREAD_ENABLE_ANDROID_MK=1 ANDROID_NDK=1 check_targets ot-cli spi-hdlc-adapter
 }
 
 main "$@"
diff --git a/script/check-arm-build-autotools b/script/check-arm-build-autotools
index dee5ce3..6c1de79 100755
--- a/script/check-arm-build-autotools
+++ b/script/check-arm-build-autotools
@@ -58,6 +58,8 @@
         "DNS_CLIENT=1"
         "JOINER=1"
         "SLAAC=1"
+        # cc2538 does not have enough resources to support Thread 1.2
+        "THREAD_VERSION=1.1"
     )
 
     reset_source
@@ -133,7 +135,6 @@
 build_nrf52811()
 {
     local options=(
-        "BORDER_ROUTER=1"
         "COAP=1"
         "DNS_CLIENT=1"
         "LINK_RAW=1"
@@ -172,9 +173,11 @@
         "LINK_RAW=1"
         "MAC_FILTER=1"
         "MTD_NETDIAG=1"
+        "PING_SENDER=1"
         "SERVICE=1"
         "SLAAC=1"
         "SNTP_CLIENT=1"
+        "SRP_CLIENT=1"
         "UDP_FORWARD=1"
     )
 
@@ -214,9 +217,11 @@
         "LINK_RAW=1"
         "MAC_FILTER=1"
         "MTD_NETDIAG=1"
+        "PING_SENDER=1"
         "SERVICE=1"
         "SLAAC=1"
         "SNTP_CLIENT=1"
+        "SRP_CLIENT=1"
         "UDP_FORWARD=1"
     )
 
diff --git a/script/check-arm-build-cmake b/script/check-arm-build-cmake
index 5eddf85..17a8b65 100755
--- a/script/check-arm-build-cmake
+++ b/script/check-arm-build-cmake
@@ -56,8 +56,13 @@
 
 build_cc2538()
 {
+    local options=(
+        # cc2538 does not have enough resources to support Thread 1.2
+        "-DOT_THREAD_VERSION=1.1"
+    )
+
     reset_source
-    "$(dirname "$0")"/cmake-build cc2538 "${OT_COMMON_OPTIONS[@]}" "${OT_BASIC_CHECK_OPTIONS[@]}"
+    "$(dirname "$0")"/cmake-build cc2538 "${OT_COMMON_OPTIONS[@]}" "${OT_BASIC_CHECK_OPTIONS[@]}" "${options[@]}"
 }
 
 build_cc2650()
@@ -78,12 +83,48 @@
     "$(dirname "$0")"/cmake-build kw41z "${OT_COMMON_OPTIONS[@]}" "${OT_BASIC_CHECK_OPTIONS[@]}"
 }
 
+build_nrf52811()
+{
+    reset_source
+    "$(dirname "$0")"/cmake-build nrf52811 "$1" "${OT_COMMON_OPTIONS[@]}"
+}
+
+build_nrf52833()
+{
+    reset_source
+    "$(dirname "$0")"/cmake-build nrf52833 "$1" "${OT_COMMON_OPTIONS[@]}" "${OT_BASIC_CHECK_OPTIONS[@]}"
+}
+
+build_nrf52840()
+{
+    reset_source
+    "$(dirname "$0")"/cmake-build nrf52840 "$1" "${OT_COMMON_OPTIONS[@]}" "${OT_BASIC_CHECK_OPTIONS[@]}"
+}
+
 build_qpg6095()
 {
     reset_source
     "$(dirname "$0")"/cmake-build qpg6095 "${OT_COMMON_OPTIONS[@]}" "${OT_BASIC_CHECK_OPTIONS[@]}"
 }
 
+build_qpg6100()
+{
+    reset_source
+    "$(dirname "$0")"/cmake-build qpg6100 "${OT_COMMON_OPTIONS[@]}" "${OT_BASIC_CHECK_OPTIONS[@]}"
+}
+
+build_gp712()
+{
+    reset_source
+    "$(dirname "$0")"/cmake-build gp712 "${OT_COMMON_OPTIONS[@]}" "${OT_BASIC_CHECK_OPTIONS[@]}"
+}
+
+build_qpg7015m()
+{
+    reset_source
+    "$(dirname "$0")"/cmake-build qpg7015m "${OT_COMMON_OPTIONS[@]}" "${OT_BASIC_CHECK_OPTIONS[@]}"
+}
+
 build_samr21()
 {
     reset_source
@@ -107,7 +148,20 @@
         build_cc2652
         build_kw41z
         build_qpg6095
+        build_qpg6100
+        build_gp712
+        build_qpg7015m
         build_samr21
+        # UART transport
+        build_nrf52840 UART_trans
+        # USB transport with bootloader e.g. to support PCA10059 dongle
+        build_nrf52840 USB_trans_bl
+        # SPI transport for NCP
+        build_nrf52840 SPI_trans_NCP
+        # Software cryptography
+        build_nrf52840 soft_crypto
+        # Software cryptography with threading support
+        build_nrf52840 soft_crypto_threading
         return 0
     fi
 
diff --git a/tests/scripts/expect/cli-anycast.exp b/script/check-core-makefiles
old mode 100644
new mode 100755
similarity index 74%
copy from tests/scripts/expect/cli-anycast.exp
copy to script/check-core-makefiles
index 3081a6b..b55066b
--- a/tests/scripts/expect/cli-anycast.exp
+++ b/script/check-core-makefiles
@@ -1,6 +1,6 @@
-#!/usr/bin/expect -f
+#!/bin/bash
 #
-#  Copyright (c) 2020, The OpenThread Authors.
+#  Copyright (c) 2021, The OpenThread Authors.
 #  All rights reserved.
 #
 #  Redistribution and use in source and binary forms, with or without
@@ -27,30 +27,25 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
-source "tests/scripts/expect/_common.exp"
+set -euo pipefail
 
-
-set spawn_id [spawn_node 1]
-
-send "panid 0xface\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-
-wait_for "state" "leader"
-expect "Done"
-
-send "ping fdde:ad00:beef:0:0:ff:fe00:fc00\n"
-expect "Done"
-expect "16 bytes from "
-expect {
-    "fdde:ad00:beef:0:0:ff:fe00:fc00" abort
-
-    -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})} {}
-
-    timeout abort
+die()
+{
+    echo >&2 "ERROR: $*"
+    exit 1
 }
 
-dispose
+main()
+{
+    ./script/update-makefiles.py || die "Could not run ./script/update-makefiles.py"
+
+    status=$(git status --porcelain)
+
+    if [ -n "${status}" ]; then
+        die "Missing files in makefiles - run ./script/update-makefiles.py"
+    else
+        echo 'PASS: Makefiles-check'
+    fi
+}
+
+main "$@"
diff --git a/script/check-posix-pty b/script/check-posix-pty
index 68b57eb..f468a1d 100755
--- a/script/check-posix-pty
+++ b/script/check-posix-pty
@@ -83,7 +83,7 @@
     # Cover setting a valid network interface name.
     readonly VALID_NETIF_NAME="wan$(date +%H%M%S)"
 
-    RADIO_URL="spinel+hdlc+uart://${CORE_PTY}?max-power-table=11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26"
+    RADIO_URL="spinel+hdlc+uart://${CORE_PTY}?region=US&max-power-table=11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26"
 
     if [[ ${DAEMON} == 1 ]]; then
         sudo "$PWD/output/posix/bin/ot-daemon" -I "${VALID_NETIF_NAME}" "${RADIO_URL}" &
@@ -113,6 +113,9 @@
 send "radiourl\r\n"
 expect "${RADIO_URL}"
 expect "Done"
+send "region\r\n"
+expect "US"
+expect "Done"
 send "panid 0xface\r\n"
 expect "Done"
 send "ifconfig up\r\n"
@@ -125,6 +128,8 @@
 expect "Done"
 send "extaddr\r\n"
 expect "Done"
+send "dataset active\r\n"
+expect "Done"
 send "ipaddr\r\n"
 expect "Done"
 send "coex\r\n"
@@ -150,7 +155,7 @@
 
     netstat -an | grep -q 5683 || die 'Application CoAP port is not available!'
 
-    extaddr=$(grep -aoE '[0-9a-z]{16}' $OT_OUTPUT)
+    extaddr=$(grep -azoP 'extaddr[\r\n]+\K[0-9a-z]{16}' $OT_OUTPUT | tr -d '\0')
     echo "Extended address is: ${extaddr}"
 
     if [[ ${DAEMON} == 1 ]]; then
@@ -162,9 +167,10 @@
             || die 'multicast group join failed'
     fi
 
-    LEADER_ALOC=fdde:ad00:beef::ff:fe00:fc00
+    prefix=$(grep -aoP 'Mesh Local Prefix: \K[0-9a-f:]+(?=::\/64)' $OT_OUTPUT | tr -d '\r\n')
+    LEADER_ALOC="$prefix::ff:fe00:fc00"
     # Retrievie test resource through application CoAP
-    coap_response=$(coap-client -B 5 -m GET coap://[${LEADER_ALOC}]:5683/TestResource)
+    coap_response=$(coap-client -B 5 -m GET "coap://[${LEADER_ALOC}]:5683/TestResource")
     echo "CoAP response is: ${coap_response}"
 
     # Verify CoAP response contains the test content
@@ -173,6 +179,14 @@
     else
         die 'Failed to access application CoAP'
     fi
+
+    # Retrievie extended address through network diagnostic get
+    coap_response=$(echo -n '120100' | xxd -r -p | coap-client -B 5 -m POST "coap://[${LEADER_ALOC}]:61631/d/dg" -f-)
+
+    # Verify Tmf CoAP is blocked
+    if [[ -z ${coap_response} ]]; then
+        die 'Tmf is not blocked'
+    fi
 }
 
 main()
diff --git a/script/check-scan-build b/script/check-scan-build
index 3c38f1e..c30889a 100755
--- a/script/check-scan-build
+++ b/script/check-scan-build
@@ -68,11 +68,14 @@
         "-DOPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_ENABLE=1"
         "-DOPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE=1"
         "-DOPENTHREAD_CONFIG_MPL_DYNAMIC_INTERVAL_ENABLE"
+        "-DOPENTHREAD_CONFIG_PING_SENDER_ENABLE=1"
         "-DOPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE=1"
         "-DOPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE=1"
         "-DOPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE=1"
         "-DOPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE=1"
         "-DOPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE=1"
+        "-DOPENTHREAD_CONFIG_SRP_CLIENT_ENABLE=1"
+        "-DOPENTHREAD_CONFIG_SRP_SERVER_ENABLE=1"
         "-DOPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE=1"
         "-DOPENTHREAD_CONFIG_TMF_NETWORK_DIAG_MTD_ENABLE=1"
         "-DOPENTHREAD_CONFIG_UDP_FORWARD_ENABLE=1"
@@ -100,7 +103,7 @@
     scan-build-9 ./configure "${configure_options[@]}"
     scan-build-9 --status-bugs -analyze-headers -v make -j"${OT_BUILD_JOBS}"
 
-    export CPPFLAGS="${options[*]} -DOPENTHREAD_CONFIG_NCP_UART_ENABLE=1"
+    export CPPFLAGS="${options[*]} -DOPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1"
     scan-build-9 ./configure "${configure_options[@]}"
     scan-build-9 --status-bugs -analyze-headers -v make -j"${OT_BUILD_JOBS}"
 }
diff --git a/script/check-simulation-build-autotools b/script/check-simulation-build-autotools
index 590c3c0..ec71578 100755
--- a/script/check-simulation-build-autotools
+++ b/script/check-simulation-build-autotools
@@ -73,13 +73,15 @@
         "-DOPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_ENABLE=1"
         "-DOPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE=1"
         "-DOPENTHREAD_CONFIG_MPL_DYNAMIC_INTERVAL_ENABLE"
+        "-DOPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1"
         "-DOPENTHREAD_CONFIG_NCP_SPI_ENABLE=1"
-        "-DOPENTHREAD_CONFIG_NCP_UART_ENABLE=1"
+        "-DOPENTHREAD_CONFIG_PING_SENDER_ENABLE=1"
         "-DOPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE=1"
         "-DOPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE=1"
         "-DOPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE=1"
         "-DOPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE=1"
         "-DOPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE=1"
+        "-DOPENTHREAD_CONFIG_SRP_CLIENT_ENABLE=1"
         "-DOPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE=1"
         "-DOPENTHREAD_CONFIG_TMF_NETWORK_DIAG_MTD_ENABLE=1"
         "-DOPENTHREAD_CONFIG_UDP_FORWARD_ENABLE=1"
@@ -132,6 +134,7 @@
         "-DOPENTHREAD_CONFIG_JAM_DETECTION_ENABLE=1"
         "-DOPENTHREAD_CONFIG_LEGACY_ENABLE=1"
         "-DOPENTHREAD_CONFIG_MAC_FILTER_ENABLE=1"
+        "-DOPENTHREAD_CONFIG_PING_SENDER_ENABLE=1"
         "-DOPENTHREAD_CONFIG_NCP_SPI_ENABLE=1"
         "-DOPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE=1"
         "-DOPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE=1"
@@ -153,7 +156,7 @@
         "-DOPENTHREAD_CONFIG_ANOUNCE_SENDER_ENABLE=1"
         "-DOPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE=1"
         "-DOPENTHREAD_CONFIG_TIME_SYNC_ENABLE=1"
-        "-DOPENTHREAD_CONFIG_NCP_UART_ENABLE=1"
+        "-DOPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1"
     )
     export CPPFLAGS="${options[*]}"
 
@@ -172,7 +175,7 @@
     cd ..
 
     options=(
-        "-DOPENTHREAD_CONFIG_NCP_UART_ENABLE=1"
+        "-DOPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1"
         "-DOPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE=1"
     )
     export CPPFLAGS="${options[*]}"
@@ -196,7 +199,7 @@
 {
     # TREL radio link only.
     local options=(
-        "-DOPENTHREAD_CONFIG_NCP_UART_ENABLE=1"
+        "-DOPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1"
         "-DOPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE=1"
         "-DOPENTHREAD_CONFIG_LOG_LEVEL=OT_LOG_LEVEL_DEBG"
         "-DOPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE=0"
@@ -220,7 +223,7 @@
 
     # Multi radio link - 15.4 and TREL.
     options=(
-        "-DOPENTHREAD_CONFIG_NCP_UART_ENABLE=1"
+        "-DOPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1"
         "-DOPENTHREAD_CONFIG_PLATFORM_FLASH_API_ENABLE=1"
         "-DOPENTHREAD_CONFIG_DIAG_ENABLE=1"
         "-DOPENTHREAD_CONFIG_LINK_RAW_ENABLE=1"
diff --git a/script/check-size b/script/check-size
index b4c5897..572c264 100755
--- a/script/check-size
+++ b/script/check-size
@@ -48,9 +48,15 @@
     arm-none-eabi-gcc --version
 }
 
+setup_ninja_build()
+{
+    sudo apt-get --no-install-recommends install -y ninja-build
+}
+
 setup()
 {
     setup_arm_gcc_7
+    setup_ninja_build
 }
 
 markdown_init()
@@ -98,60 +104,73 @@
     esac
 }
 
+nm_size()
+{
+    arm-none-eabi-nm --print-size --defined-only -C "$1" | cut -d' ' -f2- >nmsize_old
+    arm-none-eabi-nm --print-size --defined-only -C "$2" | cut -d' ' -f2- >nmsize_new
+    diff -Nuar nmsize_old nmsize_new || true
+}
+
 size_nrf52840_version()
 {
-    local flags=(
-        "BORDER_AGENT=1"
-        "BORDER_ROUTER=1"
-        "CHANNEL_MANAGER=1"
-        "CHANNEL_MONITOR=1"
-        "CHILD_SUPERVISION=1"
-        "COAP=1"
-        "COAPS=1"
-        "COMMISSIONER=1"
-        "DATASET_UPDATER=1"
-        "DHCP6_CLIENT=1"
-        "DHCP6_SERVER=1"
-        "DIAGNOSTIC=1"
-        "DISABLE_DOC=1"
-        "DNS_CLIENT=1"
-        "ECDSA=1"
-        "FULL_LOGS=1"
-        "JAM_DETECTION=1"
-        "JOINER=1"
-        "LINK_RAW=1"
-        "MAC_FILTER=1"
-        "MESSAGE_USE_HEAP=1"
-        "MTD_NETDIAG=1"
-        "SERVICE=1"
-        "SLAAC=1"
-        "SNTP_CLIENT=1"
-        "TIME_SYNC=1"
-        "UDP_FORWARD=1"
+    local options=(
+        "-DOT_BORDER_AGENT=ON"
+        "-DOT_BORDER_ROUTER=ON"
+        "-DOT_CHANNEL_MANAGER=ON"
+        "-DOT_CHANNEL_MONITOR=ON"
+        "-DOT_CHILD_SUPERVISION=ON"
+        "-DOT_COAP=ON"
+        "-DOT_COAPS=ON"
+        "-DOT_COMMISSIONER=ON"
+        "-DOT_DATASET_UPDATER=ON"
+        "-DOT_DHCP6_CLIENT=ON"
+        "-DOT_DHCP6_SERVER=ON"
+        "-DOT_DIAGNOSTIC=ON"
+        "-DOT_DNSSD_SERVER=ON"
+        "-DOT_DNS_CLIENT=ON"
+        "-DOT_ECDSA=ON"
+        "-DOT_FULL_LOGS=ON"
+        "-DOT_JAM_DETECTION=ON"
+        "-DOT_JOINER=ON"
+        "-DOT_LINK_RAW=ON"
+        "-DOT_MAC_FILTER=ON"
+        "-DOT_MESSAGE_USE_HEAP=ON"
+        "-DOT_MTD_NETDIAG=ON"
+        "-DOT_PING_SENDER=ON"
+        "-DOT_SERVICE=ON"
+        "-DOT_SLAAC=ON"
+        "-DOT_SNTP_CLIENT=ON"
+        "-DOT_SRP_CLIENT=ON"
+        "-DOT_SRP_SERVER=ON"
+        "-DOT_TIME_SYNC=ON"
+        "-DOT_UDP_FORWARD=ON"
     )
 
     local thread_version=$1
 
     if [[ ${thread_version} == "1.2" ]]; then
-        flags+=(
-            "THREAD_VERSION=1.2"
-            "BACKBONE_ROUTER=1"
-            "DUA=1"
-            "MLR=1"
-            "CSL_RECEIVER=1"
-            "LINK_METRICS=1"
+        options+=(
+            "-DOT_THREAD_VERSION=1.2"
+            "-DOT_BACKBONE_ROUTER=ON"
+            "-DOT_DUA=ON"
+            "-DOT_MLR=ON"
+            "-DOT_CSL_RECEIVER=ON"
+            "-DOT_LINK_METRICS=ON"
         )
     fi
 
     rm -rf "${OT_TMP_DIR}"
 
+    local build_dir="build"
+
     # new commit
     mkdir -p "${OT_TMP_DIR}/b"
-    git archive "${OT_SHA_NEW}" | tar x -C "${OT_TMP_DIR}/b"
+    script/git-tool clone https://github.com/openthread/ot-nrf528xx.git "${OT_TMP_DIR}/b"
+    rm -rf "${OT_TMP_DIR}/b/openthread/*" # replace openthread submodule with latest commit
+    git archive "${OT_SHA_NEW}" | tar x -C "${OT_TMP_DIR}/b/openthread"
 
     (cd "${OT_TMP_DIR}/b" \
-        && ./bootstrap \
-        && make -f examples/Makefile-nrf52840 "${flags[@]}")
+        && OT_CMAKE_BUILD_DIR=${build_dir} script/build nrf52840 UART_trans "${options[@]}")
 
     # old commit
     if [[ "${GITHUB_ACTIONS+x}" ]]; then
@@ -159,39 +178,57 @@
     fi
 
     mkdir -p "${OT_TMP_DIR}/a"
-    git archive "${OT_SHA_OLD}" | tar x -C "${OT_TMP_DIR}/a"
+    git clone https://github.com/openthread/ot-nrf528xx.git "${OT_TMP_DIR}/a"
+    rm -rf "${OT_TMP_DIR}/a/openthread/*" # replace openthread submodule with last commit
+    git archive "${OT_SHA_OLD}" | tar x -C "${OT_TMP_DIR}/a/openthread"
+
     (cd "${OT_TMP_DIR}/a" \
-        && ./bootstrap \
-        && make -f examples/Makefile-nrf52840 "${flags[@]}")
+        && OT_CMAKE_BUILD_DIR=${build_dir} script/build nrf52840 UART_trans "${options[@]}")
 
     # rename the generated files to be ready for size-report
     # shellcheck disable=SC2011
     (
-        cd "${OT_TMP_DIR}"/a/output/nrf52840/bin
+        cd "${OT_TMP_DIR}"/a/"${build_dir}"/bin
         ls | xargs -I{} mv {} {}_"${thread_version}"
-        cd "${OT_TMP_DIR}"/b/output/nrf52840/bin
+        cd "${OT_TMP_DIR}"/b/"${build_dir}"/bin
         ls | xargs -I{} mv {} {}_"${thread_version}"
 
-        cd "${OT_TMP_DIR}"/a/output/nrf52840/lib/
+        cd "${OT_TMP_DIR}"/a/"${build_dir}"/lib
         ls ./*.a | xargs -I{} mv {} {}_"${thread_version}"
-        cd "${OT_TMP_DIR}"/b/output/nrf52840/lib/
+        cd "${OT_TMP_DIR}"/b/"${build_dir}"/lib
         ls ./*.a | xargs -I{} mv {} {}_"${thread_version}"
     )
 
-    "${reporter}" size "${OT_TMP_DIR}"/a/output/nrf52840/bin/ot-cli-ftd_"${thread_version}" "${OT_TMP_DIR}"/b/output/nrf52840/bin/ot-cli-ftd_"${thread_version}"
-    "${reporter}" size "${OT_TMP_DIR}"/a/output/nrf52840/bin/ot-cli-mtd_"${thread_version}" "${OT_TMP_DIR}"/b/output/nrf52840/bin/ot-cli-mtd_"${thread_version}"
-    "${reporter}" size "${OT_TMP_DIR}"/a/output/nrf52840/bin/ot-ncp-ftd_"${thread_version}" "${OT_TMP_DIR}"/b/output/nrf52840/bin/ot-ncp-ftd_"${thread_version}"
-    "${reporter}" size "${OT_TMP_DIR}"/a/output/nrf52840/bin/ot-ncp-mtd_"${thread_version}" "${OT_TMP_DIR}"/b/output/nrf52840/bin/ot-ncp-mtd_"${thread_version}"
-    "${reporter}" size "${OT_TMP_DIR}"/a/output/nrf52840/bin/ot-rcp_"${thread_version}" "${OT_TMP_DIR}"/b/output/nrf52840/bin/ot-rcp_"${thread_version}"
+    local bins=(
+        "ot-cli-ftd"
+        "ot-cli-mtd"
+        "ot-ncp-ftd"
+        "ot-ncp-mtd"
+        "ot-rcp"
+    )
 
-    "${reporter}" size "${OT_TMP_DIR}"/a/output/nrf52840/lib/libopenthread-cli-ftd.a_"${thread_version}" "${OT_TMP_DIR}"/b/output/nrf52840/lib/libopenthread-cli-ftd.a_"${thread_version}"
-    "${reporter}" size "${OT_TMP_DIR}"/a/output/nrf52840/lib/libopenthread-cli-mtd.a_"${thread_version}" "${OT_TMP_DIR}"/b/output/nrf52840/lib/libopenthread-cli-mtd.a_"${thread_version}"
-    "${reporter}" size "${OT_TMP_DIR}"/a/output/nrf52840/lib/libopenthread-ftd.a_"${thread_version}" "${OT_TMP_DIR}"/b/output/nrf52840/lib/libopenthread-ftd.a_"${thread_version}"
-    "${reporter}" size "${OT_TMP_DIR}"/a/output/nrf52840/lib/libopenthread-mtd.a_"${thread_version}" "${OT_TMP_DIR}"/b/output/nrf52840/lib/libopenthread-mtd.a_"${thread_version}"
-    "${reporter}" size "${OT_TMP_DIR}"/a/output/nrf52840/lib/libopenthread-ncp-ftd.a_"${thread_version}" "${OT_TMP_DIR}"/b/output/nrf52840/lib/libopenthread-ncp-ftd.a_"${thread_version}"
-    "${reporter}" size "${OT_TMP_DIR}"/a/output/nrf52840/lib/libopenthread-ncp-mtd.a_"${thread_version}" "${OT_TMP_DIR}"/b/output/nrf52840/lib/libopenthread-ncp-mtd.a_"${thread_version}"
-    "${reporter}" size "${OT_TMP_DIR}"/a/output/nrf52840/lib/libopenthread-rcp.a_"${thread_version}" "${OT_TMP_DIR}"/b/output/nrf52840/lib/libopenthread-rcp.a_"${thread_version}"
-    "${reporter}" size "${OT_TMP_DIR}"/a/output/nrf52840/lib/libopenthread-radio.a_"${thread_version}" "${OT_TMP_DIR}"/b/output/nrf52840/lib/libopenthread-radio.a_"${thread_version}"
+    local libs=(
+        "libopenthread-cli-ftd.a"
+        "libopenthread-cli-mtd.a"
+        "libopenthread-ftd.a"
+        "libopenthread-mtd.a"
+        "libopenthread-ncp-ftd.a"
+        "libopenthread-ncp-mtd.a"
+        "libopenthread-rcp.a"
+        "libopenthread-radio.a"
+    )
+
+    for file in "${bins[@]}"; do
+        "${reporter}" size "${OT_TMP_DIR}"/a/"${build_dir}"/bin/"${file}"_"${thread_version}" "${OT_TMP_DIR}"/b/"${build_dir}"/bin/"${file}"_"${thread_version}"
+        echo nm_size "${OT_TMP_DIR}"/a/"${build_dir}"/bin/"${file}"_"${thread_version}" "${OT_TMP_DIR}"/b/"${build_dir}"/bin/"${file}"_"${thread_version}"
+        nm_size "${OT_TMP_DIR}"/a/"${build_dir}"/bin/"${file}"_"${thread_version}" "${OT_TMP_DIR}"/b/"${build_dir}"/bin/"${file}"_"${thread_version}"
+    done
+
+    for file in "${libs[@]}"; do
+        "${reporter}" size "${OT_TMP_DIR}"/a/"${build_dir}"/lib/"${file}"_"${thread_version}" "${OT_TMP_DIR}"/b/"${build_dir}"/lib/"${file}"_"${thread_version}"
+        echo nm_size "${OT_TMP_DIR}"/a/"${build_dir}"/lib/"${file}"_"${thread_version}" "${OT_TMP_DIR}"/b/"${build_dir}"/lib/"${file}"_"${thread_version}"
+        nm_size "${OT_TMP_DIR}"/a/"${build_dir}"/lib/"${file}"_"${thread_version}" "${OT_TMP_DIR}"/b/"${build_dir}"/lib/"${file}"_"${thread_version}"
+    done
 }
 
 size_nrf52840()
diff --git a/script/cmake-build b/script/cmake-build
index e8f3d94..4b5e7db 100755
--- a/script/cmake-build
+++ b/script/cmake-build
@@ -42,6 +42,21 @@
 #
 #      script/cmake-build ${platform} -D${option}=OFF
 #
+#  Need to add build type when compilling nrf528xx:
+#
+#      script/cmake-build ${platform} ${nrf_build_type} -D${option}=ON -D${option}=OFF
+#
+#  Need to add BOARD when compiling efr32:
+#
+#      script/cmake-build ${platform} -DBOARD=${EFR32 board}
+#
+#    Example:
+#      script/cmake-build efr32mg21 -DBOARD=brd4180b
+#
+#  To enable dynamic multiprotocol support on efr32:
+#
+#      script/cmake-build ${platform}  -DBOARD=${EFR32 board} -DDMP=ON
+#
 #  Compile with the specified ninja build target:
 #
 #      OT_CMAKE_NINJA_TARGET="ot-cli-ftd" script/cmake-build ${platform}
@@ -64,11 +79,13 @@
 OT_CMAKE_NINJA_TARGET=${OT_CMAKE_NINJA_TARGET:-}
 
 readonly OT_SRCDIR="$(pwd)"
-readonly OT_PLATFORMS=(cc1352 cc2538 cc2650 cc2652 kw41z qpg6095 samr21 simulation posix)
+readonly OT_PLATFORMS=(cc1352 cc2538 cc2650 cc2652 efr32mg1 efr32mg12 efr32mg13 efr32mg21 kw41z nrf52811 nrf52833 nrf52840 gp712 qpg7015m qpg6095 qpg6100 samr21 simulation posix)
+readonly OT_NRF528XX_BUILD_TYPES=(UART_trans USB_trans_bl SPI_trans_NCP soft_crypto soft_crypto_threading)
 readonly OT_POSIX_SIM_COMMON_OPTIONS=(
     "-DOT_BORDER_AGENT=ON"
     "-DOT_BORDER_ROUTER=ON"
     "-DOT_COAP=ON"
+    "-DOT_COAP_BLOCK=ON"
     "-DOT_COAP_OBSERVE=ON"
     "-DOT_COAPS=ON"
     "-DOT_COMMISSIONER=ON"
@@ -87,15 +104,67 @@
     "-DOT_LEGACY=ON"
     "-DOT_MAC_FILTER=ON"
     "-DOT_MTD_NETDIAG=ON"
+    "-DOT_PING_SENDER=ON"
     "-DOT_REFERENCE_DEVICE=ON"
     "-DOT_SERVICE=ON"
     "-DOT_SNTP_CLIENT=ON"
+    "-DOT_SRP_CLIENT=ON"
     "-DOT_COVERAGE=ON"
     "-DOT_LOG_LEVEL_DYNAMIC=ON"
     "-DOT_COMPILE_WARNING_AS_ERROR=ON"
     "-DOT_RCP_RESTORATION_MAX_COUNT=2"
 )
 
+readonly OT_nrf52811_OPTIONS=(
+    "-DOT_BORDER_ROUTER=ON"
+    "-DOT_COAP=ON"
+    "-DOT_DNS_CLIENT=ON"
+    "-DOT_LINK_RAW=ON"
+    "-DOT_MAC_FILTER=ON"
+    "-DOT_MTD_NETDIAG=ON"
+)
+
+readonly OT_nrf52833_OPTIONS=(
+    "-DOT_BORDER_AGENT=ON"
+    "-DOT_BORDER_ROUTER=ON"
+    "-DOT_COAP=ON"
+    "-DOT_COAPS=ON"
+    "-DOT_ECDSA=ON"
+    "-DOT_FULL_LOGS=ON"
+    "-DOT_IP6_FRAGM=ON"
+    "-DOT_LINK_RAW=ON"
+    "-DOT_MAC_FILTER=ON"
+    "-DOT_MTD_NETDIAG=ON"
+    "-DOT_SERVICE=ON"
+    "-DOT_SNTP_CLIENT=ON"
+    "-DOT_UDP_FORWARD=ON"
+)
+
+readonly OT_nrf52840_OPTIONS=("${OT_nrf52833_OPTIONS[@]:0}")
+
+readonly OT_efr32_OPTIONS=(
+    "-DOT_DIAGNOSTIC=ON"
+    "-DOT_EXTERNAL_HEAP=ON"
+    "-DOT_EXTERNAL_MBEDTLS=silabs-mbedtls"
+    "-DOT_BUILTIN_MBEDTLS_MANAGEMENT=OFF"
+)
+
+readonly OT_efr32mg1_OPTIONS=(
+    "${OT_efr32_OPTIONS[@]:0}"
+)
+
+readonly OT_efr32mg12_OPTIONS=(
+    "${OT_efr32_OPTIONS[@]:0}"
+)
+
+readonly OT_efr32mg13_OPTIONS=(
+    "${OT_efr32_OPTIONS[@]:0}"
+)
+
+readonly OT_efr32mg21_OPTIONS=(
+    "${OT_efr32_OPTIONS[@]:0}"
+)
+
 die()
 {
     echo " ** ERROR: Openthread CMake doesn't support platform \"$1\""
@@ -130,6 +199,16 @@
     fi
 
     local platform="$1"
+    # Check if the platform supports cmake.
+    echo "${OT_PLATFORMS[@]}" | grep -wq "${platform}" || die "${platform}"
+
+    local nrf_build_type=""
+    if [[ ${platform} == nrf528* ]]; then
+        nrf_build_type="$2"
+        echo "${OT_NRF528XX_BUILD_TYPES[@]}" | grep -wq "${nrf_build_type}" || die "${nrf_build_type}"
+        shift
+    fi
+
     shift
     local local_options=()
     local options=(
@@ -137,9 +216,6 @@
         "-DOT_SLAAC=ON"
     )
 
-    # Check if the platform supports cmake.
-    echo "${OT_PLATFORMS[@]}" | grep -wq "${platform}" || die "${platform}"
-
     case "${platform}" in
         posix)
             local_options+=(
@@ -156,9 +232,91 @@
             OT_CMAKE_NINJA_TARGET=("ot-cli-mtd" "ot-ncp-mtd")
             options+=("-DCMAKE_TOOLCHAIN_FILE=examples/platforms/${platform}/arm-none-eabi.cmake" "-DCMAKE_BUILD_TYPE=MinSizeRel")
             ;;
-        cc1352 | cc2538 | cc2652 | kw41z | qpg6095 | samr21)
+        cc1352 | cc2538 | cc2652 | kw41z | samr21)
             options+=("-DCMAKE_TOOLCHAIN_FILE=examples/platforms/${platform}/arm-none-eabi.cmake" "-DCMAKE_BUILD_TYPE=MinSizeRel")
             ;;
+        qpg6095 | qpg6100)
+            OT_CMAKE_NINJA_TARGET=("ot-cli-mtd" "ot-cli-ftd")
+            options+=("-DCMAKE_TOOLCHAIN_FILE=examples/platforms/${platform}/arm-none-eabi.cmake" "-DCMAKE_BUILD_TYPE=MinSizeRel")
+            ;;
+        qpg7015m | gp712)
+            OT_CMAKE_NINJA_TARGET=("ot-cli-ftd" "ot-rcp")
+            options+=("-DCMAKE_TOOLCHAIN_FILE=examples/platforms/${platform}/arm-linux-gnueabihf.cmake" "-DCMAKE_BUILD_TYPE=Release")
+            ;;
+        efr32*)
+            options+=("-DCMAKE_TOOLCHAIN_FILE=examples/platforms/efr32/${platform}/arm-none-eabi.cmake")
+
+            case "${platform}" in
+                efr32mg1)
+                    OT_CMAKE_NINJA_TARGET=("ot-rcp")
+                    options+=("${OT_efr32mg1_OPTIONS[@]}")
+                    ;;
+                efr32mg12)
+                    OT_CMAKE_NINJA_TARGET=("ot-rcp" "ot-cli-ftd" "ot-cli-mtd" "ot-ncp-ftd" "ot-ncp-mtd")
+                    OT_CMAKE_NINJA_TARGET+=("sleepy-demo-ftd" "sleepy-demo-mtd")
+                    options+=("${OT_efr32mg12_OPTIONS[@]}")
+                    ;;
+                efr32mg13)
+                    OT_CMAKE_NINJA_TARGET=("ot-rcp" "ot-cli-ftd" "ot-cli-mtd" "ot-ncp-ftd" "ot-ncp-mtd")
+                    OT_CMAKE_NINJA_TARGET+=("sleepy-demo-ftd" "sleepy-demo-mtd")
+                    options+=("${OT_efr32mg13_OPTIONS[@]}")
+                    ;;
+                efr32mg21)
+                    OT_CMAKE_NINJA_TARGET=("ot-rcp" "ot-cli-ftd" "ot-cli-mtd" "ot-ncp-ftd" "ot-ncp-mtd")
+                    OT_CMAKE_NINJA_TARGET+=("sleepy-demo-ftd" "sleepy-demo-mtd")
+                    options+=("${OT_efr32mg21_OPTIONS[@]}")
+                    ;;
+            esac
+            ;;
+        nrf52811)
+            local_options+=("${OT_nrf52811_OPTIONS[@]}" "-DCMAKE_TOOLCHAIN_FILE=examples/platforms/nrf528xx/${platform}/arm-none-eabi.cmake" "-DCMAKE_BUILD_TYPE=Release")
+            case "${nrf_build_type}" in
+                UART_trans)
+                    OT_CMAKE_NINJA_TARGET=("ot-cli-mtd" "ot-ncp-mtd" "ot-rcp")
+                    options+=("${local_options[@]}")
+                    ;;
+                SPI_trans_NCP)
+                    OT_CMAKE_NINJA_TARGET=("ot-ncp-mtd" "ot-rcp")
+                    options+=("${local_options[@]}" "-DOT_NCP_SPI=ON")
+                    ;;
+            esac
+            ;;
+        nrf52833)
+            local_options+=("${OT_nrf52833_OPTIONS[@]}" "-DCMAKE_TOOLCHAIN_FILE=examples/platforms/nrf528xx/${platform}/arm-none-eabi.cmake" "-DCMAKE_BUILD_TYPE=Release")
+            case "${nrf_build_type}" in
+                UART_trans)
+                    options+=("${local_options[@]}")
+                    ;;
+                USB_trans_bl)
+                    options+=("${local_options[@]}" "-DOT_USB=ON" "-DOT_BOOTLOADER=USB")
+                    ;;
+                SPI_trans_NCP)
+                    OT_CMAKE_NINJA_TARGET=("ot-ncp-ftd" "ot-ncp-mtd" "ot-rcp")
+                    options+=("${local_options[@]}" "-DOT_NCP_SPI=ON")
+                    ;;
+            esac
+            ;;
+        nrf52840)
+            local_options+=("${OT_nrf52840_OPTIONS[@]}" "-DCMAKE_TOOLCHAIN_FILE=examples/platforms/nrf528xx/${platform}/arm-none-eabi.cmake" "-DCMAKE_BUILD_TYPE=Release")
+            case "${nrf_build_type}" in
+                UART_trans)
+                    options+=("${local_options[@]}" "-DOT_EXTERNAL_MBEDTLS=nordicsemi-mbedtls")
+                    ;;
+                USB_trans_bl)
+                    options+=("${local_options[@]}" "-DOT_USB=ON" "-DOT_BOOTLOADER=USB" "-DOT_EXTERNAL_MBEDTLS=nordicsemi-mbedtls")
+                    ;;
+                SPI_trans_NCP)
+                    OT_CMAKE_NINJA_TARGET=("ot-ncp-ftd" "ot-ncp-mtd" "ot-rcp")
+                    options+=("${local_options[@]}" "-DOT_NCP_SPI=ON" "-DOT_EXTERNAL_MBEDTLS=nordicsemi-mbedtls")
+                    ;;
+                soft_crypto)
+                    options+=("${local_options[@]}")
+                    ;;
+                soft_crypto_threading)
+                    options+=("${local_options[@]}" "-DOT_MBEDTLS_THREADING=ON")
+                    ;;
+            esac
+            ;;
         *)
             options+=("-DCMAKE_TOOLCHAIN_FILE=examples/platforms/${platform}/arm-none-eabi.cmake")
             ;;
diff --git a/script/git-tool b/script/git-tool
index 4cf8ba1..4bcdae8 100755
--- a/script/git-tool
+++ b/script/git-tool
@@ -38,13 +38,16 @@
 
     project_name=$(git remote get-url origin | grep -oE '[^/:]+/[^/:]+\.git$' | cut -d. -f1)
     echo "project name: ${project_name}"
-    depends_on_pr="$(grep -E "^Depends-On: *${project_name}" | cut -d# -f2)" || {
-        echo "No dependency on ${project_name} found."
-        return 0
-    }
-    echo "pr: #${depends_on_pr}"
-    git fetch --depth 1 origin "pull/${depends_on_pr}/head"
-    git checkout FETCH_HEAD
+
+    git config user.name || git config user.name 'OpenThread Git'
+    git config user.email || git config user.email 'git@openthread'
+
+    while read -r dependency; do
+        echo "${dependency}"
+        depends_on_pr="$(echo "${dependency}" | tr -d '\r\n' | cut -d# -f2)"
+        echo "pr: #${depends_on_pr}"
+        git pull --no-edit origin "pull/${depends_on_pr}/merge"
+    done < <(grep -E "^Depends-On: *${project_name}" || true)
 }
 
 get_pr_body()
diff --git a/script/make-pretty b/script/make-pretty
index 63f131d..1f7edae 100755
--- a/script/make-pretty
+++ b/script/make-pretty
@@ -85,6 +85,7 @@
     '-DOT_CHANNEL_MONITOR=ON'
     '-DOT_CHILD_SUPERVISION=ON'
     '-DOT_COAP=ON'
+    '-DOT_COAP_BLOCK=ON'
     '-DOT_COAP_OBSERVE=ON'
     '-DOT_COAPS=ON'
     '-DOT_COMMISSIONER=ON'
@@ -105,10 +106,12 @@
     '-DOT_LINK_METRICS=ON'
     '-DOT_MAC_FILTER=ON'
     '-DOT_MTD_NETDIAG=ON'
+    '-DOT_PING_SENDER=ON'
     '-DOT_REFERENCE_DEVICE=ON'
     '-DOT_SERVICE=ON'
     '-DOT_SLAAC=ON'
     '-DOT_SNTP_CLIENT=ON'
+    '-DOT_SRP_CLIENT=ON'
     '-DOT_THREAD_VERSION=1.2'
     '-DOT_COVERAGE=ON'
     '-DOT_LOG_LEVEL_DYNAMIC=ON'
diff --git a/script/test b/script/test
index 7e20f13..af73c54 100755
--- a/script/test
+++ b/script/test
@@ -32,23 +32,45 @@
 
 set -euo pipefail
 
-readonly OT_BUILDDIR="${PWD}/build"
+readonly OT_BUILDDIR="${OT_BUILDDIR:-${PWD}/build}"
 readonly OT_SRCDIR="${PWD}"
 
 readonly COLOR_PASS='\033[0;32m'
 readonly COLOR_FAIL='\033[0;31m'
+readonly COLOR_SKIP='\033[0;33m'
 readonly COLOR_NONE='\033[0m'
 
 readonly OT_NODE_TYPE="${OT_NODE_TYPE:-cli}"
 readonly OT_NATIVE_IP="${OT_NATIVE_IP:-0}"
-readonly THREAD_VERSION="${THREAD_VERSION:-1.1}"
+readonly THREAD_VERSION="${THREAD_VERSION:-1.2}"
 readonly INTER_OP="${INTER_OP:-0}"
 readonly VERBOSE="${VERBOSE:-0}"
+readonly BORDER_ROUTING="${BORDER_ROUTING:-1}"
+readonly INTER_OP_BBR="${INTER_OP_BBR:-1}"
+
+readonly OT_COREDUMP_DIR="${PWD}/ot-core-dump"
+readonly FULL_LOGS=${FULL_LOGS:-0}
 
 build_simulation()
 {
     local version="$1"
-    local options=("-DOT_MESSAGE_USE_HEAP=ON" "-DOT_THREAD_VERSION=${version}" "-DBUILD_TESTING=ON" "-DOT_REFERENCE_DEVICE=ON")
+    local options=(
+        "-DOT_MESSAGE_USE_HEAP=ON"
+        "-DOT_THREAD_VERSION=${version}"
+        "-DBUILD_TESTING=ON"
+        "-DOT_REFERENCE_DEVICE=ON"
+        "-DOT_SRP_SERVER=ON"
+        "-DOT_SRP_CLIENT=ON"
+        "-DOT_SERVICE=ON"
+        "-DOT_ECDSA=ON"
+        "-DOT_PING_SENDER=ON"
+        "-DOT_DNSSD_SERVER=ON"
+        "-DOT_DNS_CLIENT=ON"
+    )
+
+    if [[ ${FULL_LOGS} == 1 ]]; then
+        options+=("-DOT_FULL_LOGS=ON")
+    fi
 
     if [[ ${version} == "1.2" ]]; then
         options+=("-DOT_DUA=ON")
@@ -57,11 +79,6 @@
 
     if [[ ${VIRTUAL_TIME} == 1 ]]; then
         options+=("-DOT_SIMULATION_VIRTUAL_TIME=ON")
-
-        if [[ ${OT_NODE_TYPE} == rcp* ]]; then
-            options+=("-DOT_SIMULATION_VIRTUAL_TIME_UART=ON")
-        fi
-
     fi
 
     if [[ ${version} == "1.2" ]]; then
@@ -75,12 +92,20 @@
 
     OT_CMAKE_BUILD_DIR="${OT_BUILDDIR}/openthread-simulation-${version}" "${OT_SRCDIR}"/script/cmake-build simulation "${options[@]}"
 
-    if [[ ${version} == "1.2" ]]; then
+    if [[ ${VIRTUAL_TIME} == 1 ]] && [[ ${OT_NODE_TYPE} == rcp* ]]; then
+        OT_CMAKE_NINJA_TARGET=ot-rcp OT_CMAKE_BUILD_DIR="${OT_BUILDDIR}/openthread-simulation-${version}" "${OT_SRCDIR}"/script/cmake-build simulation "${options[@]}" "-DOT_SIMULATION_VIRTUAL_TIME_UART=ON"
+    fi
+
+    if [[ ${version} == "1.2" && ${INTER_OP_BBR} == 1 ]]; then
 
         options+=("-DOT_BACKBONE_ROUTER=ON")
 
         OT_CMAKE_BUILD_DIR="${OT_BUILDDIR}/openthread-simulation-${version}-bbr" "${OT_SRCDIR}"/script/cmake-build simulation "${options[@]}"
 
+        if [[ ${VIRTUAL_TIME} == 1 ]] && [[ ${OT_NODE_TYPE} == rcp* ]]; then
+            OT_CMAKE_NINJA_TARGET=ot-rcp OT_CMAKE_BUILD_DIR="${OT_BUILDDIR}/openthread-simulation-${version}-bbr" "${OT_SRCDIR}"/script/cmake-build simulation "${options[@]}" "-DOT_SIMULATION_VIRTUAL_TIME_UART=ON"
+        fi
+
     fi
 }
 
@@ -94,6 +119,10 @@
         options+=("-DOT_MLR=ON")
     fi
 
+    if [[ ${FULL_LOGS} == 1 ]]; then
+        options+=("-DOT_FULL_LOGS=ON")
+    fi
+
     if [[ ${VIRTUAL_TIME} == 1 ]]; then
         options+=("-DOT_POSIX_VIRTUAL_TIME=ON")
     fi
@@ -108,7 +137,7 @@
 
     OT_CMAKE_BUILD_DIR="${OT_BUILDDIR}/openthread-posix-${version}" "${OT_SRCDIR}"/script/cmake-build posix "${options[@]}"
 
-    if [[ ${version} == "1.2" ]]; then
+    if [[ ${version} == "1.2" && ${INTER_OP_BBR} == 1 ]]; then
 
         options+=("-DOT_BACKBONE_ROUTER=ON")
 
@@ -162,7 +191,7 @@
 {
     do_unit_version "${THREAD_VERSION}"
 
-    if [[ ${THREAD_VERSION} == "1.2" ]]; then
+    if [[ ${THREAD_VERSION} == "1.2" && ${INTER_OP_BBR} == 1 ]]; then
         do_unit_version "1.2-bbr"
     fi
 }
@@ -188,8 +217,11 @@
         fi
     fi
 
+    export PYTHONPATH=tests/scripts/thread-cert
+
     [[ ! -d tmp ]] || rm -rvf tmp
-    PYTHONUNBUFFERED=1 "$1"
+    PYTHONUNBUFFERED=1 "$@"
+    exit 0
 }
 
 do_cert_suite()
@@ -231,7 +263,7 @@
     echo "Building OTBR Docker ..."
     local otdir
     local otbrdir
-    local otbr_options="-DOT_DUA=ON -DOT_MLR=ON -DOT_COVERAGE=ON -DOTBR_REST=OFF -DOTBR_WEB=OFF"
+    local otbr_options="-DOT_DNSSD_SERVER=ON -DOT_DNS_CLIENT=ON -DOT_SRP_CLIENT=ON -DOT_SLAAC=ON -DOT_DUA=ON -DOT_MLR=ON -DOT_COVERAGE=ON -DOTBR_REST=OFF -DOTBR_WEB=OFF -DOTBR_DUA_ROUTING=ON"
     local otbr_docker_image=${OTBR_DOCKER_IMAGE:-otbr-ot12-backbone-ci}
 
     otbrdir=$(mktemp -d -t otbr_XXXXXX)
@@ -244,6 +276,8 @@
         cp -r "${otdir}" third_party/openthread/repo
         rm -rf .git
         docker build -t "${otbr_docker_image}" -f etc/docker/Dockerfile . \
+            --build-arg BORDER_ROUTING="${BORDER_ROUTING}" \
+            --build-arg INFRA_IF_NAME=eth0 \
             --build-arg BACKBONE_ROUTER=1 \
             --build-arg REFERENCE_DEVICE=1 \
             --build-arg OT_BACKBONE_CI=1 \
@@ -259,6 +293,43 @@
     python3 ./tests/scripts/thread-cert/pktverify/verify.py "$1"
 }
 
+ot_exec_expect_script()
+{
+    local log_file="tmp/log_expect"
+    local script="$1"
+
+    echo -e "\n${COLOR_PASS}EXEC${COLOR_NONE} ${script}"
+    sudo killall ot-rcp || true
+    sudo killall ot-cli || true
+    sudo killall ot-cli-ftd || true
+    sudo killall ot-cli-mtd || true
+    sudo rm -rf tmp
+    mkdir tmp
+    {
+        if [[ ${OT_NATIVE_IP} == 1 ]]; then
+            sudo -E expect -df "${script}" 2>"${log_file}"
+        else
+            expect -df "${script}" 2>"${log_file}"
+        fi
+    } || {
+        local EXIT_CODE=$?
+
+        # The exit status 77 for skipping is inherited from automake's test driver for script-based testsuites
+        if [[ ${EXIT_CODE} == 77 ]]; then
+            echo -e "\n${COLOR_SKIP}SKIP${COLOR_NONE} ${script}"
+            return 0
+        else
+            echo -e "\n${COLOR_FAIL}FAIL${COLOR_NONE} ${script}"
+            cat "${log_file}" >&2
+            return "${EXIT_CODE}"
+        fi
+    }
+    echo -e "\n${COLOR_PASS}PASS${COLOR_NONE} ${script}"
+    if [[ ${VERBOSE} == 1 ]]; then
+        cat "${log_file}" >&2
+    fi
+}
+
 do_expect()
 {
     local test_patterns
@@ -275,33 +346,13 @@
         test_patterns=(-name 'cli-*.exp' -o -name 'simulation-*.exp')
     fi
 
-    local log_file="tmp/log_expect"
-    while read -r script; do
-        sudo rm -rf tmp
-        mkdir tmp
-        {
-            if [[ ${OT_NATIVE_IP} == 1 ]]; then
-                sudo -E expect -df "${script}" 2>"${log_file}"
-            else
-                expect -df "${script}" 2>"${log_file}"
-            fi
-        } || {
-            local exit_code=$?
-            cat "${log_file}" >&2
-            echo -e "${COLOR_FAIL}FAIL${COLOR_NONE} ${script}"
-            exit "${exit_code}"
-        }
-        if [[ ${VERBOSE} == 1 ]]; then
-            cat "${log_file}" >&2
-        fi
-        echo -e "${COLOR_PASS}PASS${COLOR_NONE} ${script}"
-    done < <(
-        if [[ $# != 0 ]]; then
-            for script in "$@"; do echo ${script}; done
-        else
-            find tests/scripts/expect -type f -executable \( "${test_patterns[@]}" \)
-        fi
-    )
+    export -f ot_exec_expect_script
+
+    if [[ $# != 0 ]]; then
+        for script in "$@"; do bash -c "ot_exec_expect_script ${script}"; done
+    else
+        find tests/scripts/expect -type f -perm "$([[ $OSTYPE == darwin* ]] && echo '+' || echo '/')"111 \( "${test_patterns[@]}" \) -exec bash -c 'ot_exec_expect_script "$1"' _ {} \;
+    fi
 
     exit 0
 }
@@ -316,10 +367,14 @@
                     'rcp-ncp' for NCP on POSIX platform.
                     The default is 'cli'.
     OT_NATIVE_IP    1 to enable platform UDP and netif on POSIX platform. The default is 0.
+    OT_BUILDDIR     The output directory for cmake build. By default the directory is './build'. For example,
+                    'OT_BUILDDIR=\${PWD}/my_awesome_build ./script/test clean build'.
     VERBOSE         1 to build or test verbosely. The default is 0.
-    VIRTUAL_TIME    1 for virtual time, otherwise real time. The default is 1.
-    THREAD_VERSION  1.1 for Thread 1.1 stack, 1.2 for Thread 1.2 stack. The default is 1.1.
+    VIRTUAL_TIME    1 for virtual time, otherwise real time. The default value is 0 when running expect tests,
+                    otherwise default value is 1.
+    THREAD_VERSION  1.1 for Thread 1.1 stack, 1.2 for Thread 1.2 stack. The default is 1.2.
     INTER_OP        1 to build 1.1 together. Only works when THREAD_VERSION is 1.2. The default is 0.
+    INTER_OP_BBR    1 to build bbr version together. Only works when THREAD_VERSION is 1.2. The default is 1.
 
 COMMANDS:
     clean           Clean built files to prepare for new build.
@@ -347,10 +402,14 @@
     VIRTUAL_TIME=0 $0 clean build cert tests/scripts/thread-cert/Cert_5_1_01_RouterAttach.py
     VIRTUAL_TIME=0 $0 cert tests/scripts/thread-cert/Cert_5_1_02_ChildAddressTimeout.py
 
+    # Test Thread 1.1 CLI with real time
+    THREAD_VERSION=1.1 VIRTUAL_TIME=0 $0 clean build cert tests/scripts/thread-cert/Cert_5_1_01_RouterAttach.py
+    THREAD_VERSION=1.1 VIRTUAL_TIME=0 $0 cert tests/scripts/thread-cert/Cert_5_1_02_ChildAddressTimeout.py
+
     # Test Thread 1.2 with real time, use 'INTER_OP=1' when the case needs both versions.
-    THREAD_VERSION=1.2 VIRTUAL_TIME=0 $0 clean build cert tests/scripts/thread-cert/v1_2_test_enhanced_keep_alive.py
-    THREAD_VERSION=1.2 INTER_OP=1 VIRTUAL_TIME=0 $0 clean build cert tests/scripts/thread-cert/v1_2_router_5_1_1.py
-    THREAD_VERSION=1.2 INTER_OP=1 VIRTUAL_TIME=0 $0 clean build cert_suite tests/scripts/thread-cert/v1_2_*
+    VIRTUAL_TIME=0 $0 clean build cert tests/scripts/thread-cert/v1_2_test_enhanced_keep_alive.py
+    INTER_OP=1 VIRTUAL_TIME=0 $0 clean build cert tests/scripts/thread-cert/v1_2_router_5_1_1.py
+    INTER_OP=1 VIRTUAL_TIME=0 $0 clean build cert_suite tests/scripts/thread-cert/v1_2_*
 
     # Run a single expect test
     $0 clean build expect tests/scripts/expect/cli-log-level.exp
@@ -365,7 +424,7 @@
 do_package()
 {
     local builddir
-    local options=("-DCMAKE_BUILD_TYPE=Release" "-DOT_LOG_LEVEL=INFO")
+    local options=("-DCMAKE_BUILD_TYPE=Release" "-DOT_LOG_LEVEL=INFO" "-DOT_THREAD_VERSION=${THREAD_VERSION}")
 
     if [[ ${ot_extra_options[*]+x} ]]; then
         options+=("${ot_extra_options[@]}")
@@ -386,16 +445,47 @@
 
 do_prepare_coredump_upload()
 {
-    echo "$PWD/ot-core-dump/corefile-%e-%p-%t" | sudo tee /proc/sys/kernel/core_pattern
-    mkdir -p ot-core-dump
+    echo "$OT_COREDUMP_DIR/corefile-%e-%p-%t" | sudo tee /proc/sys/kernel/core_pattern
+    rm -rf "$OT_COREDUMP_DIR"
+    mkdir -p "$OT_COREDUMP_DIR"
 }
 
 do_copy_so_lib()
 {
-    mkdir -p so-lib
-    cp /lib/x86_64-linux-gnu/libgcc_s.so.1 ./so-lib
-    cp /lib/x86_64-linux-gnu/libc.so.6 ./so-lib
-    cp /lib64/ld-linux-x86-64.so.2 ./so-lib
+    mkdir -p "$OT_COREDUMP_DIR/so-lib"
+    cp /lib/x86_64-linux-gnu/libgcc_s.so.1 "$OT_COREDUMP_DIR/so-lib"
+    cp /lib/x86_64-linux-gnu/libc.so.6 "$OT_COREDUMP_DIR/so-lib"
+    cp /lib64/ld-linux-x86-64.so.2 "$OT_COREDUMP_DIR/so-lib"
+}
+
+do_check_crash()
+{
+    shopt -s nullglob
+
+    # Scan core dumps and collect binaries which crashed
+    declare -A bin_list=([dummy]='')
+    for f in "$OT_COREDUMP_DIR"/core*; do
+        bin=$(file "$f" | grep -E -o "execfn: '(.*')," | sed -r "s/execfn: '(.*)',/\1/")
+        bin_list[$bin]=''
+    done
+
+    for key in "${!bin_list[@]}"; do
+        if [ "$key" != "dummy" ]; then
+            # Add postfix for binaries to avoid conflicts caused by different Thread version
+            postfix=""
+            if [[ $key =~ openthread-(simulation|posix)-([0-9]\.[0-9]) ]]; then
+                postfix="-$(echo "$key" | sed -r "s/.*openthread-(simulation|posix)-([0-9]\.[0-9]).*/\2/")"
+            fi
+            bin_name=$(basename "$key")
+            cp "$key" "$OT_COREDUMP_DIR"/"$bin_name""$postfix"
+        fi
+    done
+
+    # echo 1 and copy so libs if crash found, echo 0 otherwise
+    [[ ${#bin_list[@]} -gt 1 ]] && (
+        echo 1
+        do_copy_so_lib
+    ) || echo 0
 }
 
 do_generate_coverage()
@@ -517,11 +607,13 @@
                 ;;
             cert)
                 shift
-                do_cert "$1"
+                do_cert "$@"
+                shift $#
                 ;;
             cert_suite)
                 shift
                 do_cert_suite "$@"
+                shift $#
                 ;;
             get_thread_wireshark)
                 do_get_thread_wireshark
@@ -549,8 +641,8 @@
             prepare_coredump_upload)
                 do_prepare_coredump_upload
                 ;;
-            copy_so_lib)
-                do_copy_so_lib
+            check_crash)
+                do_check_crash
                 ;;
             generate_coverage)
                 shift
diff --git a/script/update-makefiles.py b/script/update-makefiles.py
new file mode 100755
index 0000000..4d0c4d7
--- /dev/null
+++ b/script/update-makefiles.py
@@ -0,0 +1,191 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+# This script updates different make/build files (CMakeLists.txt, BUILD.gn,
+# Andriod.mk, Andriod.bp, auto-make) in OpenThread repo based on the
+# current files present in `./src/core/` & `./include/openthread/`
+# folders. This script MUST be called from openthread root folder.
+
+import os
+
+#----------------------------------------------------------------------------------------------
+# Helper functions
+
+
+def get_file_list(path, extension):
+    """Get a sorted list of full file names (with path) in a given `path` folder having a given `extension`"""
+    return sorted([
+        "{}/{}".format(dir_path, file_name)[2:]
+        for dir_path, dir_names, file_names in os.walk(path)
+        for file_name in file_names
+        if file_name.endswith(extension)
+    ])
+
+
+def read_txt_file(file_name):
+    """Read the content of a text file with name `file_name` and return content as a list of lines"""
+    with open(file_name, 'r') as file:
+        lines = file.readlines()
+    return lines
+
+
+def write_txt_file(file_name, lines):
+    """Write a text file with name `file_name` with the content given as a list of `lines`"""
+    with open(file_name, 'w') as file:
+        file.writelines(lines)
+
+
+def update_build_file(file_name, start_string, end_string, new_list, search_string=None):
+    """
+    Update the file given by `file_name` by replacing the list after the first occurrence of `start_string` up to
+    `end_string` with the `new_list`. If `search_string` is given, then the search for `start_string` only starts
+    after seeing the `search_string` line in the file.
+    """
+    STATE_SEARCH = 1
+    STATE_MATCH_START = 2
+    STATE_MATCH_END = 3
+    STATE_DONE = 4
+
+    lines = read_txt_file(file_name)
+    new_lines = []
+    state = STATE_SEARCH if search_string else STATE_MATCH_START
+
+    for line in lines:
+        if state == STATE_SEARCH:
+            new_lines.append(line)
+            if line.startswith(search_string):
+                state = STATE_MATCH_START
+        elif state == STATE_MATCH_START:
+            new_lines.append(line)
+            if line.startswith(start_string):
+                new_lines.extend(new_list)
+                state = STATE_MATCH_END
+        elif state == STATE_MATCH_END:
+            if line.startswith(end_string):
+                new_lines.append(line)
+                state = STATE_DONE
+        else:
+            new_lines.append(line)
+
+    if state != STATE_DONE:
+        raise RuntimeError('failed to update build file: {}'.format(file_name))
+
+    if new_lines != lines:
+        write_txt_file(file_name, new_lines)
+
+
+#----------------------------------------------------------------------------------------------
+
+# Get the list of hpp/h/cpp files in different folders.
+
+src_core_path = "./src/core"
+core_h_files = get_file_list(src_core_path, '.h')
+core_hpp_files = get_file_list(src_core_path, '.hpp')
+core_cpp_files = get_file_list(src_core_path, '.cpp')
+core_cpp_files = [item for item in core_cpp_files if not item.endswith("extension_example.cpp")]
+core_hpp_cpp_files = sorted(core_hpp_files + core_cpp_files)
+core_h_hpp_files = sorted(core_h_files + core_hpp_files)
+
+include_path = "./include/openthread"
+include_h_files = get_file_list(include_path, '.h')
+include_ot_h_files = [name[8:] for name in include_h_files if not name.startswith("include/openthread/platform")]
+include_platform_h_files = [name[8:] for name in include_h_files if name.startswith("include/openthread/platform")]
+
+#----------------------------------------------------------------------------------------------
+# Update CMakeLists.txt files
+
+core_cmakelist_txt_file = "./src/core/CMakeLists.txt"
+
+formatted_list = ["    {}\n".format(file_name[9:]) for file_name in core_cpp_files]
+update_build_file(core_cmakelist_txt_file, "set(COMMON_SOURCES\n", ")\n", formatted_list)
+
+print("Updated " + core_cmakelist_txt_file)
+
+#----------------------------------------------------------------------------------------------
+# Update Build.gn files
+
+core_build_gn_file = "./src/core/BUILD.gn"
+
+formatted_list = ["  \"{}\",\n".format(file_name[9:]) for file_name in core_hpp_cpp_files]
+update_build_file(core_build_gn_file, "openthread_core_files = [\n", "]\n", formatted_list)
+
+formatted_list = ["    \"{}\",\n".format(file_name[9:]) for file_name in core_h_files]
+update_build_file(core_build_gn_file, "  public = [\n", "  ]\n", formatted_list)
+
+print("Updated " + core_build_gn_file)
+
+include_build_gn_file = "./include/openthread/BUILD.gn"
+
+formatted_list = ["    \"{}\",\n".format(file_name[19:]) for file_name in include_h_files]
+update_build_file(include_build_gn_file, "  public = [\n", "  ]\n", formatted_list)
+
+print("Updated " + include_build_gn_file)
+
+#----------------------------------------------------------------------------------------------
+# Update Android.mk file
+
+android_mk_file = "./Android.mk"
+
+formatted_list = ["    {:<55} \\\n".format(file_name) for file_name in core_cpp_files]
+start_string = "LOCAL_SRC_FILES                                          := \\\n"
+end_string = "    src/lib/hdlc/hdlc.cpp"
+update_build_file(android_mk_file, start_string, end_string, formatted_list)
+
+print("Updated " + android_mk_file)
+
+#----------------------------------------------------------------------------------------------
+# Update Makefile.am files
+
+core_makefile_am_file = "./src/core/Makefile.am"
+
+formatted_list = ["    {:<45} \\\n".format(file_name[9:]) for file_name in core_cpp_files]
+start_string = "SOURCES_COMMON                                  = \\\n"
+end_string = "    $(NULL)\n"
+update_build_file(core_makefile_am_file, start_string, end_string, formatted_list)
+
+formatted_list = ["    {:<45} \\\n".format(file_name[9:]) for file_name in core_h_hpp_files]
+start_string = "HEADERS_COMMON                                  = \\\n"
+end_string = "    $(NULL)\n"
+update_build_file(core_makefile_am_file, start_string, end_string, formatted_list)
+
+print("Updated " + core_makefile_am_file)
+
+include_makefile_am_file = "./include/Makefile.am"
+
+formatted_list = ["    {:<37} \\\n".format(file_name) for file_name in include_ot_h_files]
+start_string = "openthread_headers                      = \\\n"
+end_string = "    $(NULL)\n"
+update_build_file(include_makefile_am_file, start_string, end_string, formatted_list)
+
+formatted_list = ["    {:<37} \\\n".format(file_name) for file_name in include_platform_h_files]
+start_string = "ot_platform_headers                     = \\\n"
+end_string = "    $(NULL)\n"
+update_build_file(include_makefile_am_file, start_string, end_string, formatted_list)
+
+print("Updated " + include_makefile_am_file)
diff --git a/src/Makefile.am b/src/Makefile.am
index b06a7e4..97622dc 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -51,6 +51,10 @@
 
 if OPENTHREAD_ENABLE_NCP
 SUBDIRS                                += ncp
+else
+if OPENTHREAD_ENABLE_RADIO_ONLY
+SUBDIRS                                += ncp
+endif
 endif
 
 if OPENTHREAD_PLATFORM_POSIX
diff --git a/src/cli/BUILD.gn b/src/cli/BUILD.gn
index 1eeb4aa..cce5768 100644
--- a/src/cli/BUILD.gn
+++ b/src/cli/BUILD.gn
@@ -37,16 +37,16 @@
   "cli_commissioner.cpp",
   "cli_commissioner.hpp",
   "cli_config.h",
-  "cli_console.cpp",
-  "cli_console.hpp",
   "cli_dataset.cpp",
   "cli_dataset.hpp",
   "cli_joiner.cpp",
   "cli_joiner.hpp",
   "cli_network_data.cpp",
   "cli_network_data.hpp",
-  "cli_uart.cpp",
-  "cli_uart.hpp",
+  "cli_srp_client.cpp",
+  "cli_srp_client.hpp",
+  "cli_srp_server.cpp",
+  "cli_srp_server.hpp",
   "cli_udp.cpp",
   "cli_udp.hpp",
   "x509_cert_key.hpp",
@@ -54,11 +54,6 @@
 
 config("cli_config") {
   defines = []
-  if (openthread_posix) {
-    defines += ["OPENTHREAD_POSIX=1"]
-  } else {
-    defines += ["OPENTHREAD_POSIX=0"]
-  }
 }
 
 static_library("libopenthread-cli-ftd") {
diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt
index 2a1456e..c60eb05 100644
--- a/src/cli/CMakeLists.txt
+++ b/src/cli/CMakeLists.txt
@@ -26,13 +26,6 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
-set(OT_CLI_TRANSPORT "UART" CACHE STRING "set the CLI transport")
-set(OT_CLI_TRANSPORT_VALUES
-    "UART"
-    "CONSOLE"
-)
-set_property(CACHE OT_CLI_TRANSPORT PROPERTY STRINGS ${OT_CLI_TRANSPORT_VALUES})
-
 set(COMMON_INCLUDES
     ${PROJECT_SOURCE_DIR}/src
     ${PROJECT_SOURCE_DIR}/src/core
@@ -43,11 +36,11 @@
     cli_coap.cpp
     cli_coap_secure.cpp
     cli_commissioner.cpp
-    cli_console.cpp
     cli_dataset.cpp
     cli_joiner.cpp
     cli_network_data.cpp
-    cli_uart.cpp
+    cli_srp_client.cpp
+    cli_srp_server.cpp
     cli_udp.cpp
 )
 
diff --git a/src/cli/Makefile.am b/src/cli/Makefile.am
index b1b89c2..060ff15 100644
--- a/src/cli/Makefile.am
+++ b/src/cli/Makefile.am
@@ -125,16 +125,6 @@
     $(OPENTHREAD_TARGET_DEFINES)      \
     $(NULL)
 
-if OPENTHREAD_POSIX
-CPPFLAGS_COMMON +=                    \
-    -DOPENTHREAD_POSIX=1              \
-    $(NULL)
-else
-CPPFLAGS_COMMON +=                    \
-    -DOPENTHREAD_POSIX=0              \
-    $(NULL)
-endif
-
 libopenthread_cli_mtd_a_CPPFLAGS =    \
     -DOPENTHREAD_MTD=1                \
     -DOPENTHREAD_FTD=0                \
@@ -154,11 +144,11 @@
     cli_coap.cpp                      \
     cli_coap_secure.cpp               \
     cli_commissioner.cpp              \
-    cli_console.cpp                   \
     cli_dataset.cpp                   \
     cli_joiner.cpp                    \
     cli_network_data.cpp              \
-    cli_uart.cpp                      \
+    cli_srp_client.cpp                \
+    cli_srp_server.cpp                \
     cli_udp.cpp                       \
     $(NULL)
 
@@ -176,11 +166,11 @@
     cli_coap_secure.hpp               \
     cli_commissioner.hpp              \
     cli_config.h                      \
-    cli_console.hpp                   \
     cli_dataset.hpp                   \
     cli_joiner.hpp                    \
     cli_network_data.hpp              \
-    cli_uart.hpp                      \
+    cli_srp_client.hpp                \
+    cli_srp_server.hpp                \
     cli_udp.hpp                       \
     x509_cert_key.hpp                 \
     $(NULL)
diff --git a/src/cli/README.md b/src/cli/README.md
index 6969ddd..f9c0321 100644
--- a/src/cli/README.md
+++ b/src/cli/README.md
@@ -22,6 +22,7 @@
 ## OpenThread Command List
 
 - [bbr](#bbr)
+- [br](#br)
 - [bufferinfo](#bufferinfo)
 - [ccathreshold](#ccathreshold)
 - [channel](#channel)
@@ -40,7 +41,7 @@
 - [delaytimermin](#delaytimermin)
 - [diag](#diag)
 - [discover](#discover-channel)
-- [dns](#dns-resolve-hostname-dns-server-ip-dns-server-port)
+- [dns](#dns-config)
 - [domainname](#domainname)
 - [dua](#dua-iid)
 - [eidcache](#eidcache)
@@ -66,6 +67,7 @@
 - [masterkey](#masterkey)
 - [mlr](#mlr-reg-ipaddr--timeout)
 - [mode](#mode)
+- [multiradio](#multiradio)
 - [neighbor](#neighbor-list)
 - [netdata](README_NETDATA.md)
 - [netstat](#netstat)
@@ -84,6 +86,7 @@
 - [promiscuous](#promiscuous)
 - [pskc](#pskc--p-keypassphrase)
 - [rcp](#rcp)
+- [region](#region)
 - [releaserouterid](#releaserouterid-routerid)
 - [reset](#reset)
 - [rloc16](#rloc16)
@@ -98,6 +101,7 @@
 - [singleton](#singleton)
 - [sntp](#sntp-query-sntp-server-ip-sntp-server-port)
 - [state](#state)
+- [srp](README_SRP.md)
 - [thread](#thread-start)
 - [txpower](#txpower)
 - [unsecureport](#unsecureport-add-port)
@@ -311,6 +315,20 @@
 Done
 ```
 
+### br
+
+Enbale/disable the Border Routing functionality.
+
+```bash
+> br enable
+Done
+```
+
+```bash
+> br disable
+Done
+```
+
 ### bufferinfo
 
 Show the current message buffer information.
@@ -729,18 +747,91 @@
 Done
 ```
 
-### dns resolve \<hostname\> \[DNS server IP\] \[DNS server port\]
+### dns config
 
-Send DNS Query to obtain IPv6 address for given hostname. The latter two parameters have following default values:
+Get the default query config used by DNS client.
 
-- DNS server IP: 2001:4860:4860::8888 (Google DNS Server)
-- DNS server port: 53
+The config includes the server IPv6 address and port, response timeout in msec (wait time to rx response), maximum tx attempts before reporting failure, boolean flag to indicate whether the server can resolve the query recursively or not.
+
+```bash
+> dns config
+Server: [fd00:0:0:0:0:0:0:1]:1234
+ResponseTimeout: 5000 ms
+MaxTxAttempts: 2
+RecursionDesired: no
+Done
+>
+```
+
+### dns config \[DNS server IP\] \[DNS server port\] \[response timeout (ms)\] \[max tx attempts\] \[recursion desired (boolean)\]
+
+Set the default query config.
+
+```bash
+> dns config fd00::1 1234 5000 2 0
+Done
+
+> dns config
+Server: [fd00:0:0:0:0:0:0:1]:1234
+ResponseTimeout: 5000 ms
+MaxTxAttempts: 2
+RecursionDesired: no
+Done
+```
+
+We can leave some of the fields as unspecified (or use value zero). The unspecified fields are replaced by the corresponding OT config option definitions `OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_{}` to form the default query config.
+
+```bash
+> dns config fd00::2
+Done
+
+> dns config
+Server: [fd00:0:0:0:0:0:0:2]:53
+ResponseTimeout: 3000 ms
+MaxTxAttempts: 3
+RecursionDesired: yes
+Done
+```
+
+### dns resolve \<hostname\> \[DNS server IP\] \[DNS server port\] \[response timeout (ms)\] \[max tx attempts\] \[recursion desired (boolean)\]
+
+Send DNS Query to obtain IPv6 address for given hostname.
+
+The parameters after `hostname` are optional. Any unspecified (or zero) value for these optional parameters is replaced by the value from the current default config (`dns config`).
 
 ```bash
 > dns resolve ipv6.google.com
 > DNS response for ipv6.google.com - 2a00:1450:401b:801:0:0:0:200e TTL: 300
 ```
 
+### dns browse \<service-name\> \[DNS server IP\] \[DNS server port\] \[response timeout (ms)\] \[max tx attempts\] \[recursion desired (boolean)\]
+
+Send a browse (service instance enumeration) DNS query to get the list of services for given service-name.
+
+The parameters after `service-name` are optional. Any unspecified (or zero) value for these optional parameters is replaced by the value from the current default config (`dns config`).
+
+```bash
+> dns browse _service._udp.example.com
+DNS browse response for _service._udp.example.com.
+inst1
+    Port:1234, Priority:1, Weight:2, TTL:7200
+    Host:host.example.com.
+    HostAddress:fd00:0:0:0:0:0:0:abcd TTL:7200
+    TXT:[a=6531, b=6c12] TTL:7300
+instance2
+    Port:1234, Priority:1, Weight:2, TTL:7200
+    Host:host.example.com.
+    HostAddress:fd00:0:0:0:0:0:0:abcd TTL:7200
+    TXT:[a=1234] TTL:7300
+Done
+```
+
+### dns service \<service-instance-label\> \<service-name\> \[DNS server IP\] \[DNS server port\] \[response timeout (ms)\] \[max tx attempts\] \[recursion desired (boolean)\]
+
+Send a service instance resolution DNS query for a given service instance. Service instance label is provided first, followed by the service name (note that service instance label can contain dot '.' character).
+
+The parameters after `service-name` are optional. Any unspecified (or zero) value for these optional parameters is replaced by the value from the current default config (`dns config`).
+
 ### domainname
 
 Get the Thread Domain Name for Thread 1.2 device.
@@ -1363,6 +1454,43 @@
 Done
 ```
 
+### multiradio
+
+Get the list of supported radio links by the device.
+
+This command is always available, even when only a single radio is supported by the device.
+
+```bash
+> multiradio
+[15.4, TREL]
+Done
+```
+
+### multiradio neighbor list
+
+Get the list of neighbors and their supported radios and their preference.
+
+This command is only available when device supports more than one radio link.
+
+```bash
+> multiradio neighbor list
+ExtAddr:3a65bc38dbe4a5be, RLOC16:0xcc00, Radios:[15.4(255), TREL(255)]
+ExtAddr:17df23452ee4a4be, RLOC16:0x1300, Radios:[15.4(255)]
+Done
+```
+
+### multiradio neighbor \<ext address\>
+
+Get the radio info for specific neighbor with a given extended address.
+
+This command is only available when device supports more than one radio link.
+
+```bash
+> multiradio neighbor 3a65bc38dbe4a5be
+[15.4(255), TREL(255)]
+Done
+```
+
 ### neighbor list
 
 List RLOC16 of neighbors.
@@ -1709,6 +1837,18 @@
 
 RCP-related commands.
 
+### region
+
+Set the radio region, this can affect the transmit power limit.
+
+```bash
+> region US
+Done
+> region
+US
+Done
+```
+
 ### rcp version
 
 Print RCP version string.
@@ -1793,10 +1933,10 @@
 
 ```bash
 > router table
-| ID | RLOC16 | Next Hop | Path Cost | LQ In | LQ Out | Age | Extended MAC     |
-+----+--------+----------+-----------+-------+--------+-----+------------------+
-| 21 | 0x5400 |       21 |         0 |     3 |      3 |   5 | d28d7f875888fccb |
-| 56 | 0xe000 |       56 |         0 |     0 |      0 | 182 | f2d92a82c8d8fe43 |
+| ID | RLOC16 | Next Hop | Path Cost | LQ In | LQ Out | Age | Extended MAC     | Link |
++----+--------+----------+-----------+-------+--------+-----+------------------+------+
+| 22 | 0x5800 |       63 |         0 |     0 |      0 |   0 | 0aeb8196c9f61658 |    0 |
+| 49 | 0xc400 |       63 |         0 |     3 |      3 |   0 | faa1c03908e2dbf2 |    1 |
 Done
 ```
 
diff --git a/src/cli/README_COAP.md b/src/cli/README_COAP.md
index 1dc8630..755f2d5 100644
--- a/src/cli/README_COAP.md
+++ b/src/cli/README_COAP.md
@@ -115,13 +115,18 @@
 
 - address: IPv6 address of the CoAP server.
 - uri-path: URI path of the resource.
-- type: "con" for Confirmable or "non-con" for Non-confirmable (default).
+- type: "con" for Confirmable or "non-con" for Non-confirmable (default). Use "block-<block-size>" if the response should be transferred block-wise. ("block-16","block-32","block-64","block-128","block-256","block-512","block-1024")
 
 ```bash
 > coap get fdde:ad00:beef:0:2780:9423:166c:1aac test-resource
 Done
 ```
 
+```bash
+> coap get fdde:ad00:beef:0:2780:9423:166c:1aac test-resource block-1024
+Done
+```
+
 ### observe \<address\> \<uri-path\> \[type\]
 
 This is the same a `get`, but the `Observe` parameter will be sent, set to 0 triggering a subscription request.
@@ -176,26 +181,36 @@
 
 - address: IPv6 address of the CoAP server.
 - uri-path: URI path of the resource.
-- type: "con" for Confirmable or "non-con" for Non-confirmable (default).
-- payload: CoAP request payload.
+- type: "con" for Confirmable or "non-con" for Non-confirmable (default). Use "block-<block-size>" to send blocks with random payload. ("block-16","block-32","block-64","block-128","block-256","block-512","block-1024")
+- payload: CoAP request payload. If \[type\] is "block-<block-size>", the amount of blocks to be sent can be set here.
 
 ```bash
 > coap post fdde:ad00:beef:0:2780:9423:166c:1aac test-resource con payload
 Done
 ```
 
+```bash
+> coap post fdde:ad00:beef:0:2780:9423:166c:1aac test-resource block-1024 10
+Done
+```
+
 ### put \<address\> \<uri-path\> \[type\] \[payload\]
 
 - address: IPv6 address of the CoAP server.
 - uri-path: URI path of the resource.
-- type: "con" for Confirmable or "non-con" for Non-confirmable (default).
-- payload: CoAP request payload.
+- type: "con" for Confirmable or "non-con" for Non-confirmable (default). Use "block-<block-size>" to send blocks with random payload. ("block-16","block-32","block-64","block-128","block-256","block-512","block-1024")
+- payload: CoAP request payload. If \[type\] is "block-<block-size>", the amount of blocks to be sent can be set here.
 
 ```bash
 > coap put fdde:ad00:beef:0:2780:9423:166c:1aac test-resource con payload
 Done
 ```
 
+```bash
+> coap put fdde:ad00:beef:0:2780:9423:166c:1aac test-resource block-1024 10
+Done
+```
+
 ### resource \[uri-path\]
 
 Sets the URI path for the test resource.
diff --git a/src/cli/README_COAPS.md b/src/cli/README_COAPS.md
index 2d31fee..0cb4f99 100644
--- a/src/cli/README_COAPS.md
+++ b/src/cli/README_COAPS.md
@@ -163,24 +163,34 @@
 ### get \<uri-path\> \[type\]
 
 - uri-path: URI path of the resource.
-- type: "con" for Confirmable or "non-con" for Non-confirmable (default).
+- type: "con" for Confirmable or "non-con" for Non-confirmable (default). Use "block-<block-size>" if the response should be transferred block-wise. ("block-16","block-32","block-64","block-128","block-256","block-512","block-1024")
 
 ```bash
 > coaps get test-resource
 Done
 ```
 
+```bash
+> coaps get test-resource block-1024
+Done
+```
+
 ### post \<uri-path\> \[type\] \[payload\]
 
 - uri-path: URI path of the resource.
-- type: "con" for Confirmable or "non-con" for Non-confirmable (default).
-- payload: CoAPS request payload.
+- type: "con" for Confirmable or "non-con" for Non-confirmable (default). Use "block-<block-size>" to send blocks with random payload. ("block-16","block-32","block-64","block-128","block-256","block-512","block-1024")
+- payload: CoAP request payload. If \[type\] is "block-<block-size>", the amount of blocks to be sent can be set here.
 
 ```bash
 > coaps post test-resource con payload
 Done
 ```
 
+```bash
+> coaps post test-resource block-1024 10
+Done
+```
+
 ### psk \<psk\> \<pskid\>
 
 Set DTLS ciphersuite to `TLS_PSK_WITH_AES_128_CCM_8`.
@@ -196,14 +206,19 @@
 ### put \<uri-path\> \[type\] \[payload\]
 
 - uri-path: URI path of the resource.
-- type: "con" for Confirmable or "non-con" for Non-confirmable (default).
-- payload: CoAPS request payload.
+- type: "con" for Confirmable or "non-con" for Non-confirmable (default). Use "block-<block-size>" to send blocks with random payload. ("block-16","block-32","block-64","block-128","block-256","block-512","block-1024")
+- payload: CoAP request payload. If \[type\] is "block-<block-size>", the amount of blocks to be sent can be set here.
 
 ```bash
 > coaps put test-resource con payload
 Done
 ```
 
+```bash
+> coaps put test-resource block-1024 10
+Done
+```
+
 ### resource \[uri-path\]
 
 Sets the URI path for the test resource.
diff --git a/src/cli/README_JOINER.md b/src/cli/README_JOINER.md
index 6d8c2ab..715ccdb 100644
--- a/src/cli/README_JOINER.md
+++ b/src/cli/README_JOINER.md
@@ -18,7 +18,7 @@
 
 Usage: `joiner help`
 
-Print dataset help menu.
+Print joiner help menu.
 
 ```bash
 > joiner help
diff --git a/src/cli/README_SRP.md b/src/cli/README_SRP.md
new file mode 100644
index 0000000..08c0b6f
--- /dev/null
+++ b/src/cli/README_SRP.md
@@ -0,0 +1,197 @@
+# OpenThread CLI - SRP (Service Registration Protocol)
+
+## Quick Start
+
+### Build with SRP Server & Client support
+
+Use the `SRP_SERVER=1 SERVICE=1 ECDSA=1` build switches to enable SRP Server API support, and the `SRP_CLIENT=1 ECDSA=1` build switches for SRP Client API support.
+
+Make simulation SRP Server & Client:
+
+```bash
+./bootstrap
+make -f examples/Makefile-simulation SRP_SERVER=1 SRP_CLIENT=1 SERVICE=1 ECDSA=1
+```
+
+## Simulation Example
+
+### Start SRP Server
+
+Start the SRP Server node:
+
+```bash
+./output/simulation/bin/ot-cli-ftd 1
+```
+
+Setup a Thread network and start the SRP Server:
+
+```bash
+> dataset init new
+Done
+> dataset
+Active Timestamp: 1
+Channel: 22
+Channel Mask: 0x07fff800
+Ext PAN ID: 8d6ed7a05a28fb3b
+Mesh Local Prefix: fded:5114:8263:1fe1::/64
+Master Key: 7fcbae4153cc2955c28440c15d4d4219
+Network Name: OpenThread-f7af
+PAN ID: 0xf7af
+PSKc: b658e40f174e3a11be149b302ef07a0f
+Security Policy: 672, onrcb
+Done
+> dataset commit active
+Done
+> ifconfig up
+Done
+> thread start
+Done
+> state
+leader
+Done
+>ipaddr
+fded:5114:8263:1fe1:0:ff:fe00:fc00
+fded:5114:8263:1fe1:0:ff:fe00:c000
+fded:5114:8263:1fe1:68bc:ec03:c1ad:9325
+fe80:0:0:0:a8cd:6e23:df3d:4193
+Done
+> srp server enable
+Done
+```
+
+### Start SRP Client
+
+Start the SRP Client node:
+
+```bash
+./output/simulation/bin/ot-cli-ftd 2
+```
+
+Join the Thread Network and register a `_ipps._tcp` service:
+
+```bash
+> dataset masterkey 7fcbae4153cc2955c28440c15d4d4219
+Done
+> dataset commit active
+Done
+> ifconfig up
+Done
+> thread start
+Done
+> state
+child
+Done
+> ipaddr
+fded:5114:8263:1fe1:0:ff:fe00:c001
+fded:5114:8263:1fe1:44f9:cc06:4a2d:534
+fe80:0:0:0:38dd:fdf7:5fd:24e
+Done
+> srp client host name my-host
+Done
+> srp client host address fded:5114:8263:1fe1:44f9:cc06:4a2d:534
+Done
+> srp client service add my-service _ipps._tcp 12345
+Done
+> srp client autostart enable
+Done
+```
+
+The last command enables the auto-start mode on the client which then monitors the network data to discover available SRP servers within the Thread network and automatically starts the client.
+
+Alternatively, the client can be started manually using the `srp client start`.
+
+The SRP Server listening UDP port (which is `c002`(`49154`) in the example below) can be found from the Server Data (listed by the `netdata show` command).
+
+Make sure the SRP Server address & port are used for the `srp client start` command.
+
+```bash
+> netdata show
+Prefixes:
+Routes:
+Services:
+44970 5d c002 s 8400
+Done
+srp client start fded:5114:8263:1fe1:68bc:ec03:c1ad:9325 49154
+Done
+```
+
+### Verify the service status
+
+Check if the host and service has been successfully registered on the client node:
+
+```bash
+> srp client host
+name:"my-host", state:Registered, addrs:[fded:5114:8263:1fe1:44f9:cc06:4a2d:534]
+Done
+> srp client service
+instance:"my-service", name:"_ipps._tcp", state:Registered, port:12345, priority:0, weight:0
+Done
+```
+
+Make sure it shows `state:Registered` for both host and service commands.
+
+Check the host & service on the server node:
+
+```bash
+> srp server host
+my-host.default.service.arpa.
+    deleted: false
+    addresses: [fded:5114:8263:1fe1:44f9:cc06:4a2d:534]
+Done
+> srp server service
+my-service._ipps._tcp.default.service.arpa.
+    deleted: false
+    port: 12345
+    priority: 0
+    weight: 0
+    TXT: 00
+    host: my-host.default.service.arpa.
+    addresses: [fded:5114:8263:1fe1:44f9:cc06:4a2d:534]
+Done
+```
+
+Make sure it shows `deleted: false` for both host and service commands.
+
+### Remove the service
+
+A service can be removed through the client node:
+
+```bash
+> srp client service remove my-service _ipps._tcp
+Done
+```
+
+Confirm on the server node that the service has been removed:
+
+```bash
+> srp server service
+my-service._ipps._tcp.default.service.arpa.
+    deleted: true
+Done
+```
+
+The service entry is listed because the name of service is not removed.
+
+### Remove the host and service names
+
+A host and service, along with their names, can be removed through the client node:
+
+```bash
+> srp client host remove 1
+Done
+```
+
+Confirm on the server node that no host or service entries are listed:
+
+```bash
+> srp server host
+Done
+> srp server service
+Done
+>
+```
+
+## CLI Reference
+
+- [SRP Client CLI Reference](README_SRP_CLIENT.md)
+- [SRP Server CLI Reference](README_SRP_SERVER.md)
diff --git a/src/cli/README_SRP_CLIENT.md b/src/cli/README_SRP_CLIENT.md
new file mode 100644
index 0000000..0baa1f5
--- /dev/null
+++ b/src/cli/README_SRP_CLIENT.md
@@ -0,0 +1,363 @@
+# OpenThread CLI - SRP Client
+
+## Command List
+
+Usage : `srp client [command] ...`
+
+- [help](#help)
+- [autostart](#autostart)
+- [callback](#callback)
+- [host](#host)
+- [keyleaseinterval](#keyleaseinterval)
+- [leaseinterval](#leaseinterval)
+- [server](#server)
+- [service](#service)
+- [start](#start)
+- [state](#state)
+- [stop](#stop)
+
+## Command Details
+
+### help
+
+Usage: `srp client help`
+
+Print SRP client help menu.
+
+```bash
+> srp client help
+autostart
+callback
+help
+host
+keyleaseinterval
+leaseinterval
+service
+start
+state
+stop
+Done
+```
+
+### autostart
+
+Usage `srp client autostart [enable|disable]`
+
+Enable/Disable auto start mode in SRP client. This command requires `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE` feature to be enabled.
+
+Get the current autostart mode.
+
+```bash
+> srp client autostart
+Disabled
+Done
+```
+
+Set the autostart mode.
+
+```bash
+> srp client autostart enable
+Done
+
+> srp client autostart
+Enabled
+Done
+```
+
+### callback
+
+Usage `srp client callback [enable|disable]`
+
+Enable/Disable printing callback events from SRP client.
+
+Get current callback mode
+
+```bash
+> srp client callback
+Disabled
+Done
+```
+
+Set callback mode
+
+```bash
+> srp client callback enable
+Done
+
+> srp client callback
+Enabled
+Done
+```
+
+#### Example
+
+When two services are successfully registered:
+
+```bash
+SRP client callback - error:OK
+Host info:
+    name:"dev4312", state:Registered, addrs:[fd00:0:0:0:0:0:0:1]
+Service list:
+    instance:"ins2", name:"_test2._udp", state:Registered, port:111, priority:1, weight:1
+    instance:"ins1", name:"_test1._udp", state:Registered, port:777, priority:0, weight:0
+```
+
+When service `ins2` is removed:
+
+```bash
+SRP client callback - error:OK
+Host info:
+    name:"dev4312", state:Registered, addrs:[fd00:0:0:0:0:0:0:1]
+Service list:
+    instance:"ins1", name:"_test1._udp", state:Registered, port:777, priority:0, weight:0
+Removed service list:
+    instance:"ins2", name:"_test2._udp", state:Removed, port:111, priority:1, weight:1
+```
+
+When host info (and all services) is removed:
+
+```bash
+SRP client callback - error:OK
+Host info:
+    name:"dev4312", state:Removed, addrs:[fd00:0:0:0:0:0:0:1]
+Service list:
+Removed service list:
+    instance:"ins1", name:"_test1._udp", state:Removed, port:777, priority:0, weight:0
+```
+
+### host
+
+Usage: `srp client host`
+
+Print the full host info (host name, state, list of host addresses).
+
+```bash
+> srp client host
+name:"dev4312", state:Registered, addrs:[fd00:0:0:0:0:0:0:1234, fd00:0:0:0:0:0:0:beef]
+Done
+```
+
+### host name
+
+Usage: `srp client host name [name]`
+
+Get the host name.
+
+```bash
+> srp client host name
+dev4312
+Done
+```
+
+Set host name (can be set when the host is removed or not yet registered with server).
+
+```bash
+srp client host name dev4312
+Done
+```
+
+### host address
+
+Usage : `srp client host address [<address> ...]`
+
+Get the list of host addresses.
+
+```bash
+> srp client host address
+fd00:0:0:0:0:0:0:1234
+fd00:0:0:0:0:0:0:beef
+Done
+```
+
+Set the list of host addresses (can be set while client is running to update the host addresses)
+
+```bash
+> srp client host address fd00::cafe
+Done
+```
+
+### host state
+
+Usage: `srp client host state`
+
+Get the host state.
+
+```bash
+> srp client host state
+Registered
+Done
+```
+
+The possible states are (same value for service state):
+
+- `ToAdd`: item to be added/registered.
+- `Adding`: item is being added/registered.
+- `ToRefresh`: item to be refreshed (renew lease).
+- `Refreshing`: item is being refreshed.
+- `ToRemove`: item to be removed.
+- `Removing`: item is being removed.
+- `Registered`: item is registered with server.
+- `Removed`: item is removed.
+
+### host remove
+
+Usage: `srp client host remove [removekeylease]`
+
+Remove host info and all services from server. `removekeylease` is boolean value indicating whether or not the host key lease should also be removed
+
+```bash
+> srp client host remove 1
+Done
+```
+
+### host clear
+
+Usage: `srp client host clear`
+
+Clear host info and all services on client (unlike `host remove`, with `host clear` no update is sent to server).
+
+```bash
+> srp client host clear
+Done
+```
+
+### keyleaseinterval
+
+Usage: `srp client keyleaseinterval [interval]`
+
+Get the key lease interval (in seconds).
+
+```bash
+> srp client keyleaseinterval
+1209600
+Done
+>
+```
+
+Set the key lease interval.
+
+```bash
+> srp client keyleaseinterval 864000
+Done
+```
+
+### leaseinterval
+
+Usage: `srp client leaseinterval [interval]`
+
+Get the lease interval (in seconds).
+
+```bash
+> srp client leaseinterval
+7200
+Done
+>
+```
+
+Set the lease interval.
+
+```bash
+> srp client leaseinterval 3600
+Done
+```
+
+### server
+
+Usage: `srp client server`
+
+Print the server socket address (IPv6 address and port number).
+
+```bash
+> srp client server
+[fd00:0:0:0:d88a:618b:384d:e760]:4724
+Done
+```
+
+### server address
+
+Print the server IPv6 address.
+
+```bash
+> srp client server address
+fd00:0:0:0:d88a:618b:384d:e760
+Done
+```
+
+### server port
+
+Print the server port number
+
+```bash
+> srp client server port
+4724
+Done
+```
+
+### service
+
+Usage: `srp client service`
+
+Print the list of services.
+
+```bash
+> srp client service
+instance:"ins2", name:"_test2._udp", state:Registered, port:111, priority:1, weight:1
+instance:"ins1", name:"_test1._udp", state:Registered, port:777, priority:0, weight:0
+Done
+```
+
+### service add
+
+Usage: `srp client service add <instancename> <servicename> <port> [priority] [weight] [txt]`
+
+Add a service with a given instance name, service name, port number, priority, weight and txt values. The priority and weight are optional and if not provided zero will be used. The txt should follow hex-string format and is treated as an already encoded TXT data byte sequence. It is also optional and if not provided it is considered empty.
+
+```bash
+> srp client service add ins2 _test2._udp 111 1 1
+Done
+```
+
+### service remove
+
+Usage: `srp client service remove <instancename> <servicename>`
+
+Remove a service with a give instance name and service name.
+
+```bash
+> srp client service remove ins2 _test2._udp
+Done
+```
+
+### start
+
+Usage: `srp client start <serveraddr> <serverport>`
+
+Start the SRP client with a given server IPv6 address and port number.
+
+```bash
+> srp client start fd00::d88a:618b:384d:e760 4724
+Done
+```
+
+### state
+
+Usage: `srp client state`
+
+Indicates the state of SRP client, i.e., whether it is enabled or disabled.
+
+```bash
+> srp client state
+Enabled
+Done
+```
+
+### stop
+
+Usage: `srp client stop`
+
+Stop the SRP client.
+
+```bash
+> srp client stop
+Done
+```
diff --git a/src/cli/README_SRP_SERVER.md b/src/cli/README_SRP_SERVER.md
new file mode 100644
index 0000000..effa638
--- /dev/null
+++ b/src/cli/README_SRP_SERVER.md
@@ -0,0 +1,120 @@
+# OpenThread CLI - SRP Server
+
+## Quick Start
+
+See [README_SRP.md](README_SRP.md).
+
+## Command List
+
+- [help](#help)
+- [disable](#disable)
+- [domain](#domain)
+- [enable](#enable)
+- [host](#host)
+- [lease](#lease)
+- [service](#service)
+
+## Command Details
+
+### help
+
+Usage: `srp server help`
+
+Print SRP server help menu.
+
+```bash
+> srp server help
+disable
+domain
+enable
+help
+host
+lease
+service
+Done
+```
+
+### disable
+
+Usage: `srp server disable`
+
+Disable the SRP server.
+
+```bash
+> srp server disable
+Done
+```
+
+### domain
+
+Usage: `srp server domain [domain-name]`
+
+Get the domain.
+
+```bash
+> srp server domain
+default.service.arpa.
+Done
+```
+
+Set the domain.
+
+```bash
+> srp server domain thread.service.arpa.
+Done
+```
+
+### enable
+
+Usage: `srp server enable`
+
+Enable the SRP server.
+
+```bash
+> srp server enable
+Done
+```
+
+### host
+
+Usage: `srp server host`
+
+Print information of all registered hosts.
+
+```bash
+> srp server host
+srp-api-test-1.default.service.arpa.
+    deleted: false
+    addresses: [fdde:ad00:beef:0:0:ff:fe00:fc10]
+srp-api-test-0.default.service.arpa.
+    deleted: false
+    addresses: [fdde:ad00:beef:0:0:ff:fe00:fc10]
+Done
+```
+
+### srp server service
+
+Usage: `srp server service`
+
+Print information of all registered services.
+
+```bash
+> srp server service
+srp-api-test-1._ipps._tcp.default.service.arpa.
+    deleted: false
+    port: 49152
+    priority: 0
+    weight: 0
+    TXT: 0130
+    host: srp-api-test-1.default.service.arpa.
+    addresses: [fdde:ad00:beef:0:0:ff:fe00:fc10]
+srp-api-test-0._ipps._tcp.default.service.arpa.
+    deleted: false
+    port: 49152
+    priority: 0
+    weight: 0
+    TXT: 0130
+    host: srp-api-test-0.default.service.arpa.
+    addresses: [fdde:ad00:beef:0:0:ff:fe00:fc10]
+Done
+```
diff --git a/src/cli/README_UDP.md b/src/cli/README_UDP.md
index 08ca4fc..b473935 100644
--- a/src/cli/README_UDP.md
+++ b/src/cli/README_UDP.md
@@ -152,8 +152,10 @@
 
 - ip: the IPv6 destination address.
 - port: the UDP destination port.
-- type: the type of the message: _ `-t`: text payload in the `value`, same as without specifying the type. _ `-s`: autogenerated payload with specified length indicated in the `value`.  
-   \* `-x`: binary data in hexadecimal representation in the `value`.
+- type: the type of the message:
+  - `-t`: text payload in the `value`, same as without specifying the type.
+  - `-s`: autogenerated payload with specified length indicated in the `value`.
+  - `-x`: binary data in hexadecimal representation in the `value`.
 
 ```bash
 > udp send fdde:ad00:beef:0:bb1:ebd6:ad10:f33 1234 -t hello
@@ -182,7 +184,10 @@
 
 Send a few bytes over UDP.
 
-- type: the type of the message: _ `-t`: text payload in the `value`, same as without specifying the type. _ `-s`: autogenerated payload with specified length indicated in the `value`. \* `-x`: binary data in hexadecimal representation in the `value`.
+- type: the type of the message:
+  - `-t`: text payload in the `value`, same as without specifying the type.
+  - `-s`: autogenerated payload with specified length indicated in the `value`.
+  - `-x`: binary data in hexadecimal representation in the `value`.
 
 ```bash
 > udp send -t hello
diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp
index 658feb9..cfbc556 100644
--- a/src/cli/cli.cpp
+++ b/src/cli/cli.cpp
@@ -35,14 +35,15 @@
 
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 
 #include <openthread/diag.h>
+#include <openthread/dns.h>
 #include <openthread/icmp6.h>
 #include <openthread/link.h>
 #include <openthread/logging.h>
 #include <openthread/ncp.h>
 #include <openthread/thread.h>
-#include <openthread/platform/uart.h>
 #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
 #include <openthread/network_time.h>
 #endif
@@ -85,12 +86,9 @@
 #include "common/new.hpp"
 #include "common/string.hpp"
 #include "mac/channel_mask.hpp"
-#include "net/ip6.hpp"
-#include "utils/otns.hpp"
 #include "utils/parse_cmdline.hpp"
 
 using ot::Encoding::BigEndian::HostSwap16;
-using ot::Encoding::BigEndian::HostSwap32;
 
 using ot::Utils::CmdLineParser::ParseAsBool;
 using ot::Utils::CmdLineParser::ParseAsHexString;
@@ -108,20 +106,14 @@
 constexpr Interpreter::Command Interpreter::sCommands[];
 
 Interpreter *Interpreter::sInterpreter = nullptr;
+static OT_DEFINE_ALIGNED_VAR(sInterpreterRaw, sizeof(Interpreter), uint64_t);
 
-Interpreter::Interpreter(Instance *aInstance)
-    : mUserCommands(nullptr)
+Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, void *aContext)
+    : mInstance(aInstance)
+    , mOutputCallback(aCallback)
+    , mOutputContext(aContext)
+    , mUserCommands(nullptr)
     , mUserCommandsLength(0)
-    , mPingLength(kDefaultPingLength)
-    , mPingCount(kDefaultPingCount)
-    , mPingInterval(kDefaultPingInterval)
-    , mPingHopLimit(0)
-    , mPingAllowZeroHopLimit(false)
-    , mPingIdentifier(0)
-    , mPingTimer(*aInstance, Interpreter::HandlePingTimer, this)
-#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
-    , mResolvingInProgress(false)
-#endif
 #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
     , mSntpQueryingInProgress(false)
 #endif
@@ -140,22 +132,16 @@
 #if OPENTHREAD_CONFIG_JOINER_ENABLE
     , mJoiner(*this)
 #endif
-    , mInstance(aInstance)
-{
-#if OPENTHREAD_FTD || OPENTHREAD_CONFIG_TMF_NETWORK_DIAG_MTD_ENABLE
-    otThreadSetReceiveDiagnosticGetCallback(mInstance, &Interpreter::HandleDiagnosticGetResponse, this);
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+    , mSrpClient(*this)
 #endif
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+    , mSrpServer(*this)
+#endif
+{
 #if OPENTHREAD_FTD
     otThreadSetDiscoveryRequestCallback(mInstance, &Interpreter::HandleDiscoveryRequest, this);
 #endif
-
-    mIcmpHandler.mReceiveCallback = Interpreter::HandleIcmpReceive;
-    mIcmpHandler.mContext         = this;
-    IgnoreError(otIcmp6RegisterHandler(mInstance, &mIcmpHandler));
-
-#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
-    memset(mResolvingHostname, 0, sizeof(mResolvingHostname));
-#endif
 }
 
 void Interpreter::OutputResult(otError aError)
@@ -174,14 +160,19 @@
     }
 }
 
-void Interpreter::OutputBytes(const uint8_t *aBytes, uint8_t aLength)
+void Interpreter::OutputBytes(const uint8_t *aBytes, uint16_t aLength)
 {
-    for (int i = 0; i < aLength; i++)
+    for (uint16_t i = 0; i < aLength; i++)
     {
         OutputFormat("%02x", aBytes[i]);
     }
 }
 
+void Interpreter::OutputEnabledDisabledStatus(bool aEnabled)
+{
+    OutputLine(aEnabled ? "Enabled" : "Disabled");
+}
+
 int Interpreter::OutputIp6Address(const otIp6Address &aAddress)
 {
     return OutputFormat(
@@ -206,6 +197,8 @@
     return error;
 }
 
+#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
+
 otError Interpreter::ParsePingInterval(const char *aString, uint32_t &aInterval)
 {
     otError        error    = OT_ERROR_NONE;
@@ -252,6 +245,8 @@
     return error;
 }
 
+#endif // OPENTHREAD_CONFIG_PING_SENDER_ENABLE
+
 otError Interpreter::ProcessHelp(uint8_t aArgsLength, char *aArgs[])
 {
     OT_UNUSED_VARIABLE(aArgsLength);
@@ -270,6 +265,34 @@
     return OT_ERROR_NONE;
 }
 
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+otError Interpreter::ProcessBorderRouting(uint8_t aArgsLength, char *aArgs[])
+{
+    otError error  = OT_ERROR_NONE;
+    bool    enable = false;
+
+    VerifyOrExit(aArgsLength == 1, error = OT_ERROR_INVALID_ARGS);
+
+    if (strcmp(aArgs[0], "enable") == 0)
+    {
+        enable = true;
+    }
+    else if (strcmp(aArgs[0], "disable") == 0)
+    {
+        enable = false;
+    }
+    else
+    {
+        ExitNow(error = OT_ERROR_INVALID_COMMAND);
+    }
+
+    SuccessOrExit(error = otBorderRoutingSetEnabled(mInstance, enable));
+
+exit:
+    return error;
+}
+#endif
+
 #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
 otError Interpreter::ProcessBackboneRouter(uint8_t aArgsLength, char *aArgs[])
 {
@@ -1048,7 +1071,7 @@
 
     if (aArgsLength == 0)
     {
-        OutputLine(otPlatRadioIsCoexEnabled(mInstance) ? "Enabled" : "Disabled");
+        OutputEnabledDisabledStatus(otPlatRadioIsCoexEnabled(mInstance));
     }
     else if (strcmp(aArgs[0], "enable") == 0)
     {
@@ -1301,87 +1324,271 @@
     return error;
 }
 
-#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
-otError Interpreter::ProcessDns(uint8_t aArgsLength, char *aArgs[])
+void Interpreter::OutputDnsTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength)
 {
-    otError          error = OT_ERROR_NONE;
-    uint16_t         port  = OT_DNS_DEFAULT_SERVER_PORT;
-    Ip6::MessageInfo messageInfo;
-    otDnsQuery       query;
+    otDnsTxtEntry         entry;
+    otDnsTxtEntryIterator iterator;
+    bool                  isFirst = true;
 
-    VerifyOrExit(aArgsLength > 0, error = OT_ERROR_INVALID_ARGS);
+    otDnsInitTxtEntryIterator(&iterator, aTxtData, aTxtDataLength);
 
-    if (strcmp(aArgs[0], "resolve") == 0)
+    OutputFormat("[");
+
+    while (otDnsGetNextTxtEntry(&iterator, &entry) == OT_ERROR_NONE)
     {
-        VerifyOrExit(!mResolvingInProgress, error = OT_ERROR_BUSY);
-        VerifyOrExit(aArgsLength > 1, error = OT_ERROR_INVALID_ARGS);
-        VerifyOrExit(strlen(aArgs[1]) < OT_DNS_MAX_HOSTNAME_LENGTH, error = OT_ERROR_INVALID_ARGS);
-
-        strcpy(mResolvingHostname, aArgs[1]);
-
-        if (aArgsLength > 2)
+        if (!isFirst)
         {
-            SuccessOrExit(error = ParseAsIp6Address(aArgs[2], messageInfo.GetPeerAddr()));
+            OutputFormat(", ");
+        }
+
+        if (entry.mKey == nullptr)
+        {
+            // A null `mKey` indicates that the key in the entry is
+            // longer than the recommended max key length, so the entry
+            // could not be parsed. In this case, the whole entry is
+            // returned encoded in `mValue`.
+
+            OutputFormat("[");
+            OutputBytes(entry.mValue, entry.mValueLength);
+            OutputFormat("]");
         }
         else
         {
-            // Use IPv6 address of default DNS server.
-            SuccessOrExit(error = messageInfo.GetPeerAddr().FromString(OT_DNS_DEFAULT_SERVER_IP));
+            OutputFormat("%s", entry.mKey);
+
+            if (entry.mValue != nullptr)
+            {
+                OutputFormat("=");
+                OutputBytes(entry.mValue, entry.mValueLength);
+            }
         }
 
-        if (aArgsLength > 3)
-        {
-            SuccessOrExit(error = ParseAsUint16(aArgs[3], port));
-        }
-
-        messageInfo.SetPeerPort(port);
-
-        query.mHostname    = mResolvingHostname;
-        query.mMessageInfo = static_cast<const otMessageInfo *>(&messageInfo);
-        query.mNoRecursion = false;
-
-        SuccessOrExit(error = otDnsClientQuery(mInstance, &query, &Interpreter::HandleDnsResponse, this));
-
-        mResolvingInProgress = true;
-    }
-    else
-    {
-        ExitNow(error = OT_ERROR_INVALID_COMMAND);
+        isFirst = false;
     }
 
-    error = OT_ERROR_PENDING;
+    OutputFormat("]");
+}
+
+#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
+
+otError Interpreter::GetDnsConfig(uint8_t            aArgsLength,
+                                  char *             aArgs[],
+                                  otDnsQueryConfig *&aConfig,
+                                  uint8_t            aStartArgsIndex)
+{
+    // This method gets the optional config from given `aArgs` after the
+    // `aStartArgsIndex`. The format: `[server IPv6 address] [server
+    // port] [timeout] [max tx attempt] [recursion desired]`.
+
+    otError error = OT_ERROR_NONE;
+    bool    recursionDesired;
+
+    memset(aConfig, 0, sizeof(otDnsQueryConfig));
+
+    VerifyOrExit(aArgsLength > aStartArgsIndex, aConfig = nullptr);
+
+    SuccessOrExit(error = ParseAsIp6Address(aArgs[aStartArgsIndex], aConfig->mServerSockAddr.mAddress));
+
+    VerifyOrExit(aArgsLength > aStartArgsIndex + 1);
+    SuccessOrExit(error = ParseAsUint16(aArgs[aStartArgsIndex + 1], aConfig->mServerSockAddr.mPort));
+
+    VerifyOrExit(aArgsLength > aStartArgsIndex + 2);
+    SuccessOrExit(error = ParseAsUint32(aArgs[aStartArgsIndex + 2], aConfig->mResponseTimeout));
+
+    VerifyOrExit(aArgsLength > aStartArgsIndex + 3);
+    SuccessOrExit(error = ParseAsUint8(aArgs[aStartArgsIndex + 3], aConfig->mMaxTxAttempts));
+
+    VerifyOrExit(aArgsLength > aStartArgsIndex + 4);
+    SuccessOrExit(error = ParseAsBool(aArgs[aStartArgsIndex + 4], recursionDesired));
+    aConfig->mRecursionFlag = recursionDesired ? OT_DNS_FLAG_RECURSION_DESIRED : OT_DNS_FLAG_NO_RECURSION;
 
 exit:
     return error;
 }
 
-void Interpreter::HandleDnsResponse(void *              aContext,
-                                    const char *        aHostname,
-                                    const otIp6Address *aAddress,
-                                    uint32_t            aTtl,
-                                    otError             aResult)
+otError Interpreter::ProcessDns(uint8_t aArgsLength, char *aArgs[])
 {
-    static_cast<Interpreter *>(aContext)->HandleDnsResponse(aHostname, static_cast<const Ip6::Address *>(aAddress),
-                                                            aTtl, aResult);
-}
+    otError           error = OT_ERROR_NONE;
+    otDnsQueryConfig  queryConfig;
+    otDnsQueryConfig *config = &queryConfig;
 
-void Interpreter::HandleDnsResponse(const char *aHostname, const Ip6::Address *aAddress, uint32_t aTtl, otError aResult)
-{
-    OutputFormat("DNS response for %s - ", aHostname);
+    VerifyOrExit(aArgsLength > 0, error = OT_ERROR_INVALID_ARGS);
 
-    if (aResult == OT_ERROR_NONE)
+    if (strcmp(aArgs[0], "config") == 0)
     {
-        if (aAddress != nullptr)
+        if (aArgsLength == 1)
         {
-            OutputIp6Address(*aAddress);
+            const otDnsQueryConfig *defaultConfig = otDnsClientGetDefaultConfig(mInstance);
+
+            OutputFormat("Server: [");
+            OutputIp6Address(defaultConfig->mServerSockAddr.mAddress);
+            OutputLine("]:%d", defaultConfig->mServerSockAddr.mPort);
+            OutputLine("ResponseTimeout: %u ms", defaultConfig->mResponseTimeout);
+            OutputLine("MaxTxAttempts: %u", defaultConfig->mMaxTxAttempts);
+            OutputLine("RecursionDesired: %s",
+                       (defaultConfig->mRecursionFlag == OT_DNS_FLAG_RECURSION_DESIRED) ? "yes" : "no");
         }
-        OutputLine(" TTL: %d", aTtl);
+        else
+        {
+            SuccessOrExit(error = GetDnsConfig(aArgsLength, aArgs, config, 1));
+            otDnsClientSetDefaultConfig(mInstance, config);
+        }
+    }
+    else if (strcmp(aArgs[0], "resolve") == 0)
+    {
+        SuccessOrExit(error = GetDnsConfig(aArgsLength, aArgs, config, 2));
+        SuccessOrExit(error = otDnsClientResolveAddress(mInstance, aArgs[1], &Interpreter::HandleDnsAddressResponse,
+                                                        this, config));
+        error = OT_ERROR_PENDING;
+    }
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+    else if (strcmp(aArgs[0], "browse") == 0)
+    {
+        SuccessOrExit(error = GetDnsConfig(aArgsLength, aArgs, config, 2));
+        SuccessOrExit(error =
+                          otDnsClientBrowse(mInstance, aArgs[1], &Interpreter::HandleDnsBrowseResponse, this, config));
+        error = OT_ERROR_PENDING;
+    }
+    else if (strcmp(aArgs[0], "service") == 0)
+    {
+        SuccessOrExit(error = GetDnsConfig(aArgsLength, aArgs, config, 3));
+        SuccessOrExit(error = otDnsClientResolveService(mInstance, aArgs[1], aArgs[2],
+                                                        &Interpreter::HandleDnsServiceResponse, this, config));
+        error = OT_ERROR_PENDING;
+    }
+#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+    else
+    {
+        ExitNow(error = OT_ERROR_INVALID_COMMAND);
     }
 
-    OutputResult(aResult);
-
-    mResolvingInProgress = false;
+exit:
+    return error;
 }
+
+void Interpreter::HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse, void *aContext)
+{
+    static_cast<Interpreter *>(aContext)->HandleDnsAddressResponse(aError, aResponse);
+}
+
+void Interpreter::HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse)
+{
+    char         hostName[OT_DNS_MAX_NAME_SIZE];
+    otIp6Address address;
+    uint32_t     ttl;
+
+    IgnoreError(otDnsAddressResponseGetHostName(aResponse, hostName, sizeof(hostName)));
+
+    OutputFormat("DNS response for %s - ", hostName);
+
+    if (aError == OT_ERROR_NONE)
+    {
+        uint16_t index = 0;
+
+        while (otDnsAddressResponseGetAddress(aResponse, index, &address, &ttl) == OT_ERROR_NONE)
+        {
+            OutputIp6Address(address);
+            OutputFormat(" TTL:%u ", ttl);
+            index++;
+        }
+
+        OutputLine("");
+    }
+
+    OutputResult(aError);
+}
+
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+
+void Interpreter::OutputDnsServiceInfo(uint8_t aIndentSize, const otDnsServiceInfo &aServiceInfo)
+{
+    OutputLine(aIndentSize, "Port:%d, Priority:%d, Weight:%d, TTL:%u", aServiceInfo.mPort, aServiceInfo.mPriority,
+               aServiceInfo.mWeight, aServiceInfo.mTtl);
+    OutputLine(aIndentSize, "Host:%s", aServiceInfo.mHostNameBuffer);
+    OutputFormat(aIndentSize, "HostAddress:");
+    OutputIp6Address(aServiceInfo.mHostAddress);
+    OutputLine(" TTL:%u", aServiceInfo.mHostAddressTtl);
+    OutputFormat(aIndentSize, "TXT:");
+    OutputDnsTxtData(aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize);
+    OutputLine(" TTL:%u", aServiceInfo.mTxtDataTtl);
+}
+
+void Interpreter::HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse, void *aContext)
+{
+    static_cast<Interpreter *>(aContext)->HandleDnsBrowseResponse(aError, aResponse);
+}
+
+void Interpreter::HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse)
+{
+    char             name[OT_DNS_MAX_NAME_SIZE];
+    char             label[OT_DNS_MAX_LABEL_SIZE];
+    uint8_t          txtBuffer[255];
+    otDnsServiceInfo serviceInfo;
+
+    IgnoreError(otDnsBrowseResponseGetServiceName(aResponse, name, sizeof(name)));
+
+    OutputLine("DNS browse response for %s", name);
+
+    if (aError == OT_ERROR_NONE)
+    {
+        uint16_t index = 0;
+
+        while (otDnsBrowseResponseGetServiceInstance(aResponse, index, label, sizeof(label)) == OT_ERROR_NONE)
+        {
+            OutputLine("%s", label);
+            index++;
+
+            serviceInfo.mHostNameBuffer     = name;
+            serviceInfo.mHostNameBufferSize = sizeof(name);
+            serviceInfo.mTxtData            = txtBuffer;
+            serviceInfo.mTxtDataSize        = sizeof(txtBuffer);
+
+            if (otDnsBrowseResponseGetServiceInfo(aResponse, label, &serviceInfo) == OT_ERROR_NONE)
+            {
+                OutputDnsServiceInfo(kIndentSize, serviceInfo);
+            }
+
+            OutputLine("");
+        }
+    }
+
+    OutputResult(aError);
+}
+
+void Interpreter::HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse, void *aContext)
+{
+    static_cast<Interpreter *>(aContext)->HandleDnsServiceResponse(aError, aResponse);
+}
+
+void Interpreter::HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse)
+{
+    char             name[OT_DNS_MAX_NAME_SIZE];
+    char             label[OT_DNS_MAX_LABEL_SIZE];
+    uint8_t          txtBuffer[255];
+    otDnsServiceInfo serviceInfo;
+
+    IgnoreError(otDnsServiceResponseGetServiceName(aResponse, label, sizeof(label), name, sizeof(name)));
+
+    OutputLine("DNS service resolution response for %s for service %s", label, name);
+
+    if (aError == OT_ERROR_NONE)
+    {
+        serviceInfo.mHostNameBuffer     = name;
+        serviceInfo.mHostNameBufferSize = sizeof(name);
+        serviceInfo.mTxtData            = txtBuffer;
+        serviceInfo.mTxtDataSize        = sizeof(txtBuffer);
+
+        if (otDnsServiceResponseGetServiceInfo(aResponse, &serviceInfo) == OT_ERROR_NONE)
+        {
+            OutputDnsServiceInfo(/* aIndetSize */ 0, serviceInfo);
+            OutputLine("");
+        }
+    }
+
+    OutputResult(aError);
+}
+
+#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
 #endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
 
 #if OPENTHREAD_FTD
@@ -1447,7 +1654,7 @@
     return error;
 }
 
-#if OPENTHREAD_POSIX
+#if OPENTHREAD_POSIX && !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
 otError Interpreter::ProcessExit(uint8_t aArgsLength, char *aArgs[])
 {
     OT_UNUSED_VARIABLE(aArgsLength);
@@ -1757,14 +1964,7 @@
 
     if (aArgsLength == 0)
     {
-        if (otIp6IsMulticastPromiscuousEnabled(mInstance))
-        {
-            OutputLine("Enabled");
-        }
-        else
-        {
-            OutputLine("Disabled");
-        }
+        OutputEnabledDisabledStatus(otIp6IsMulticastPromiscuousEnabled(mInstance));
     }
     else
     {
@@ -2364,14 +2564,14 @@
 otError Interpreter::ProcessMlrReg(uint8_t aArgsLength, char *aArgs[])
 {
     otError      error = OT_ERROR_NONE;
-    otIp6Address addresses[kIPv6AddressesNumMax];
+    otIp6Address addresses[kIp6AddressesNumMax];
     uint32_t     timeout;
     uint8_t      i;
 
     VerifyOrExit(aArgsLength >= 1, error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit(aArgsLength <= kIPv6AddressesNumMax + 1, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aArgsLength <= kIp6AddressesNumMax + 1, error = OT_ERROR_INVALID_ARGS);
 
-    for (i = 0; i < aArgsLength && i < kIPv6AddressesNumMax; i++)
+    for (i = 0; i < aArgsLength && i < kIp6AddressesNumMax; i++)
     {
         if (ParseAsIp6Address(aArgs[i], addresses[i]) != OT_ERROR_NONE)
         {
@@ -2495,6 +2695,94 @@
     return error;
 }
 
+otError Interpreter::ProcessMultiRadio(uint8_t aArgsLength, char *aArgs[])
+{
+    otError error = OT_ERROR_NONE;
+
+    OT_UNUSED_VARIABLE(aArgs);
+
+    if (aArgsLength == 0)
+    {
+        bool isFirst = true;
+
+        OutputFormat("[");
+#if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
+        OutputFormat("15.4");
+        isFirst = false;
+#endif
+#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
+        OutputFormat("%sTREL", isFirst ? "" : ", ");
+#endif
+        OutputLine("]");
+
+        OT_UNUSED_VARIABLE(isFirst);
+    }
+#if OPENTHREAD_CONFIG_MULTI_RADIO
+    else if (strcmp(aArgs[0], "neighbor") == 0)
+    {
+        otMultiRadioNeighborInfo multiRadioInfo;
+
+        VerifyOrExit(aArgsLength == 2, error = OT_ERROR_INVALID_ARGS);
+
+        if (strcmp(aArgs[1], "list") == 0)
+        {
+            otNeighborInfoIterator iterator = OT_NEIGHBOR_INFO_ITERATOR_INIT;
+            otNeighborInfo         neighInfo;
+
+            while (otThreadGetNextNeighborInfo(mInstance, &iterator, &neighInfo) == OT_ERROR_NONE)
+            {
+                if (otMultiRadioGetNeighborInfo(mInstance, &neighInfo.mExtAddress, &multiRadioInfo) != OT_ERROR_NONE)
+                {
+                    continue;
+                }
+
+                OutputFormat("ExtAddr:");
+                OutputExtAddress(neighInfo.mExtAddress);
+                OutputFormat(", RLOC16:0x%04x, Radios:", neighInfo.mRloc16);
+                OutputMultiRadioInfo(multiRadioInfo);
+            }
+        }
+        else
+        {
+            otExtAddress extAddress;
+
+            SuccessOrExit(error = ParseAsHexString(aArgs[1], extAddress.m8));
+            SuccessOrExit(error = otMultiRadioGetNeighborInfo(mInstance, &extAddress, &multiRadioInfo));
+            OutputMultiRadioInfo(multiRadioInfo);
+        }
+    }
+#endif // OPENTHREAD_CONFIG_MULTI_RADIO
+    else
+    {
+        ExitNow(error = OT_ERROR_INVALID_COMMAND);
+    }
+
+exit:
+    return error;
+}
+
+#if OPENTHREAD_CONFIG_MULTI_RADIO
+void Interpreter::OutputMultiRadioInfo(const otMultiRadioNeighborInfo &aMultiRadioInfo)
+{
+    bool isFirst = true;
+
+    OutputFormat("[");
+
+    if (aMultiRadioInfo.mSupportsIeee802154)
+    {
+        OutputFormat("15.4(%d)", aMultiRadioInfo.mIeee802154Info.mPreference);
+        isFirst = false;
+    }
+
+    if (aMultiRadioInfo.mSupportsTrelUdp6)
+    {
+        OutputFormat("%sTREL(%d)", isFirst ? "" : ", ", aMultiRadioInfo.mTrelUdp6Info.mPreference);
+    }
+
+    OutputLine("]");
+}
+#endif // OPENTHREAD_CONFIG_MULTI_RADIO
+
 #if OPENTHREAD_FTD
 otError Interpreter::ProcessNeighbor(uint8_t aArgsLength, char *aArgs[])
 {
@@ -2853,153 +3141,70 @@
 }
 #endif
 
-void Interpreter::HandleIcmpReceive(void *               aContext,
-                                    otMessage *          aMessage,
-                                    const otMessageInfo *aMessageInfo,
-                                    const otIcmp6Header *aIcmpHeader)
+#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
+
+void Interpreter::HandlePingReply(const otPingSenderReply *aReply, void *aContext)
 {
-    static_cast<Interpreter *>(aContext)->HandleIcmpReceive(aMessage, aMessageInfo, aIcmpHeader);
+    static_cast<Interpreter *>(aContext)->HandlePingReply(aReply);
 }
 
-void Interpreter::HandleIcmpReceive(otMessage *          aMessage,
-                                    const otMessageInfo *aMessageInfo,
-                                    const otIcmp6Header *aIcmpHeader)
+void Interpreter::HandlePingReply(const otPingSenderReply *aReply)
 {
-    uint32_t timestamp = 0;
-    uint16_t dataSize;
-
-    VerifyOrExit(aIcmpHeader->mType == OT_ICMP6_TYPE_ECHO_REPLY);
-    VerifyOrExit((mPingIdentifier != 0) && (mPingIdentifier == HostSwap16(aIcmpHeader->mData.m16[0])));
-
-    dataSize = otMessageGetLength(aMessage) - otMessageGetOffset(aMessage);
-    OutputFormat("%u bytes from ", dataSize + static_cast<uint16_t>(sizeof(otIcmp6Header)));
-
-    OutputIp6Address(aMessageInfo->mPeerAddr);
-
-    OutputFormat(": icmp_seq=%d hlim=%d", HostSwap16(aIcmpHeader->mData.m16[1]), aMessageInfo->mHopLimit);
-
-    if (otMessageRead(aMessage, otMessageGetOffset(aMessage), &timestamp, sizeof(uint32_t)) == sizeof(uint32_t))
-    {
-        OutputFormat(" time=%dms", TimerMilli::GetNow().GetValue() - HostSwap32(timestamp));
-    }
-
-    OutputLine("");
-
-    SignalPingReply(static_cast<const Ip6::MessageInfo *>(aMessageInfo)->GetPeerAddr(), dataSize, HostSwap32(timestamp),
-                    aMessageInfo->mHopLimit);
-
-exit:
-    return;
+    OutputFormat("%u bytes from ", static_cast<uint16_t>(aReply->mSize + sizeof(otIcmp6Header)));
+    OutputIp6Address(aReply->mSenderAddress);
+    OutputLine(": icmp_seq=%d hlim=%d time=%dms", aReply->mSequenceNumber, aReply->mHopLimit, aReply->mRoundTripTime);
 }
 
 otError Interpreter::ProcessPing(uint8_t aArgsLength, char *aArgs[])
 {
-    otError  error = OT_ERROR_NONE;
-    uint8_t  index = 1;
-    uint32_t interval;
+    otError            error = OT_ERROR_NONE;
+    otPingSenderConfig config;
 
     VerifyOrExit(aArgsLength > 0, error = OT_ERROR_INVALID_ARGS);
 
     if (strcmp(aArgs[0], "stop") == 0)
     {
-        mPingIdentifier = 0;
-        VerifyOrExit(mPingTimer.IsRunning(), error = OT_ERROR_INVALID_STATE);
-        mPingTimer.Stop();
+        otPingSenderStop(mInstance);
         ExitNow();
     }
 
-    VerifyOrExit(!mPingTimer.IsRunning(), error = OT_ERROR_BUSY);
+    memset(&config, 0, sizeof(config));
 
-    SuccessOrExit(error = ParseAsIp6Address(aArgs[0], mPingDestAddress));
+    SuccessOrExit(error = ParseAsIp6Address(aArgs[0], config.mDestination));
 
-    mPingLength            = kDefaultPingLength;
-    mPingCount             = kDefaultPingCount;
-    mPingInterval          = kDefaultPingInterval;
-    mPingHopLimit          = 0;
-    mPingAllowZeroHopLimit = false;
-
-    while (index < aArgsLength)
+    if (aArgsLength > 1)
     {
-        switch (index)
-        {
-        case 1:
-            SuccessOrExit(error = ParseAsUint16(aArgs[index], mPingLength));
-            break;
-
-        case 2:
-            SuccessOrExit(error = ParseAsUint16(aArgs[index], mPingCount));
-            break;
-
-        case 3:
-            SuccessOrExit(error = ParsePingInterval(aArgs[index], interval));
-            VerifyOrExit(0 < interval && interval <= Timer::kMaxDelay, error = OT_ERROR_INVALID_ARGS);
-            mPingInterval = interval;
-            break;
-
-        case 4:
-            SuccessOrExit(error = ParseAsUint8(aArgs[index], mPingHopLimit));
-            mPingAllowZeroHopLimit = (mPingHopLimit == 0);
-            break;
-
-        default:
-            ExitNow(error = OT_ERROR_INVALID_ARGS);
-        }
-
-        index++;
+        SuccessOrExit(error = ParseAsUint16(aArgs[1], config.mSize));
     }
 
-    mPingIdentifier++;
-
-    if (mPingIdentifier == 0)
+    if (aArgsLength > 2)
     {
-        mPingIdentifier++;
+        SuccessOrExit(error = ParseAsUint16(aArgs[2], config.mCount));
     }
 
-    SendPing();
+    if (aArgsLength > 3)
+    {
+        SuccessOrExit(error = ParsePingInterval(aArgs[3], config.mInterval));
+    }
+
+    if (aArgsLength > 4)
+    {
+        SuccessOrExit(error = ParseAsUint8(aArgs[4], config.mHopLimit));
+        config.mAllowZeroHopLimit = (config.mHopLimit == 0);
+    }
+
+    VerifyOrExit(aArgsLength <= 5, error = OT_ERROR_INVALID_ARGS);
+
+    config.mCallback        = Interpreter::HandlePingReply;
+    config.mCallbackContext = this;
+
+    error = otPingSenderPing(mInstance, &config);
 
 exit:
     return error;
 }
 
-void Interpreter::HandlePingTimer(Timer &aTimer)
-{
-    GetOwner(aTimer).SendPing();
-}
-
-void Interpreter::SendPing(void)
-{
-    uint32_t      timestamp = HostSwap32(TimerMilli::GetNow().GetValue());
-    otMessage *   message   = nullptr;
-    otMessageInfo messageInfo;
-
-    memset(&messageInfo, 0, sizeof(messageInfo));
-    messageInfo.mPeerAddr          = mPingDestAddress;
-    messageInfo.mHopLimit          = mPingHopLimit;
-    messageInfo.mAllowZeroHopLimit = mPingAllowZeroHopLimit;
-
-    message = otIp6NewMessage(mInstance, nullptr);
-    VerifyOrExit(message != nullptr);
-
-    SuccessOrExit(otMessageAppend(message, &timestamp, sizeof(timestamp)));
-    SuccessOrExit(otMessageSetLength(message, mPingLength));
-    SuccessOrExit(otIcmp6SendEchoRequest(mInstance, message, &messageInfo, mPingIdentifier));
-
-    SignalPingRequest(static_cast<Ip6::MessageInfo *>(&messageInfo)->GetPeerAddr(), mPingLength, HostSwap32(timestamp),
-                      messageInfo.mHopLimit);
-
-    message = nullptr;
-
-exit:
-    if (message != nullptr)
-    {
-        otMessageFree(message);
-    }
-
-    if (--mPingCount)
-    {
-        mPingTimer.Start(mPingInterval);
-    }
-}
+#endif // OPENTHREAD_CONFIG_PING_SENDER_ENABLE
 
 otError Interpreter::ProcessPollPeriod(uint8_t aArgsLength, char *aArgs[])
 {
@@ -3027,14 +3232,7 @@
 
     if (aArgsLength == 0)
     {
-        if (otLinkIsPromiscuous(mInstance) && otPlatRadioGetPromiscuous(mInstance))
-        {
-            OutputLine("Enabled");
-        }
-        else
-        {
-            OutputLine("Disabled");
-        }
+        OutputEnabledDisabledStatus(otLinkIsPromiscuous(mInstance) && otPlatRadioGetPromiscuous(mInstance));
     }
     else
     {
@@ -3316,6 +3514,29 @@
     return error;
 }
 
+otError Interpreter::ProcessRegion(uint8_t aArgsLength, char *aArgs[])
+{
+    otError  error = OT_ERROR_NONE;
+    uint16_t regionCode;
+
+    if (aArgsLength == 0)
+    {
+        SuccessOrExit(error = otPlatRadioGetRegion(mInstance, &regionCode));
+        OutputLine("%c%c", regionCode >> 8, regionCode & 0xff);
+    }
+    else
+    {
+        VerifyOrExit(strlen(aArgs[0]) == 2, error = OT_ERROR_INVALID_ARGS);
+
+        regionCode =
+            static_cast<uint16_t>(static_cast<uint16_t>(aArgs[0][0]) << 8) + static_cast<uint16_t>(aArgs[0][1]);
+        error = otPlatRadioSetRegion(mInstance, regionCode);
+    }
+
+exit:
+    return error;
+}
+
 #if OPENTHREAD_FTD
 otError Interpreter::ProcessReleaseRouterId(uint8_t aArgsLength, char *aArgs[])
 {
@@ -3468,8 +3689,8 @@
 
         if (isTable)
         {
-            OutputLine("| ID | RLOC16 | Next Hop | Path Cost | LQ In | LQ Out | Age | Extended MAC     |");
-            OutputLine("+----+--------+----------+-----------+-------+--------+-----+------------------+");
+            OutputLine("| ID | RLOC16 | Next Hop | Path Cost | LQ In | LQ Out | Age | Extended MAC     | Link |");
+            OutputLine("+----+--------+----------+-----------+-------+--------+-----+------------------+------+");
         }
 
         maxRouterId = otThreadGetMaxRouterId(mInstance);
@@ -3492,7 +3713,7 @@
                 OutputFormat("| %3d ", routerInfo.mAge);
                 OutputFormat("| ");
                 OutputExtAddress(routerInfo.mExtAddress);
-                OutputLine(" |");
+                OutputLine(" | %4d |", routerInfo.mLinkEstablished);
             }
             else
             {
@@ -3558,14 +3779,7 @@
 
     if (aArgsLength == 0)
     {
-        if (otThreadIsRouterEligible(mInstance))
-        {
-            OutputLine("Enabled");
-        }
-        else
-        {
-            OutputLine("Disabled");
-        }
+        OutputEnabledDisabledStatus(otThreadIsRouterEligible(mInstance));
     }
     else if (strcmp(aArgs[0], "enable") == 0)
     {
@@ -3817,6 +4031,42 @@
 }
 #endif // OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
 
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE || OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+otError Interpreter::ProcessSrp(uint8_t aArgsLength, char *aArgs[])
+{
+    otError error = OT_ERROR_NONE;
+
+    if (aArgsLength == 0)
+    {
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+        OutputLine("client");
+#endif
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+        OutputLine("server");
+#endif
+        ExitNow();
+    }
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+    if (strcmp(aArgs[0], "client") == 0)
+    {
+        ExitNow(error = mSrpClient.Process(aArgsLength - 1, aArgs + 1));
+    }
+#endif
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+    if (strcmp(aArgs[0], "server") == 0)
+    {
+        ExitNow(error = mSrpServer.Process(aArgsLength - 1, aArgs + 1));
+    }
+#endif
+
+    error = OT_ERROR_INVALID_COMMAND;
+
+exit:
+    return error;
+}
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE || OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+
 otError Interpreter::ProcessState(uint8_t aArgsLength, char *aArgs[])
 {
     otError error = OT_ERROR_NONE;
@@ -4436,23 +4686,23 @@
     output[sizeof(output) - 1] = '\0';
 
     error = otDiagProcessCmd(mInstance, aArgsLength, aArgs, output, sizeof(output) - 1);
-    Output(output, static_cast<uint16_t>(strlen(output)));
+    OutputFormat("%s", output);
 
     return error;
 }
 #endif
 
-void Interpreter::ProcessLine(char *aBuf, uint16_t aBufLength)
+void Interpreter::ProcessLine(char *aBuf)
 {
     char *         args[kMaxArgs] = {nullptr};
     uint8_t        argsLength;
     const Command *command;
 
-    VerifyOrExit(aBuf != nullptr && StringLength(aBuf, aBufLength + 1) <= aBufLength);
+    VerifyOrExit(aBuf != nullptr && StringLength(aBuf, kMaxLineLength) <= kMaxLineLength - 1);
 
     VerifyOrExit(Utils::CmdLineParser::ParseCmd(aBuf, argsLength, args, kMaxArgs) == OT_ERROR_NONE,
                  OutputLine("Error: too many args (max %d)", kMaxArgs));
-    VerifyOrExit(argsLength >= 1, OutputLine("Error: no given command."));
+    VerifyOrExit(argsLength >= 1);
 
 #if OPENTHREAD_CONFIG_DIAG_ENABLE
     VerifyOrExit((!otDiagIsEnabled(mInstance) || (strcmp(args[0], "diag") == 0)),
@@ -4506,7 +4756,8 @@
 
     if (strcmp(aArgs[0], "get") == 0)
     {
-        IgnoreError(otThreadSendDiagnosticGet(mInstance, &address, tlvTypes, count));
+        SuccessOrExit(error = otThreadSendDiagnosticGet(mInstance, &address, tlvTypes, count,
+                                                        &Interpreter::HandleDiagnosticGetResponse, this));
         ExitNow(error = OT_ERROR_PENDING);
     }
     else if (strcmp(aArgs[0], "reset") == 0)
@@ -4727,48 +4978,6 @@
     mUserCommandsContext = aContext;
 }
 
-Interpreter &Interpreter::GetOwner(OwnerLocator &aOwnerLocator)
-{
-#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
-    Interpreter &interpreter = (aOwnerLocator.GetOwner<Interpreter>());
-#else
-    OT_UNUSED_VARIABLE(aOwnerLocator);
-
-    Interpreter &interpreter = Interpreter::GetInterpreter();
-#endif
-    return interpreter;
-}
-
-void Interpreter::SignalPingRequest(const Ip6::Address &aPeerAddress,
-                                    uint16_t            aPingLength,
-                                    uint32_t            aTimestamp,
-                                    uint8_t             aHopLimit)
-{
-    OT_UNUSED_VARIABLE(aPeerAddress);
-    OT_UNUSED_VARIABLE(aPingLength);
-    OT_UNUSED_VARIABLE(aTimestamp);
-    OT_UNUSED_VARIABLE(aHopLimit);
-
-#if OPENTHREAD_CONFIG_OTNS_ENABLE
-    mInstance->Get<Utils::Otns>().EmitPingRequest(aPeerAddress, aPingLength, aTimestamp, aHopLimit);
-#endif
-}
-
-void Interpreter::SignalPingReply(const Ip6::Address &aPeerAddress,
-                                  uint16_t            aPingLength,
-                                  uint32_t            aTimestamp,
-                                  uint8_t             aHopLimit)
-{
-    OT_UNUSED_VARIABLE(aPeerAddress);
-    OT_UNUSED_VARIABLE(aPingLength);
-    OT_UNUSED_VARIABLE(aTimestamp);
-    OT_UNUSED_VARIABLE(aHopLimit);
-
-#if OPENTHREAD_CONFIG_OTNS_ENABLE
-    mInstance->Get<Utils::Otns>().EmitPingReply(aPeerAddress, aPingLength, aTimestamp, aHopLimit);
-#endif
-}
-
 void Interpreter::HandleDiscoveryRequest(const otThreadDiscoveryRequestInfo &aInfo)
 {
     OutputFormat("~ Discovery Request from ");
@@ -4825,24 +5034,33 @@
 
 void Interpreter::OutputSpaces(uint8_t aCount)
 {
-    static const char kSpaces[] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '};
+    char format[sizeof("%256s")];
 
-    while (aCount > 0)
-    {
-        uint8_t len = OT_MIN(aCount, sizeof(kSpaces));
+    snprintf(format, sizeof(format), "%%%us", aCount);
 
-        Output(kSpaces, len);
-        aCount -= len;
-    }
+    OutputFormat(format, "");
 }
 
 int Interpreter::OutputFormatV(const char *aFormat, va_list aArguments)
 {
-    char buf[kMaxLineLength];
+    return mOutputCallback(mOutputContext, aFormat, aArguments);
+}
 
-    vsnprintf(buf, sizeof(buf), aFormat, aArguments);
+void Interpreter::Initialize(otInstance *aInstance, otCliOutputCallback aCallback, void *aContext)
+{
+    Instance *instance = static_cast<Instance *>(aInstance);
 
-    return Output(buf, static_cast<uint16_t>(strlen(buf)));
+    Interpreter::sInterpreter = new (&sInterpreterRaw) Interpreter(instance, aCallback, aContext);
+}
+
+extern "C" void otCliInit(otInstance *aInstance, otCliOutputCallback aCallback, void *aContext)
+{
+    Interpreter::Initialize(aInstance, aCallback, aContext);
+}
+
+extern "C" void otCliInputLine(char *aBuf)
+{
+    Interpreter::GetInterpreter().ProcessLine(aBuf);
 }
 
 extern "C" void otCliSetUserCommands(const otCliCommand *aUserCommands, uint8_t aLength, void *aContext)
@@ -4863,11 +5081,6 @@
     va_end(aAp);
 }
 
-extern "C" void otCliOutput(const char *aString, uint16_t aLength)
-{
-    Interpreter::GetInterpreter().Output(aString, aLength);
-}
-
 extern "C" void otCliAppendResult(otError aError)
 {
     Interpreter::GetInterpreter().OutputResult(aError);
diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp
index 35c7912..a632be8 100644
--- a/src/cli/cli.hpp
+++ b/src/cli/cli.hpp
@@ -41,15 +41,22 @@
 #include <stdarg.h>
 
 #include <openthread/cli.h>
-#include <openthread/dns.h>
+#include <openthread/dataset.h>
+#include <openthread/dns_client.h>
+#include <openthread/instance.h>
 #include <openthread/ip6.h>
+#include <openthread/link.h>
 #include <openthread/sntp.h>
+#include <openthread/thread.h>
+#include <openthread/thread_ftd.h>
 #include <openthread/udp.h>
 
 #include "cli/cli_commissioner.hpp"
 #include "cli/cli_dataset.hpp"
 #include "cli/cli_joiner.hpp"
 #include "cli/cli_network_data.hpp"
+#include "cli/cli_srp_client.hpp"
+#include "cli/cli_srp_server.hpp"
 #include "cli/cli_udp.hpp"
 #if OPENTHREAD_CONFIG_COAP_API_ENABLE
 #include "cli/cli_coap.hpp"
@@ -57,10 +64,10 @@
 #if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE
 #include "cli/cli_coap_secure.hpp"
 #endif
+
 #include "common/code_utils.hpp"
+#include "common/debug.hpp"
 #include "common/instance.hpp"
-#include "common/timer.hpp"
-#include "net/icmp6.hpp"
 #include "utils/lookup_table.hpp"
 
 namespace ot {
@@ -86,15 +93,19 @@
     friend class Dataset;
     friend class Joiner;
     friend class NetworkData;
+    friend class SrpClient;
+    friend class SrpServer;
     friend class UdpExample;
 
 public:
     /**
      * Constructor
      *
-     * @param[in]  aInstance  The OpenThread instance structure.
+     * @param[in]  aInstance    The OpenThread instance structure.
+     * @param[in]  aCallback    A callback method called to process CLI output.
+     * @param[in]  aContext     A user context pointer.
      */
-    explicit Interpreter(Instance *aInstance);
+    explicit Interpreter(Instance *aInstance, otCliOutputCallback aCallback, void *aContext);
 
     /**
      * This method returns a reference to the interpreter object.
@@ -110,6 +121,16 @@
     }
 
     /**
+     * This method initializes the Console interpreter.
+     *
+     * @param[in]  aInstance  The OpenThread instance structure.
+     * @param[in]  aCallback  A pointer to a callback method.
+     * @param[in]  aContext   A pointer to a user context.
+     *
+     */
+    static void Initialize(otInstance *aInstance, otCliOutputCallback aCallback, void *aContext);
+
+    /**
      * This method returns whether the interpreter is initialized.
      *
      * @returns  Whether the interpreter is initialized.
@@ -121,10 +142,9 @@
      * This method interprets a CLI command.
      *
      * @param[in]  aBuf        A pointer to a string.
-     * @param[in]  aBufLength  The length of the string in bytes.
      *
      */
-    void ProcessLine(char *aBuf, uint16_t aBufLength);
+    void ProcessLine(char *aBuf);
 
     /**
      * This method delivers raw characters to the client.
@@ -146,7 +166,7 @@
      * @param[in]  aLength  @p aBytes length.
      *
      */
-    void OutputBytes(const uint8_t *aBytes, uint8_t aLength);
+    void OutputBytes(const uint8_t *aBytes, uint16_t aLength);
 
     /**
      * This method writes a number of bytes to the CLI console as a hex string.
@@ -255,6 +275,14 @@
     void OutputResult(otError aError);
 
     /**
+     * This method delivers "Enabled" or "Disabled" status to the CLI client (it also appends newline `\r\n`).
+     *
+     * @param[in] aEnabled  A boolean indicating the status. TRUE outputs "Enabled", FALSE outputs "Disabled".
+     *
+     */
+    void OutputEnabledDisabledStatus(bool aEnabled);
+
+    /**
      * This method sets the user command table.
      *
      * @param[in]  aUserCommands  A pointer to an array with user commands.
@@ -273,12 +301,7 @@
         kIndentSize       = 4,
         kMaxArgs          = 32,
         kMaxAutoAddresses = 8,
-
-        kDefaultPingInterval = 1000, // (in mses)
-        kDefaultPingLength   = 8,    // (in bytes)
-        kDefaultPingCount    = 1,
-
-        kMaxLineLength = OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH,
+        kMaxLineLength    = OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH,
     };
 
     struct Command
@@ -287,13 +310,18 @@
         otError (Interpreter::*mHandler)(uint8_t aArgsLength, char *aArgs[]);
     };
 
-    otError        ParsePingInterval(const char *aString, uint32_t &aInterval);
+#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
+    otError ParsePingInterval(const char *aString, uint32_t &aInterval);
+#endif
     static otError ParseJoinerDiscerner(char *aString, otJoinerDiscerner &aDiscerner);
 
     otError ProcessHelp(uint8_t aArgsLength, char *aArgs[]);
     otError ProcessCcaThreshold(uint8_t aArgsLength, char *aArgs[]);
     otError ProcessBufferInfo(uint8_t aArgsLength, char *aArgs[]);
     otError ProcessChannel(uint8_t aArgsLength, char *aArgs[]);
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+    otError ProcessBorderRouting(uint8_t aArgsLength, char *aArgs[]);
+#endif
 #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
     otError ProcessBackboneRouter(uint8_t aArgsLength, char *aArgs[]);
 
@@ -353,7 +381,7 @@
     otError ProcessEidCache(uint8_t aArgsLength, char *aArgs[]);
 #endif
     otError ProcessEui64(uint8_t aArgsLength, char *aArgs[]);
-#if OPENTHREAD_POSIX
+#if OPENTHREAD_POSIX && !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
     otError ProcessExit(uint8_t aArgsLength, char *aArgs[]);
 #endif
     otError ProcessLog(uint8_t aArgsLength, char *aArgs[]);
@@ -409,6 +437,10 @@
                                    uint8_t             aFailedAddressNum);
 #endif
     otError ProcessMode(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessMultiRadio(uint8_t aArgsLength, char *aArgsp[]);
+#if OPENTHREAD_CONFIG_MULTI_RADIO
+    void OutputMultiRadioInfo(const otMultiRadioNeighborInfo &aMultiRadioInfo);
+#endif
 #if OPENTHREAD_FTD
     otError ProcessNeighbor(uint8_t aArgsLength, char *aArgs[]);
 #endif
@@ -444,17 +476,10 @@
 #if OPENTHREAD_FTD
     otError ProcessParentPriority(uint8_t aArgsLength, char *aArgs[]);
 #endif
+#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
     otError ProcessPing(uint8_t aArgsLength, char *aArgs[]);
+#endif
     otError ProcessPollPeriod(uint8_t aArgsLength, char *aArgs[]);
-    void    SignalPingRequest(const Ip6::Address &aPeerAddress,
-                              uint16_t            aPingLength,
-                              uint32_t            aTimestamp,
-                              uint8_t             aHopLimit);
-    void    SignalPingReply(const Ip6::Address &aPeerAddress,
-                            uint16_t            aPingLength,
-                            uint32_t            aTimestamp,
-                            uint8_t             aHopLimit);
-
 #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
     otError ProcessPrefix(uint8_t aArgsLength, char *aArgs[]);
     otError ProcessPrefixAdd(uint8_t aArgsLength, char *aArgs[]);
@@ -467,6 +492,7 @@
     otError ProcessPskc(uint8_t aArgsLength, char *aArgs[]);
 #endif
     otError ProcessRcp(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessRegion(uint8_t aArgsLength, char *aArgs[]);
 #if OPENTHREAD_FTD
     otError ProcessReleaseRouterId(uint8_t aArgsLength, char *aArgs[]);
 #endif
@@ -490,6 +516,9 @@
 #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
     otError ProcessSntp(uint8_t aArgsLength, char *aArgs[]);
 #endif
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE || OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+    otError ProcessSrp(uint8_t aArgsLength, char *aArgs[]);
+#endif
     otError ProcessState(uint8_t aArgsLength, char *aArgs[]);
     otError ProcessThread(uint8_t aArgsLength, char *aArgs[]);
     otError ProcessDataset(uint8_t aArgsLength, char *aArgs[]);
@@ -509,11 +538,9 @@
     otError ProcessMacSend(uint8_t aArgsLength, char *aArgs[]);
 #endif
 
-    static void HandleIcmpReceive(void *               aContext,
-                                  otMessage *          aMessage,
-                                  const otMessageInfo *aMessageInfo,
-                                  const otIcmp6Header *aIcmpHeader);
-    static void HandlePingTimer(Timer &aTimer);
+#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
+    static void HandlePingReply(const otPingSenderReply *aReply, void *aContext);
+#endif
     static void HandleActiveScanResult(otActiveScanResult *aResult, void *aContext);
     static void HandleEnergyScanResult(otEnergyScanResult *aResult, void *aContext);
     static void HandleLinkPcapReceive(const otRadioFrame *aFrame, bool aIsTx, void *aContext);
@@ -534,26 +561,31 @@
     void OutputChildTableEntry(uint8_t aIndentSize, const otNetworkDiagChildEntry &aChildEntry);
 #endif
 
+    void OutputDnsTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength);
+
 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
-    static void HandleDnsResponse(void *              aContext,
-                                  const char *        aHostname,
-                                  const otIp6Address *aAddress,
-                                  uint32_t            aTtl,
-                                  otError             aResult);
+    otError     GetDnsConfig(uint8_t aArgsLength, char *aArgs[], otDnsQueryConfig *&aConfig, uint8_t aStartArgsIndex);
+    static void HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse, void *aContext);
+    void        HandleDnsAddressResponse(otError aError, const otDnsAddressResponse *aResponse);
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+    void        OutputDnsServiceInfo(uint8_t aIndentSize, const otDnsServiceInfo &aServiceInfo);
+    static void HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse, void *aContext);
+    void        HandleDnsBrowseResponse(otError aError, const otDnsBrowseResponse *aResponse);
+    static void HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse, void *aContext);
+    void        HandleDnsServiceResponse(otError aError, const otDnsServiceResponse *aResponse);
+#endif
 #endif
 
 #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
     static void HandleSntpResponse(void *aContext, uint64_t aTime, otError aResult);
 #endif
 
-    void HandleIcmpReceive(otMessage *aMessage, const otMessageInfo *aMessageInfo, const otIcmp6Header *aIcmpHeader);
-    void SendPing(void);
+#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
+    void HandlePingReply(const otPingSenderReply *aReply);
+#endif
     void HandleActiveScanResult(otActiveScanResult *aResult);
     void HandleEnergyScanResult(otEnergyScanResult *aResult);
     void HandleLinkPcapReceive(const otRadioFrame *aFrame, bool aIsTx);
-#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
-    void HandleDnsResponse(const char *aHostname, const Ip6::Address *aAddress, uint32_t aTtl, otError aResult);
-#endif
 #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
     void HandleSntpResponse(uint64_t aTime, otError aResult);
 #endif
@@ -585,8 +617,6 @@
     const char *LinkMetricsStatusToStr(uint8_t aStatus);
 #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
 
-    static Interpreter &GetOwner(OwnerLocator &aOwnerLocator);
-
     static void HandleDiscoveryRequest(const otThreadDiscoveryRequestInfo *aInfo, void *aContext)
     {
         static_cast<Interpreter *>(aContext)->HandleDiscoveryRequest(*aInfo);
@@ -597,6 +627,9 @@
 #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
         {"bbr", &Interpreter::ProcessBackboneRouter},
 #endif
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+        {"br", &Interpreter::ProcessBorderRouting},
+#endif
         {"bufferinfo", &Interpreter::ProcessBufferInfo},
         {"ccathreshold", &Interpreter::ProcessCcaThreshold},
         {"channel", &Interpreter::ProcessChannel},
@@ -649,7 +682,7 @@
         {"eidcache", &Interpreter::ProcessEidCache},
 #endif
         {"eui64", &Interpreter::ProcessEui64},
-#if OPENTHREAD_POSIX
+#if OPENTHREAD_POSIX && !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
         {"exit", &Interpreter::ProcessExit},
 #endif
         {"extaddr", &Interpreter::ProcessExtAddress},
@@ -687,6 +720,7 @@
         {"mlr", &Interpreter::ProcessMlr},
 #endif
         {"mode", &Interpreter::ProcessMode},
+        {"multiradio", &Interpreter::ProcessMultiRadio},
 #if OPENTHREAD_FTD
         {"neighbor", &Interpreter::ProcessNeighbor},
 #endif
@@ -711,7 +745,9 @@
         {"parentpriority", &Interpreter::ProcessParentPriority},
         {"partitionid", &Interpreter::ProcessPartitionId},
 #endif
+#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
         {"ping", &Interpreter::ProcessPing},
+#endif
         {"pollperiod", &Interpreter::ProcessPollPeriod},
 #if OPENTHREAD_FTD
         {"preferrouterid", &Interpreter::ProcessPreferRouterId},
@@ -724,6 +760,7 @@
         {"pskc", &Interpreter::ProcessPskc},
 #endif
         {"rcp", &Interpreter::ProcessRcp},
+        {"region", &Interpreter::ProcessRegion},
 #if OPENTHREAD_FTD
         {"releaserouterid", &Interpreter::ProcessReleaseRouterId},
 #endif
@@ -747,6 +784,9 @@
 #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
         {"sntp", &Interpreter::ProcessSntp},
 #endif
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE || OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+        {"srp", &Interpreter::ProcessSrp},
+#endif
         {"state", &Interpreter::ProcessState},
         {"thread", &Interpreter::ProcessThread},
         {"txpower", &Interpreter::ProcessTxPower},
@@ -757,23 +797,12 @@
 
     static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted");
 
+    Instance *          mInstance;
+    otCliOutputCallback mOutputCallback;
+    void *              mOutputContext;
     const otCliCommand *mUserCommands;
     uint8_t             mUserCommandsLength;
     void *              mUserCommandsContext;
-    uint16_t            mPingLength;
-    uint16_t            mPingCount;
-    uint32_t            mPingInterval;
-    uint8_t             mPingHopLimit;
-    bool                mPingAllowZeroHopLimit;
-    uint16_t            mPingIdentifier;
-    otIp6Address        mPingDestAddress;
-    TimerMilli          mPingTimer;
-    otIcmp6Handler      mIcmpHandler;
-#if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
-    bool mResolvingInProgress;
-    char mResolvingHostname[OT_DNS_MAX_HOSTNAME_LENGTH];
-#endif
-
 #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
     bool mSntpQueryingInProgress;
 #endif
@@ -798,7 +827,13 @@
     Joiner mJoiner;
 #endif
 
-    Instance *mInstance;
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+    SrpClient mSrpClient;
+#endif
+
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+    SrpServer mSrpServer;
+#endif
 };
 
 } // namespace Cli
diff --git a/src/cli/cli_coap.cpp b/src/cli/cli_coap.cpp
index d106175..867d280 100644
--- a/src/cli/cli_coap.cpp
+++ b/src/cli/cli_coap.cpp
@@ -35,6 +35,8 @@
 
 #if OPENTHREAD_CONFIG_COAP_API_ENABLE
 
+#include <openthread/random_noncrypto.h>
+
 #include <ctype.h>
 
 #include "cli/cli.hpp"
@@ -60,6 +62,9 @@
     , mSubscriberTokenLength(0)
     , mSubscriberConfirmableNotifications(false)
 #endif
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    , mBlockCount(1)
+#endif
 {
     memset(&mResource, 0, sizeof(mResource));
 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
@@ -180,8 +185,22 @@
         mResource.mContext = this;
         mResource.mHandler = &Coap::HandleRequest;
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        mResource.mReceiveHook  = &Coap::BlockwiseReceiveHook;
+        mResource.mTransmitHook = &Coap::BlockwiseTransmitHook;
+
+        if (aArgsLength > 2)
+        {
+            SuccessOrExit(error = ParseAsUint32(aArgs[2], mBlockCount));
+        }
+#endif
+
         strncpy(mUriPath, aArgs[1], sizeof(mUriPath) - 1);
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        otCoapAddBlockWiseResource(mInterpreter.mInstance, &mResource);
+#else
         otCoapAddResource(mInterpreter.mInstance, &mResource);
+#endif
     }
     else
     {
@@ -267,7 +286,11 @@
     OT_UNUSED_VARIABLE(aArgsLength);
     OT_UNUSED_VARIABLE(aArgs);
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    otCoapRemoveBlockWiseResource(mInterpreter.mInstance, &mResource);
+#else
     otCoapRemoveResource(mInterpreter.mInstance, &mResource);
+#endif
 
     return otCoapStop(mInterpreter.mInstance);
 }
@@ -348,6 +371,11 @@
 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
     bool coapObserve = false;
 #endif
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    bool                         coapBlock     = false;
+    otCoapBlockSzx               coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_16;
+    ot::Coap::Message::BlockType coapBlockType = ot::Coap::Message::kBlockType1;
+#endif
 
     VerifyOrExit(aArgsLength > 0, error = OT_ERROR_INVALID_ARGS);
 
@@ -355,6 +383,9 @@
     if (strcmp(aArgs[0], "get") == 0)
     {
         coapCode = OT_COAP_CODE_GET;
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        coapBlockType = ot::Coap::Message::kBlockType2;
+#endif
     }
 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
     else if (strcmp(aArgs[0], "observe") == 0)
@@ -367,10 +398,16 @@
     else if (strcmp(aArgs[0], "post") == 0)
     {
         coapCode = OT_COAP_CODE_POST;
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        coapBlockType = ot::Coap::Message::kBlockType1;
+#endif
     }
     else if (strcmp(aArgs[0], "put") == 0)
     {
         coapCode = OT_COAP_CODE_PUT;
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        coapBlockType = ot::Coap::Message::kBlockType1;
+#endif
     }
     else if (strcmp(aArgs[0], "delete") == 0)
     {
@@ -409,6 +446,50 @@
         {
             coapType = OT_COAP_TYPE_CONFIRMABLE;
         }
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        else if (strcmp(aArgs[3], "block-16") == 0)
+        {
+            coapType      = OT_COAP_TYPE_CONFIRMABLE;
+            coapBlock     = true;
+            coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_16;
+        }
+        else if (strcmp(aArgs[3], "block-32") == 0)
+        {
+            coapType      = OT_COAP_TYPE_CONFIRMABLE;
+            coapBlock     = true;
+            coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_32;
+        }
+        else if (strcmp(aArgs[3], "block-64") == 0)
+        {
+            coapType      = OT_COAP_TYPE_CONFIRMABLE;
+            coapBlock     = true;
+            coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_64;
+        }
+        else if (strcmp(aArgs[3], "block-128") == 0)
+        {
+            coapType      = OT_COAP_TYPE_CONFIRMABLE;
+            coapBlock     = true;
+            coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_128;
+        }
+        else if (strcmp(aArgs[3], "block-256") == 0)
+        {
+            coapType      = OT_COAP_TYPE_CONFIRMABLE;
+            coapBlock     = true;
+            coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_256;
+        }
+        else if (strcmp(aArgs[3], "block-512") == 0)
+        {
+            coapType      = OT_COAP_TYPE_CONFIRMABLE;
+            coapBlock     = true;
+            coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_512;
+        }
+        else if (strcmp(aArgs[3], "block-1024") == 0)
+        {
+            coapType      = OT_COAP_TYPE_CONFIRMABLE;
+            coapBlock     = true;
+            coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_1024;
+        }
+#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
     }
 
 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
@@ -434,14 +515,39 @@
 
     SuccessOrExit(error = otCoapMessageAppendUriPathOptions(message, coapUri));
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    if (coapBlock)
+    {
+        if (coapBlockType == ot::Coap::Message::kBlockType1)
+        {
+            SuccessOrExit(error = otCoapMessageAppendBlock1Option(message, 0, true, coapBlockSize));
+        }
+        else
+        {
+            SuccessOrExit(error = otCoapMessageAppendBlock2Option(message, 0, false, coapBlockSize));
+        }
+    }
+#endif
+
     if (aArgsLength > 4)
     {
-        payloadLength = static_cast<uint16_t>(strlen(aArgs[4]));
-
-        if (payloadLength > 0)
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        if (coapBlock)
         {
-            SuccessOrExit(error = otCoapMessageSetPayloadMarker(message));
+            SuccessOrExit(error = ParseAsUint32(aArgs[4], mBlockCount));
         }
+        else
+        {
+#endif
+            payloadLength = static_cast<uint16_t>(strlen(aArgs[4]));
+
+            if (payloadLength > 0)
+            {
+                SuccessOrExit(error = otCoapMessageSetPayloadMarker(message));
+            }
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        }
+#endif
     }
 
     // Embed content into message if given
@@ -468,8 +574,25 @@
 
     if ((coapType == OT_COAP_TYPE_CONFIRMABLE) || (coapCode == OT_COAP_CODE_GET))
     {
-        error = otCoapSendRequestWithParameters(mInterpreter.mInstance, message, &messageInfo, &Coap::HandleResponse,
-                                                this, GetRequestTxParameters());
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        if (coapBlock)
+        {
+            if (coapCode == OT_COAP_CODE_PUT || coapCode == OT_COAP_CODE_POST)
+            {
+                SuccessOrExit(error = otCoapMessageSetPayloadMarker(message));
+            }
+            error = otCoapSendRequestBlockWiseWithParameters(mInterpreter.mInstance, message, &messageInfo,
+                                                             &Coap::HandleResponse, this, GetRequestTxParameters(),
+                                                             Coap::BlockwiseTransmitHook, Coap::BlockwiseReceiveHook);
+        }
+        else
+        {
+#endif
+            error = otCoapSendRequestWithParameters(mInterpreter.mInstance, message, &messageInfo,
+                                                    &Coap::HandleResponse, this, GetRequestTxParameters());
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        }
+#endif
     }
     else
     {
@@ -514,8 +637,14 @@
     otMessage *responseMessage = nullptr;
     otCoapCode responseCode    = OT_COAP_CODE_EMPTY;
 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
-    uint64_t             observe        = 0;
-    bool                 observePresent = false;
+    uint64_t observe        = 0;
+    bool     observePresent = false;
+#endif
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    uint64_t blockValue   = 0;
+    bool     blockPresent = false;
+#endif
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE || OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
     otCoapOptionIterator iterator;
 #endif
 
@@ -527,9 +656,11 @@
     {
     case OT_COAP_CODE_GET:
         mInterpreter.OutputFormat("GET");
-#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE || OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
         SuccessOrExit(error = otCoapOptionIteratorInit(&iterator, aMessage));
-        if (otCoapOptionIteratorGetFirstOptionMatching(&iterator, OT_COAP_OPTION_OBSERVE) != nullptr)
+#endif
+#if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
+        if (otCoapOptionIteratorGetFirstOptionMatching(&iterator, OT_COAP_OPTION_OBSERVE) != NULL)
         {
             SuccessOrExit(error = otCoapOptionIteratorGetOptionUintValue(&iterator, &observe));
             observePresent = true;
@@ -537,6 +668,13 @@
             mInterpreter.OutputFormat(" OBS=%lu", static_cast<uint32_t>(observe));
         }
 #endif
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        if (otCoapOptionIteratorGetFirstOptionMatching(&iterator, OT_COAP_OPTION_BLOCK2) != nullptr)
+        {
+            SuccessOrExit(error = otCoapOptionIteratorGetOptionUintValue(&iterator, &blockValue));
+            blockPresent = true;
+        }
+#endif
         break;
 
     case OT_COAP_CODE_DELETE:
@@ -626,13 +764,40 @@
                 SuccessOrExit(error = otCoapMessageAppendObserveOption(responseMessage, mObserveSerial++));
             }
 #endif
-            SuccessOrExit(error = otCoapMessageSetPayloadMarker(responseMessage));
-            SuccessOrExit(error = otMessageAppend(responseMessage, mResourceContent,
-                                                  static_cast<uint16_t>(strlen(mResourceContent))));
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+            if (blockPresent)
+            {
+                SuccessOrExit(error = otCoapMessageAppendBlock2Option(responseMessage,
+                                                                      static_cast<uint32_t>(blockValue >> 4), true,
+                                                                      static_cast<otCoapBlockSzx>(blockValue & 0x7)));
+                SuccessOrExit(error = otCoapMessageSetPayloadMarker(responseMessage));
+            }
+            else
+            {
+#endif
+                SuccessOrExit(error = otCoapMessageSetPayloadMarker(responseMessage));
+                SuccessOrExit(error = otMessageAppend(responseMessage, mResourceContent,
+                                                      static_cast<uint16_t>(strlen(mResourceContent))));
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+            }
+#endif
         }
 
-        SuccessOrExit(error = otCoapSendResponseWithParameters(mInterpreter.mInstance, responseMessage, aMessageInfo,
-                                                               GetResponseTxParameters()));
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        if (blockPresent)
+        {
+            SuccessOrExit(error = otCoapSendResponseBlockWiseWithParameters(mInterpreter.mInstance, responseMessage,
+                                                                            aMessageInfo, GetResponseTxParameters(),
+                                                                            this, mResource.mTransmitHook));
+        }
+        else
+        {
+#endif
+            SuccessOrExit(error = otCoapSendResponseWithParameters(mInterpreter.mInstance, responseMessage,
+                                                                   aMessageInfo, GetResponseTxParameters()));
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        }
+#endif
     }
 
 exit:
@@ -726,6 +891,77 @@
     }
 }
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+otError Coap::BlockwiseReceiveHook(void *         aContext,
+                                   const uint8_t *aBlock,
+                                   uint32_t       aPosition,
+                                   uint16_t       aBlockLength,
+                                   bool           aMore,
+                                   uint32_t       aTotalLength)
+{
+    return static_cast<Coap *>(aContext)->BlockwiseReceiveHook(aBlock, aPosition, aBlockLength, aMore, aTotalLength);
+}
+
+otError Coap::BlockwiseReceiveHook(const uint8_t *aBlock,
+                                   uint32_t       aPosition,
+                                   uint16_t       aBlockLength,
+                                   bool           aMore,
+                                   uint32_t       aTotalLength)
+{
+    OT_UNUSED_VARIABLE(aMore);
+    OT_UNUSED_VARIABLE(aTotalLength);
+
+    mInterpreter.OutputLine("received block: Num %i Len %i", aPosition / aBlockLength, aBlockLength);
+
+    for (uint16_t i = 0; i < aBlockLength / 16; i++)
+    {
+        mInterpreter.OutputBytes(&aBlock[i * 16], 16);
+        mInterpreter.OutputLine("");
+    }
+
+    return OT_ERROR_NONE;
+}
+
+otError Coap::BlockwiseTransmitHook(void *    aContext,
+                                    uint8_t * aBlock,
+                                    uint32_t  aPosition,
+                                    uint16_t *aBlockLength,
+                                    bool *    aMore)
+{
+    return static_cast<Coap *>(aContext)->BlockwiseTransmitHook(aBlock, aPosition, aBlockLength, aMore);
+}
+
+otError Coap::BlockwiseTransmitHook(uint8_t *aBlock, uint32_t aPosition, uint16_t *aBlockLength, bool *aMore)
+{
+    static uint32_t blockCount = 0;
+    OT_UNUSED_VARIABLE(aPosition);
+
+    // Send a random payload
+    otRandomNonCryptoFillBuffer(aBlock, *aBlockLength);
+
+    mInterpreter.OutputLine("send block: Num %i Len %i", blockCount, *aBlockLength);
+
+    for (uint16_t i = 0; i < *aBlockLength / 16; i++)
+    {
+        mInterpreter.OutputBytes(&aBlock[i * 16], 16);
+        mInterpreter.OutputLine("");
+    }
+
+    if (blockCount == mBlockCount - 1)
+    {
+        blockCount = 0;
+        *aMore     = false;
+    }
+    else
+    {
+        *aMore = true;
+        blockCount++;
+    }
+
+    return OT_ERROR_NONE;
+}
+#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+
 } // namespace Cli
 } // namespace ot
 
diff --git a/src/cli/cli_coap.hpp b/src/cli/cli_coap.hpp
index 0325781..586386a 100644
--- a/src/cli/cli_coap.hpp
+++ b/src/cli/cli_coap.hpp
@@ -115,6 +115,27 @@
     static void HandleResponse(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo, otError aError);
     void        HandleResponse(otMessage *aMessage, const otMessageInfo *aMessageInfo, otError aError);
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+
+    static otError BlockwiseReceiveHook(void *         aContext,
+                                        const uint8_t *aBlock,
+                                        uint32_t       aPosition,
+                                        uint16_t       aBlockLength,
+                                        bool           aMore,
+                                        uint32_t       aTotalLength);
+    otError        BlockwiseReceiveHook(const uint8_t *aBlock,
+                                        uint32_t       aPosition,
+                                        uint16_t       aBlockLength,
+                                        bool           aMore,
+                                        uint32_t       aTotalLength);
+    static otError BlockwiseTransmitHook(void *    aContext,
+                                         uint8_t * aBlock,
+                                         uint32_t  aPosition,
+                                         uint16_t *aBlockLength,
+                                         bool *    aMore);
+    otError        BlockwiseTransmitHook(uint8_t *aBlock, uint32_t aPosition, uint16_t *aBlockLength, bool *aMore);
+#endif
+
     const otCoapTxParameters *GetRequestTxParameters(void) const
     {
         return mUseDefaultRequestTxParameters ? nullptr : &mRequestTxParameters;
@@ -154,7 +175,11 @@
     otCoapTxParameters mRequestTxParameters;
     otCoapTxParameters mResponseTxParameters;
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    otCoapBlockwiseResource mResource;
+#else
     otCoapResource mResource;
+#endif
 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
     otIp6Address mRequestAddr;
     otSockAddr   mSubscriberSock;
@@ -170,6 +195,9 @@
     uint8_t  mSubscriberTokenLength;
     bool     mSubscriberConfirmableNotifications;
 #endif
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    uint32_t mBlockCount;
+#endif
 };
 
 } // namespace Cli
diff --git a/src/cli/cli_coap_secure.cpp b/src/cli/cli_coap_secure.cpp
index 2f1b586..880c66d 100644
--- a/src/cli/cli_coap_secure.cpp
+++ b/src/cli/cli_coap_secure.cpp
@@ -37,6 +37,7 @@
 
 #include <mbedtls/debug.h>
 #include <openthread/ip6.h>
+#include <openthread/random_noncrypto.h>
 
 #include "cli/cli.hpp"
 #include "utils/parse_cmdline.hpp"
@@ -46,6 +47,7 @@
 
 using ot::Utils::CmdLineParser::ParseAsIp6Address;
 using ot::Utils::CmdLineParser::ParseAsUint16;
+using ot::Utils::CmdLineParser::ParseAsUint32;
 
 namespace ot {
 namespace Cli {
@@ -58,6 +60,9 @@
     , mUseCertificate(false)
     , mPskLength(0)
     , mPskIdLength(0)
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    , mBlockCount(1)
+#endif
 {
     memset(&mResource, 0, sizeof(mResource));
     memset(&mPsk, 0, sizeof(mPsk));
@@ -117,8 +122,22 @@
         mResource.mContext = this;
         mResource.mHandler = &CoapSecure::HandleRequest;
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        mResource.mReceiveHook  = &CoapSecure::BlockwiseReceiveHook;
+        mResource.mTransmitHook = &CoapSecure::BlockwiseTransmitHook;
+
+        if (aArgsLength > 2)
+        {
+            SuccessOrExit(error = ParseAsUint32(aArgs[2], mBlockCount));
+        }
+#endif
+
         strncpy(mUriPath, aArgs[1], sizeof(mUriPath) - 1);
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        otCoapSecureAddBlockWiseResource(mInterpreter.mInstance, &mResource);
+#else
         otCoapSecureAddResource(mInterpreter.mInstance, &mResource);
+#endif
     }
     else
     {
@@ -183,7 +202,11 @@
     OT_UNUSED_VARIABLE(aArgsLength);
     OT_UNUSED_VARIABLE(aArgs);
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    otCoapRemoveBlockWiseResource(mInterpreter.mInstance, &mResource);
+#else
     otCoapRemoveResource(mInterpreter.mInstance, &mResource);
+#endif
 
     if (otCoapSecureIsConnectionActive(mInterpreter.mInstance))
     {
@@ -211,6 +234,11 @@
     otCoapType   coapType               = OT_COAP_TYPE_NON_CONFIRMABLE;
     otCoapCode   coapCode               = OT_COAP_CODE_GET;
     otIp6Address coapDestinationIp;
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    bool                         coapBlock     = false;
+    otCoapBlockSzx               coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_16;
+    ot::Coap::Message::BlockType coapBlockType = ot::Coap::Message::kBlockType1;
+#endif
 
     VerifyOrExit(aArgsLength > 0, error = OT_ERROR_INVALID_ARGS);
 
@@ -218,14 +246,23 @@
     if (strcmp(aArgs[0], "get") == 0)
     {
         coapCode = OT_COAP_CODE_GET;
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        coapBlockType = ot::Coap::Message::kBlockType2;
+#endif
     }
     else if (strcmp(aArgs[0], "post") == 0)
     {
         coapCode = OT_COAP_CODE_POST;
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        coapBlockType = ot::Coap::Message::kBlockType1;
+#endif
     }
     else if (strcmp(aArgs[0], "put") == 0)
     {
         coapCode = OT_COAP_CODE_PUT;
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        coapBlockType = ot::Coap::Message::kBlockType1;
+#endif
     }
     else if (strcmp(aArgs[0], "delete") == 0)
     {
@@ -270,6 +307,50 @@
         {
             coapType = OT_COAP_TYPE_CONFIRMABLE;
         }
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        else if (strcmp(aArgs[3 - indexShifter], "block-16") == 0)
+        {
+            coapType      = OT_COAP_TYPE_CONFIRMABLE;
+            coapBlock     = true;
+            coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_16;
+        }
+        else if (strcmp(aArgs[3 - indexShifter], "block-32") == 0)
+        {
+            coapType      = OT_COAP_TYPE_CONFIRMABLE;
+            coapBlock     = true;
+            coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_32;
+        }
+        else if (strcmp(aArgs[3 - indexShifter], "block-64") == 0)
+        {
+            coapType      = OT_COAP_TYPE_CONFIRMABLE;
+            coapBlock     = true;
+            coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_64;
+        }
+        else if (strcmp(aArgs[3 - indexShifter], "block-128") == 0)
+        {
+            coapType      = OT_COAP_TYPE_CONFIRMABLE;
+            coapBlock     = true;
+            coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_128;
+        }
+        else if (strcmp(aArgs[3 - indexShifter], "block-256") == 0)
+        {
+            coapType      = OT_COAP_TYPE_CONFIRMABLE;
+            coapBlock     = true;
+            coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_256;
+        }
+        else if (strcmp(aArgs[3 - indexShifter], "block-512") == 0)
+        {
+            coapType      = OT_COAP_TYPE_CONFIRMABLE;
+            coapBlock     = true;
+            coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_512;
+        }
+        else if (strcmp(aArgs[3 - indexShifter], "block-1024") == 0)
+        {
+            coapType      = OT_COAP_TYPE_CONFIRMABLE;
+            coapBlock     = true;
+            coapBlockSize = OT_COAP_OPTION_BLOCK_SZX_1024;
+        }
+#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
     }
 
     message = otCoapNewMessage(mInterpreter.mInstance, nullptr);
@@ -279,14 +360,39 @@
     otCoapMessageGenerateToken(message, OT_COAP_DEFAULT_TOKEN_LENGTH);
     SuccessOrExit(error = otCoapMessageAppendUriPathOptions(message, coapUri));
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    if (coapBlock)
+    {
+        if (coapBlockType == ot::Coap::Message::kBlockType1)
+        {
+            SuccessOrExit(error = otCoapMessageAppendBlock1Option(message, 0, true, coapBlockSize));
+        }
+        else
+        {
+            SuccessOrExit(error = otCoapMessageAppendBlock2Option(message, 0, false, coapBlockSize));
+        }
+    }
+#endif
+
     if (aArgsLength > (4 - indexShifter))
     {
-        payloadLength = static_cast<uint16_t>(strlen(aArgs[4 - indexShifter]));
-
-        if (payloadLength > 0)
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        if (coapBlock)
         {
-            SuccessOrExit(error = otCoapMessageSetPayloadMarker(message));
+            SuccessOrExit(error = ParseAsUint32(aArgs[4 - indexShifter], mBlockCount));
         }
+        else
+        {
+#endif
+            payloadLength = static_cast<uint16_t>(strlen(aArgs[4 - indexShifter]));
+
+            if (payloadLength > 0)
+            {
+                SuccessOrExit(error = otCoapMessageSetPayloadMarker(message));
+            }
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        }
+#endif
     }
 
     // add payload
@@ -301,7 +407,20 @@
 
     if ((coapType == OT_COAP_TYPE_CONFIRMABLE) || (coapCode == OT_COAP_CODE_GET))
     {
-        error = otCoapSecureSendRequest(mInterpreter.mInstance, message, &CoapSecure::HandleResponse, this);
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        if (coapBlock)
+        {
+            error =
+                otCoapSecureSendRequestBlockWise(mInterpreter.mInstance, message, &CoapSecure::HandleResponse, this,
+                                                 &CoapSecure::BlockwiseTransmitHook, &CoapSecure::BlockwiseReceiveHook);
+        }
+        else
+        {
+#endif
+            error = otCoapSecureSendRequest(mInterpreter.mInstance, message, &CoapSecure::HandleResponse, this);
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        }
+#endif
     }
     else
     {
@@ -415,7 +534,11 @@
 
 void CoapSecure::Stop(void)
 {
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    otCoapRemoveBlockWiseResource(mInterpreter.mInstance, &mResource);
+#else
     otCoapRemoveResource(mInterpreter.mInstance, &mResource);
+#endif
     otCoapSecureStop(mInterpreter.mInstance);
 }
 
@@ -452,6 +575,11 @@
     otError    error           = OT_ERROR_NONE;
     otMessage *responseMessage = nullptr;
     otCoapCode responseCode    = OT_COAP_CODE_EMPTY;
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    uint64_t             blockValue   = 0;
+    bool                 blockPresent = false;
+    otCoapOptionIterator iterator;
+#endif
 
     mInterpreter.OutputFormat("coaps request from ");
     mInterpreter.OutputIp6Address(aMessageInfo->mPeerAddr);
@@ -461,6 +589,14 @@
     {
     case OT_COAP_CODE_GET:
         mInterpreter.OutputFormat("GET");
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        SuccessOrExit(error = otCoapOptionIteratorInit(&iterator, aMessage));
+        if (otCoapOptionIteratorGetFirstOptionMatching(&iterator, OT_COAP_OPTION_BLOCK2) != nullptr)
+        {
+            SuccessOrExit(error = otCoapOptionIteratorGetOptionUintValue(&iterator, &blockValue));
+            blockPresent = true;
+        }
+#endif
         break;
 
     case OT_COAP_CODE_DELETE:
@@ -502,16 +638,39 @@
 
         if (otCoapMessageGetCode(aMessage) == OT_COAP_CODE_GET)
         {
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+            if (blockPresent)
+            {
+                SuccessOrExit(error = otCoapMessageAppendBlock2Option(responseMessage,
+                                                                      static_cast<uint32_t>(blockValue >> 4), true,
+                                                                      static_cast<otCoapBlockSzx>(blockValue & 0x7)));
+            }
+#endif
             SuccessOrExit(error = otCoapMessageSetPayloadMarker(responseMessage));
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+            if (!blockPresent)
+            {
+#endif
+                SuccessOrExit(error = otMessageAppend(responseMessage, &mResourceContent,
+                                                      static_cast<uint16_t>(strlen(mResourceContent))));
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+            }
+#endif
         }
 
-        if (otCoapMessageGetCode(aMessage) == OT_COAP_CODE_GET)
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        if (blockPresent)
         {
-            SuccessOrExit(error = otMessageAppend(responseMessage, &mResourceContent,
-                                                  static_cast<uint16_t>(strlen(mResourceContent))));
+            SuccessOrExit(error = otCoapSecureSendResponseBlockWise(mInterpreter.mInstance, responseMessage,
+                                                                    aMessageInfo, this, mResource.mTransmitHook));
         }
-
-        SuccessOrExit(error = otCoapSecureSendResponse(mInterpreter.mInstance, responseMessage, aMessageInfo));
+        else
+        {
+#endif
+            SuccessOrExit(error = otCoapSecureSendResponse(mInterpreter.mInstance, responseMessage, aMessageInfo));
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        }
+#endif
     }
 
 exit:
@@ -583,6 +742,78 @@
 }
 #endif // CLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+otError CoapSecure::BlockwiseReceiveHook(void *         aContext,
+                                         const uint8_t *aBlock,
+                                         uint32_t       aPosition,
+                                         uint16_t       aBlockLength,
+                                         bool           aMore,
+                                         uint32_t       aTotalLength)
+{
+    return static_cast<CoapSecure *>(aContext)->BlockwiseReceiveHook(aBlock, aPosition, aBlockLength, aMore,
+                                                                     aTotalLength);
+}
+
+otError CoapSecure::BlockwiseReceiveHook(const uint8_t *aBlock,
+                                         uint32_t       aPosition,
+                                         uint16_t       aBlockLength,
+                                         bool           aMore,
+                                         uint32_t       aTotalLength)
+{
+    OT_UNUSED_VARIABLE(aMore);
+    OT_UNUSED_VARIABLE(aTotalLength);
+
+    mInterpreter.OutputLine("received block: Num %i Len %i", aPosition / aBlockLength, aBlockLength);
+
+    for (uint16_t i = 0; i < aBlockLength / 16; i++)
+    {
+        mInterpreter.OutputBytes(&aBlock[i * 16], 16);
+        mInterpreter.OutputLine("");
+    }
+
+    return OT_ERROR_NONE;
+}
+
+otError CoapSecure::BlockwiseTransmitHook(void *    aContext,
+                                          uint8_t * aBlock,
+                                          uint32_t  aPosition,
+                                          uint16_t *aBlockLength,
+                                          bool *    aMore)
+{
+    return static_cast<CoapSecure *>(aContext)->BlockwiseTransmitHook(aBlock, aPosition, aBlockLength, aMore);
+}
+
+otError CoapSecure::BlockwiseTransmitHook(uint8_t *aBlock, uint32_t aPosition, uint16_t *aBlockLength, bool *aMore)
+{
+    static uint32_t blockCount = 0;
+    OT_UNUSED_VARIABLE(aPosition);
+
+    // Send a random payload
+    otRandomNonCryptoFillBuffer(aBlock, *aBlockLength);
+
+    mInterpreter.OutputLine("send block: Num %i Len %i", blockCount, *aBlockLength);
+
+    for (uint16_t i = 0; i < *aBlockLength / 16; i++)
+    {
+        mInterpreter.OutputBytes(&aBlock[i * 16], 16);
+        mInterpreter.OutputLine("");
+    }
+
+    if (blockCount == mBlockCount - 1)
+    {
+        blockCount = 0;
+        *aMore     = false;
+    }
+    else
+    {
+        *aMore = true;
+        blockCount++;
+    }
+
+    return OT_ERROR_NONE;
+}
+#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+
 } // namespace Cli
 } // namespace ot
 
diff --git a/src/cli/cli_coap_secure.hpp b/src/cli/cli_coap_secure.hpp
index 18f0d1f..fa52c17 100644
--- a/src/cli/cli_coap_secure.hpp
+++ b/src/cli/cli_coap_secure.hpp
@@ -111,6 +111,27 @@
     static void HandleResponse(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo, otError aError);
     void        HandleResponse(otMessage *aMessage, const otMessageInfo *aMessageInfo, otError aError);
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+
+    static otError BlockwiseReceiveHook(void *         aContext,
+                                        const uint8_t *aBlock,
+                                        uint32_t       aPosition,
+                                        uint16_t       aBlockLength,
+                                        bool           aMore,
+                                        uint32_t       aTotalLength);
+    otError        BlockwiseReceiveHook(const uint8_t *aBlock,
+                                        uint32_t       aPosition,
+                                        uint16_t       aBlockLength,
+                                        bool           aMore,
+                                        uint32_t       aTotalLength);
+    static otError BlockwiseTransmitHook(void *    aContext,
+                                         uint8_t * aBlock,
+                                         uint32_t  aPosition,
+                                         uint16_t *aBlockLength,
+                                         bool *    aMore);
+    otError        BlockwiseTransmitHook(uint8_t *aBlock, uint32_t aPosition, uint16_t *aBlockLength, bool *aMore);
+#endif
+
 #if CLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER
     static void DefaultHandler(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
     void        DefaultHandler(otMessage *aMessage, const otMessageInfo *aMessageInfo);
@@ -143,9 +164,13 @@
 
     Interpreter &mInterpreter;
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    otCoapBlockwiseResource mResource;
+#else
     otCoapResource mResource;
-    char           mUriPath[kMaxUriLength];
-    char           mResourceContent[kMaxBufferSize];
+#endif
+    char mUriPath[kMaxUriLength];
+    char mResourceContent[kMaxBufferSize];
 
     bool    mShutdownFlag;
     bool    mUseCertificate;
@@ -153,6 +178,9 @@
     uint8_t mPskLength;
     uint8_t mPskId[kPskIdMaxLength];
     uint8_t mPskIdLength;
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    uint32_t mBlockCount;
+#endif
 };
 
 } // namespace Cli
diff --git a/src/cli/cli_config.h b/src/cli/cli_config.h
index 3dc2512..7c8fda0 100644
--- a/src/cli/cli_config.h
+++ b/src/cli/cli_config.h
@@ -37,6 +37,15 @@
 
 #include "openthread-core-config.h"
 
+#ifndef OPENTHREAD_POSIX
+#if defined(__ANDROID__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__linux__) || defined(__NetBSD__) || \
+    defined(__unix__)
+#define OPENTHREAD_POSIX 1
+#else
+#define OPENTHREAD_POSIX 0
+#endif
+#endif
+
 /**
  * @def OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH
  *
@@ -44,59 +53,31 @@
  *
  */
 #ifndef OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH
-#define OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH 128
+#define OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH 384
 #endif
 
 /**
- * @def OPENTHREAD_CONFIG_CLI_UART_RX_BUFFER_SIZE
+ * @def OPENTHREAD_CONFIG_CLI_SRP_CLIENT_MAX_SERVICES
  *
- * The size of CLI UART RX buffer in bytes.
+ * The maximum number of service entries supported by SRP client.
+ *
+ * This is only applicable when SRP client is enabled, i.e. OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE is set.
  *
  */
-#ifndef OPENTHREAD_CONFIG_CLI_UART_RX_BUFFER_SIZE
-#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
-#define OPENTHREAD_CONFIG_CLI_UART_RX_BUFFER_SIZE 640
-#else
-#define OPENTHREAD_CONFIG_CLI_UART_RX_BUFFER_SIZE 512
-#endif
+#ifndef OPENTHREAD_CONFIG_CLI_SRP_CLIENT_MAX_SERVICES
+#define OPENTHREAD_CONFIG_CLI_SRP_CLIENT_MAX_SERVICES 2
 #endif
 
 /**
- * @def OPENTHREAD_CONFIG_CLI_TX_BUFFER_SIZE
+ * @def OPENTHREAD_CONFIG_CLI_SRP_CLIENT_MAX_HOST_ADDRESSES
  *
- * The size of CLI message buffer in bytes.
+ * The maximum number of host IPv6 address entries supported by SRP client.
+ *
+ * This is only applicable when SRP client is enabled, i.e. OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE is set.
  *
  */
-#ifndef OPENTHREAD_CONFIG_CLI_UART_TX_BUFFER_SIZE
-#define OPENTHREAD_CONFIG_CLI_UART_TX_BUFFER_SIZE 1024
-#endif
-
-/**
- * @def OPENTHREAD_CONFIG_UART_CLI_RAW
- *
- * TODO: complete.
- *
- */
-#ifndef OPENTHREAD_CONFIG_UART_CLI_RAW
-#define OPENTHREAD_CONFIG_UART_CLI_RAW 0
-#endif
-
-#define OT_CLI_TRANSPORT_UART (1)
-#define OT_CLI_TRANSPORT_CONSOLE (2)
-
-/**
- * @def OPENTHREAD_CONFIG_CLI_TRANSPORT
- *
- * The transport of the CLI.
- *
- */
-#ifndef OPENTHREAD_CONFIG_CLI_TRANSPORT
-#define OPENTHREAD_CONFIG_CLI_TRANSPORT OT_CLI_TRANSPORT_UART
-#endif
-
-#if OPENTHREAD_CONFIG_CLI_TRANSPORT != OT_CLI_TRANSPORT_UART && \
-    OPENTHREAD_CONFIG_CLI_TRANSPORT != OT_CLI_TRANSPORT_CONSOLE
-#error "Unsupported CLI transport!"
+#ifndef OPENTHREAD_CONFIG_CLI_SRP_CLIENT_MAX_HOST_ADDRESSES
+#define OPENTHREAD_CONFIG_CLI_SRP_CLIENT_MAX_HOST_ADDRESSES 2
 #endif
 
 #endif // CONFIG_CLI_H_
diff --git a/src/cli/cli_console.cpp b/src/cli/cli_console.cpp
deleted file mode 100644
index 55ef6bf..0000000
--- a/src/cli/cli_console.cpp
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- *  Copyright (c) 2016, The OpenThread Authors.
- *  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
- *   This file implements the CLI interpreter on the CONSOLE service.
- */
-
-#include "cli_console.hpp"
-
-#if OPENTHREAD_CONFIG_CLI_TRANSPORT == OT_CLI_TRANSPORT_CONSOLE
-
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "cli/cli.hpp"
-#include "common/instance.hpp"
-#include "common/new.hpp"
-
-namespace ot {
-namespace Cli {
-
-static OT_DEFINE_ALIGNED_VAR(sCliConsoleRaw, sizeof(Console), uint64_t);
-
-extern "C" void otCliConsoleInit(otInstance *aInstance, otCliConsoleOutputCallback aCallback, void *aContext)
-{
-    Console::Initialize(aInstance, aCallback, aContext);
-}
-
-extern "C" void otCliConsoleInputLine(char *aBuf, uint16_t aBufLength)
-{
-    Interpreter::GetInterpreter().ProcessLine(aBuf, aBufLength);
-}
-
-// Add stubs for simulation
-extern "C" void otPlatUartReceived(const uint8_t *aBuf, uint16_t aBufLength)
-{
-    OT_UNUSED_VARIABLE(aBuf);
-    OT_UNUSED_VARIABLE(aBufLength);
-}
-
-extern "C" void otPlatUartSendDone(void)
-{
-}
-
-void Console::Initialize(otInstance *aInstance, otCliConsoleOutputCallback aCallback, void *aContext)
-{
-    Instance *instance = static_cast<Instance *>(aInstance);
-
-    Interpreter::sInterpreter = new (&sCliConsoleRaw) Console(instance, aCallback, aContext);
-}
-
-Console::Console(Instance *aInstance, otCliConsoleOutputCallback aCallback, void *aContext)
-    : Interpreter(aInstance)
-    , mCallback(aCallback)
-    , mContext(aContext)
-{
-}
-
-int Interpreter::Output(const char *aBuf, uint16_t aBufLength)
-{
-    return static_cast<Console *>(this)->Output(aBuf, aBufLength);
-}
-
-int Console::Output(const char *aBuf, uint16_t aBufLength)
-{
-    return mCallback(aBuf, aBufLength, mContext);
-}
-
-} // namespace Cli
-} // namespace ot
-
-#endif // OPENTHREAD_CONFIG_CLI_TRANSPORT == OT_CLI_TRANSPORT_CONSOLE
diff --git a/src/cli/cli_console.hpp b/src/cli/cli_console.hpp
deleted file mode 100644
index a197b45..0000000
--- a/src/cli/cli_console.hpp
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- *  Copyright (c) 2016, The OpenThread Authors.
- *  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
- *   This file contains definitions for a CLI interpreter on the CONSOLE service.
- */
-
-#ifndef CLI_CONSOLE_HPP_
-#define CLI_CONSOLE_HPP_
-
-#include "cli_config.h"
-
-#include <openthread/cli.h>
-
-#include "cli/cli.hpp"
-
-namespace ot {
-namespace Cli {
-
-/**
- * This class implements the CLI interpreter on top of the CONSOLE platform abstraction.
- *
- */
-class Console : public Interpreter
-{
-public:
-    /**
-     * This method initializes the Console interpreter.
-     *
-     * @param[in]  aInstance  The OpenThread instance structure.
-     * @param[in]  aCallback  A pointer to a callback method.
-     * @param[in]  aContext   A pointer to a user context.
-     *
-     */
-    static void Initialize(otInstance *aInstance, otCliConsoleOutputCallback aCallback, void *aContext);
-
-    /**
-     * This method delivers raw characters to the client.
-     *
-     * @param[in]  aBuf        A pointer to a buffer.
-     * @param[in]  aBufLength  Number of bytes in the buffer.
-     *
-     * @returns The number of bytes placed in the output queue.
-     *
-     */
-    int Output(const char *aBuf, uint16_t aBufLength);
-
-private:
-    explicit Console(Instance *aInstance, otCliConsoleOutputCallback aCallback, void *aContext);
-
-    otCliConsoleOutputCallback mCallback;
-    void *                     mContext;
-
-    static Console *sConsole;
-
-    friend class Interpreter;
-};
-
-} // namespace Cli
-} // namespace ot
-
-#endif // CLI_CONSOLE_HPP_
diff --git a/src/cli/cli_dataset.cpp b/src/cli/cli_dataset.cpp
index d8331d2..ddbe2ff 100644
--- a/src/cli/cli_dataset.cpp
+++ b/src/cli/cli_dataset.cpp
@@ -38,6 +38,7 @@
 
 #include <openthread/dataset.h>
 #include <openthread/dataset_ftd.h>
+#include <openthread/dataset_updater.h>
 
 #include "cli/cli.hpp"
 #include "common/string.hpp"
@@ -910,5 +911,46 @@
     return error;
 }
 
+#if OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE && OPENTHREAD_FTD
+
+otError Dataset::ProcessUpdater(uint8_t aArgsLength, char *aArgs[])
+{
+    otError error = OT_ERROR_NONE;
+
+    if (aArgsLength == 0)
+    {
+        mInterpreter.OutputEnabledDisabledStatus(otDatasetUpdaterIsUpdateOngoing(mInterpreter.mInstance));
+        ExitNow();
+    }
+
+    if (strcmp(aArgs[0], "start") == 0)
+    {
+        error = otDatasetUpdaterRequestUpdate(mInterpreter.mInstance, &sDataset, &Dataset::HandleDatasetUpdater, this);
+    }
+    else if (strcmp(aArgs[0], "cancel") == 0)
+    {
+        otDatasetUpdaterCancelUpdate(mInterpreter.mInstance);
+    }
+    else
+    {
+        error = OT_ERROR_INVALID_ARGS;
+    }
+
+exit:
+    return error;
+}
+
+void Dataset::HandleDatasetUpdater(otError aError, void *aContext)
+{
+    static_cast<Dataset *>(aContext)->HandleDatasetUpdater(aError);
+}
+
+void Dataset::HandleDatasetUpdater(otError aError)
+{
+    mInterpreter.OutputLine("Dataset update complete: %s", otThreadErrorToString(aError));
+}
+
+#endif // OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE && OPENTHREAD_FTD
+
 } // namespace Cli
 } // namespace ot
diff --git a/src/cli/cli_dataset.hpp b/src/cli/cli_dataset.hpp
index f3f7e5d..a3c0837 100644
--- a/src/cli/cli_dataset.hpp
+++ b/src/cli/cli_dataset.hpp
@@ -99,6 +99,12 @@
     otError ProcessSecurityPolicy(uint8_t aArgsLength, char *aArgs[]);
     otError ProcessSet(uint8_t aArgsLength, char *aArgs[]);
 
+#if OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE && OPENTHREAD_FTD
+    otError     ProcessUpdater(uint8_t aArgsLength, char *aArgs[]);
+    static void HandleDatasetUpdater(otError aError, void *aContext);
+    void        HandleDatasetUpdater(otError aError);
+#endif
+
     static constexpr Command sCommands[] = {
         {"active", &Dataset::ProcessActive},
         {"activetimestamp", &Dataset::ProcessActiveTimestamp},
@@ -121,6 +127,9 @@
         {"pskc", &Dataset::ProcessPskc},
         {"securitypolicy", &Dataset::ProcessSecurityPolicy},
         {"set", &Dataset::ProcessSet},
+#if OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE && OPENTHREAD_FTD
+        {"updater", &Dataset::ProcessUpdater},
+#endif
     };
 
     static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted");
diff --git a/src/cli/cli_srp_client.cpp b/src/cli/cli_srp_client.cpp
new file mode 100644
index 0000000..be3f403
--- /dev/null
+++ b/src/cli/cli_srp_client.cpp
@@ -0,0 +1,578 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file implements a simple CLI for the SRP Client.
+ */
+
+#include "cli_srp_client.hpp"
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+
+#include <string.h>
+
+#include "cli/cli.hpp"
+#include "utils/parse_cmdline.hpp"
+
+using ot::Utils::CmdLineParser::ParseAsBool;
+using ot::Utils::CmdLineParser::ParseAsHexString;
+using ot::Utils::CmdLineParser::ParseAsIp6Address;
+using ot::Utils::CmdLineParser::ParseAsUint16;
+using ot::Utils::CmdLineParser::ParseAsUint32;
+
+namespace ot {
+namespace Cli {
+
+constexpr SrpClient::Command SrpClient::sCommands[];
+
+template <uint16_t kDestSize> static otError CopyString(char (&aDestination)[kDestSize], const char *aSource)
+{
+    // Copies a string from `aSource` to `aDestination` (char array),
+    // verifying that the string fits in the destination array.
+
+    otError error = OT_ERROR_NONE;
+    size_t  len   = strlen(aSource);
+
+    VerifyOrExit(len + 1 <= kDestSize, error = OT_ERROR_INVALID_ARGS);
+    memcpy(aDestination, aSource, len + 1);
+
+exit:
+    return error;
+}
+
+SrpClient::SrpClient(Interpreter &aInterpreter)
+    : mInterpreter(aInterpreter)
+    , mCallbackEnabled(false)
+{
+    for (Service &service : mServicePool)
+    {
+        service.MarkAsNotInUse();
+    }
+
+    memset(mHostAddresses, 0, sizeof(mHostAddresses));
+
+    otSrpClientSetCallback(mInterpreter.mInstance, SrpClient::HandleCallback, this);
+}
+
+otError SrpClient::Process(uint8_t aArgsLength, char *aArgs[])
+{
+    otError        error = OT_ERROR_INVALID_COMMAND;
+    const Command *command;
+
+    VerifyOrExit(aArgsLength != 0, IgnoreError(ProcessHelp(0, nullptr)));
+
+    command = Utils::LookupTable::Find(aArgs[0], sCommands);
+    VerifyOrExit(command != nullptr);
+
+    error = (this->*command->mHandler)(aArgsLength - 1, aArgs + 1);
+
+exit:
+    return error;
+}
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+
+otError SrpClient::ProcessAutoStart(uint8_t aArgsLength, char *aArgs[])
+{
+    otError error = OT_ERROR_NONE;
+
+    if (aArgsLength == 0)
+    {
+        mInterpreter.OutputEnabledDisabledStatus(otSrpClientIsAutoStartModeEnabled(mInterpreter.mInstance));
+        ExitNow();
+    }
+
+    VerifyOrExit(aArgsLength == 1, error = OT_ERROR_INVALID_ARGS);
+
+    if (strcmp(aArgs[0], "enable") == 0)
+    {
+        otSrpClientEnableAutoStartMode(mInterpreter.mInstance, /* aCallback */ nullptr, /* aContext */ nullptr);
+    }
+    else if (strcmp(aArgs[0], "disable") == 0)
+    {
+        otSrpClientDisableAutoStartMode(mInterpreter.mInstance);
+    }
+    else
+    {
+        error = OT_ERROR_INVALID_COMMAND;
+    }
+
+exit:
+    return error;
+}
+
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+
+otError SrpClient::ProcessCallback(uint8_t aArgsLength, char *aArgs[])
+{
+    otError error = OT_ERROR_NONE;
+
+    if (aArgsLength == 0)
+    {
+        mInterpreter.OutputEnabledDisabledStatus(mCallbackEnabled);
+        ExitNow();
+    }
+
+    VerifyOrExit(aArgsLength == 1, error = OT_ERROR_INVALID_ARGS);
+
+    if (strcmp(aArgs[0], "enable") == 0)
+    {
+        mCallbackEnabled = true;
+    }
+    else if (strcmp(aArgs[0], "disable") == 0)
+    {
+        mCallbackEnabled = false;
+    }
+    else
+    {
+        error = OT_ERROR_INVALID_ARGS;
+    }
+
+exit:
+    return error;
+}
+
+otError SrpClient::ProcessHelp(uint8_t aArgsLength, char *aArgs[])
+{
+    OT_UNUSED_VARIABLE(aArgsLength);
+    OT_UNUSED_VARIABLE(aArgs);
+
+    for (const Command &command : sCommands)
+    {
+        mInterpreter.OutputLine(command.mName);
+    }
+
+    return OT_ERROR_NONE;
+}
+
+otError SrpClient::ProcessHost(uint8_t aArgsLength, char *aArgs[])
+{
+    otError error = OT_ERROR_NONE;
+
+    if (aArgsLength == 0)
+    {
+        OutputHostInfo(0, *otSrpClientGetHostInfo(mInterpreter.mInstance));
+        ExitNow();
+    }
+
+    if (strcmp(aArgs[0], "name") == 0)
+    {
+        if (aArgsLength == 1)
+        {
+            const char *name = otSrpClientGetHostInfo(mInterpreter.mInstance)->mName;
+            mInterpreter.OutputLine("%s", (name != nullptr) ? name : "(null)");
+        }
+        else
+        {
+            VerifyOrExit(aArgsLength == 2, error = OT_ERROR_INVALID_ARGS);
+            SuccessOrExit(error = CopyString(mHostName, aArgs[1]));
+            error = otSrpClientSetHostName(mInterpreter.mInstance, mHostName);
+        }
+    }
+    else if (strcmp(aArgs[0], "state") == 0)
+    {
+        VerifyOrExit(aArgsLength == 1, error = OT_ERROR_INVALID_ARGS);
+        mInterpreter.OutputLine("%s",
+                                otSrpClientItemStateToString(otSrpClientGetHostInfo(mInterpreter.mInstance)->mState));
+    }
+    else if (strcmp(aArgs[0], "address") == 0)
+    {
+        if (aArgsLength == 1)
+        {
+            const otSrpClientHostInfo *hostInfo = otSrpClientGetHostInfo(mInterpreter.mInstance);
+
+            for (uint8_t index = 0; index < hostInfo->mNumAddresses; index++)
+            {
+                mInterpreter.OutputIp6Address(hostInfo->mAddresses[index]);
+                mInterpreter.OutputLine("");
+            }
+        }
+        else
+        {
+            uint8_t      numAddresses;
+            otIp6Address addresses[kMaxHostAddresses];
+
+            numAddresses = 0;
+            memset(addresses, 0, sizeof(addresses));
+
+            while (aArgsLength > 1)
+            {
+                VerifyOrExit(numAddresses < kMaxHostAddresses, error = OT_ERROR_NO_BUFS);
+                aArgsLength--;
+                aArgs++;
+                SuccessOrExit(error = ParseAsIp6Address(aArgs[0], addresses[numAddresses++]));
+            }
+
+            VerifyOrExit(numAddresses > 0, error = OT_ERROR_INVALID_ARGS);
+            memcpy(mHostAddresses, addresses, sizeof(addresses));
+
+            error = otSrpClientSetHostAddresses(mInterpreter.mInstance, mHostAddresses, numAddresses);
+        }
+    }
+    else if (strcmp(aArgs[0], "remove") == 0)
+    {
+        bool removeKeyLease = false;
+
+        if (aArgsLength > 1)
+        {
+            VerifyOrExit(aArgsLength == 2, error = OT_ERROR_INVALID_ARGS);
+            SuccessOrExit(error = ParseAsBool(aArgs[1], removeKeyLease));
+        }
+
+        error = otSrpClientRemoveHostAndServices(mInterpreter.mInstance, removeKeyLease);
+    }
+    else if (strcmp(aArgs[0], "clear") == 0)
+    {
+        VerifyOrExit(aArgsLength == 1, error = OT_ERROR_INVALID_ARGS);
+        otSrpClientClearHostAndServices(mInterpreter.mInstance);
+
+        for (Service &poolEntry : mServicePool)
+        {
+            poolEntry.MarkAsNotInUse();
+        }
+    }
+    else
+    {
+        error = OT_ERROR_INVALID_COMMAND;
+    }
+
+exit:
+    return error;
+}
+
+otError SrpClient::ProcessLeaseInterval(uint8_t aArgsLength, char *aArgs[])
+{
+    otError  error = OT_ERROR_NONE;
+    uint32_t interval;
+
+    if (aArgsLength == 0)
+    {
+        mInterpreter.OutputLine("%u", otSrpClientGetLeaseInterval(mInterpreter.mInstance));
+        ExitNow();
+    }
+
+    VerifyOrExit(aArgsLength == 1, error = OT_ERROR_INVALID_ARGS);
+    SuccessOrExit(error = ParseAsUint32(aArgs[0], interval));
+    otSrpClientSetLeaseInterval(mInterpreter.mInstance, interval);
+
+exit:
+    return error;
+}
+
+otError SrpClient::ProcessKeyLeaseInterval(uint8_t aArgsLength, char *aArgs[])
+{
+    otError  error = OT_ERROR_NONE;
+    uint32_t interval;
+
+    if (aArgsLength == 0)
+    {
+        mInterpreter.OutputLine("%u", otSrpClientGetKeyLeaseInterval(mInterpreter.mInstance));
+        ExitNow();
+    }
+
+    VerifyOrExit(aArgsLength == 1, error = OT_ERROR_INVALID_ARGS);
+    SuccessOrExit(error = ParseAsUint32(aArgs[0], interval));
+    otSrpClientSetKeyLeaseInterval(mInterpreter.mInstance, interval);
+
+exit:
+    return error;
+}
+
+otError SrpClient::ProcessServer(uint8_t aArgsLength, char *aArgs[])
+{
+    otError           error          = OT_ERROR_NONE;
+    const otSockAddr *serverSockAddr = otSrpClientGetServerAddress(mInterpreter.mInstance);
+
+    if (aArgsLength == 0)
+    {
+        mInterpreter.OutputFormat("[");
+        mInterpreter.OutputIp6Address(serverSockAddr->mAddress);
+        mInterpreter.OutputLine("]:%u", serverSockAddr->mPort);
+        ExitNow();
+    }
+
+    VerifyOrExit(aArgsLength == 1, error = OT_ERROR_INVALID_ARGS);
+
+    if (strcmp(aArgs[0], "address") == 0)
+    {
+        mInterpreter.OutputIp6Address(serverSockAddr->mAddress);
+        mInterpreter.OutputLine("");
+    }
+    else if (strcmp(aArgs[0], "port") == 0)
+    {
+        mInterpreter.OutputLine("%u", serverSockAddr->mPort);
+    }
+    else
+    {
+        error = OT_ERROR_INVALID_COMMAND;
+    }
+
+exit:
+    return error;
+}
+
+otError SrpClient::ProcessService(uint8_t aArgsLength, char *aArgs[])
+{
+    otError error = OT_ERROR_NONE;
+
+    if (aArgsLength == 0)
+    {
+        OutputServiceList(0, otSrpClientGetServices(mInterpreter.mInstance));
+        ExitNow();
+    }
+
+    if (strcmp(aArgs[0], "add") == 0)
+    {
+        // `add` <instance-name> <service-name> <port> [priority] [weight] [txt]
+
+        Service *entry = nullptr;
+
+        VerifyOrExit(4 <= aArgsLength && aArgsLength <= 7, error = OT_ERROR_INVALID_ARGS);
+
+        for (Service &poolEntry : mServicePool)
+        {
+            if (!poolEntry.IsInUse())
+            {
+                entry = &poolEntry;
+                break;
+            }
+        }
+
+        VerifyOrExit(entry != nullptr, error = OT_ERROR_NO_BUFS);
+
+        memset(&entry->mService, 0, sizeof(entry->mService));
+
+        SuccessOrExit(error = CopyString(entry->mInstanceName, aArgs[1]));
+        entry->mService.mInstanceName = entry->mInstanceName;
+
+        SuccessOrExit(error = CopyString(entry->mServiceName, aArgs[2]));
+        entry->mService.mName = entry->mServiceName;
+
+        SuccessOrExit(error = ParseAsUint16(aArgs[3], entry->mService.mPort));
+
+        if (aArgsLength >= 5)
+        {
+            SuccessOrExit(error = ParseAsUint16(aArgs[4], entry->mService.mPriority));
+        }
+
+        if (aArgsLength >= 6)
+        {
+            SuccessOrExit(error = ParseAsUint16(aArgs[5], entry->mService.mWeight));
+        }
+
+        if (aArgsLength >= 7)
+        {
+            entry->mService.mNumTxtEntries = 1;
+            entry->mService.mTxtEntries    = &entry->mTxtEntry;
+            entry->mTxtEntry.mKey          = nullptr; // Treat `mValue` as an already encoded TXT-DATA
+            entry->mTxtEntry.mValue        = entry->mTxtBuffer;
+            entry->mTxtEntry.mValueLength  = sizeof(entry->mTxtBuffer);
+
+            SuccessOrExit(error = ParseAsHexString(aArgs[6], entry->mTxtEntry.mValueLength, entry->mTxtBuffer));
+        }
+
+        error = otSrpClientAddService(mInterpreter.mInstance, &entry->mService);
+
+        if (error != OT_ERROR_NONE)
+        {
+            entry->MarkAsNotInUse();
+        }
+    }
+    else if (strcmp(aArgs[0], "remove") == 0)
+    {
+        // `remove` <instance-name> <service-name>
+
+        Service *entry = nullptr;
+
+        VerifyOrExit(aArgsLength == 3, error = OT_ERROR_INVALID_ARGS);
+
+        for (Service &poolEntry : mServicePool)
+        {
+            if (poolEntry.IsInUse() && (strcmp(aArgs[1], poolEntry.mInstanceName) == 0) &&
+                (strcmp(aArgs[2], poolEntry.mServiceName) == 0))
+            {
+                entry = &poolEntry;
+                break;
+            }
+        }
+
+        VerifyOrExit(entry != nullptr, error = OT_ERROR_NOT_FOUND);
+
+        error = otSrpClientRemoveService(mInterpreter.mInstance, &entry->mService);
+    }
+    else
+    {
+        error = OT_ERROR_INVALID_COMMAND;
+    }
+
+exit:
+    return error;
+}
+
+void SrpClient::OutputHostInfo(uint8_t aIndentSize, const otSrpClientHostInfo &aHostInfo)
+{
+    mInterpreter.OutputFormat(aIndentSize, "name:");
+
+    if (aHostInfo.mName != nullptr)
+    {
+        mInterpreter.OutputFormat("\"%s\"", aHostInfo.mName);
+    }
+    else
+    {
+        mInterpreter.OutputFormat("(null)");
+    }
+
+    mInterpreter.OutputFormat(", state:%s, addrs:[", otSrpClientItemStateToString(aHostInfo.mState));
+
+    for (uint8_t index = 0; index < aHostInfo.mNumAddresses; index++)
+    {
+        if (index > 0)
+        {
+            mInterpreter.OutputFormat(", ");
+        }
+
+        mInterpreter.OutputIp6Address(aHostInfo.mAddresses[index]);
+    }
+
+    mInterpreter.OutputLine("]");
+}
+
+void SrpClient::OutputServiceList(uint8_t aIndentSize, const otSrpClientService *aServices)
+{
+    while (aServices != nullptr)
+    {
+        OutputService(aIndentSize, *aServices);
+        aServices = aServices->mNext;
+    }
+}
+
+void SrpClient::OutputService(uint8_t aIndentSize, const otSrpClientService &aService)
+{
+    mInterpreter.OutputLine(aIndentSize, "instance:\"%s\", name:\"%s\", state:%s, port:%d, priority:%d, weight:%d",
+                            aService.mInstanceName, aService.mName, otSrpClientItemStateToString(aService.mState),
+                            aService.mPort, aService.mPriority, aService.mWeight);
+}
+
+otError SrpClient::ProcessStart(uint8_t aArgsLength, char *aArgs[])
+{
+    otError    error = OT_ERROR_NONE;
+    otSockAddr serverSockAddr;
+
+    VerifyOrExit(aArgsLength == 2, error = OT_ERROR_INVALID_ARGS);
+
+    SuccessOrExit(error = ParseAsIp6Address(aArgs[0], serverSockAddr.mAddress));
+    SuccessOrExit(error = ParseAsUint16(aArgs[1], serverSockAddr.mPort));
+
+    error = otSrpClientStart(mInterpreter.mInstance, &serverSockAddr);
+
+exit:
+    return error;
+}
+
+otError SrpClient::ProcessState(uint8_t aArgsLength, char *aArgs[])
+{
+    OT_UNUSED_VARIABLE(aArgs);
+
+    otError error = OT_ERROR_NONE;
+
+    VerifyOrExit(aArgsLength == 0, error = OT_ERROR_INVALID_ARGS);
+
+    mInterpreter.OutputEnabledDisabledStatus(otSrpClientIsRunning(mInterpreter.mInstance));
+
+exit:
+    return error;
+}
+
+otError SrpClient::ProcessStop(uint8_t aArgsLength, char *aArgs[])
+{
+    OT_UNUSED_VARIABLE(aArgs);
+
+    otError error = OT_ERROR_NONE;
+
+    VerifyOrExit(aArgsLength == 0, error = OT_ERROR_INVALID_ARGS);
+    otSrpClientStop(mInterpreter.mInstance);
+
+exit:
+    return error;
+}
+
+void SrpClient::HandleCallback(otError                    aError,
+                               const otSrpClientHostInfo *aHostInfo,
+                               const otSrpClientService * aServices,
+                               const otSrpClientService * aRemovedServices,
+                               void *                     aContext)
+{
+    static_cast<SrpClient *>(aContext)->HandleCallback(aError, aHostInfo, aServices, aRemovedServices);
+}
+
+void SrpClient::HandleCallback(otError                    aError,
+                               const otSrpClientHostInfo *aHostInfo,
+                               const otSrpClientService * aServices,
+                               const otSrpClientService * aRemovedServices)
+{
+    otSrpClientService *next;
+
+    if (mCallbackEnabled)
+    {
+        mInterpreter.OutputLine("SRP client callback - error:%s", otThreadErrorToString(aError));
+        mInterpreter.OutputLine("Host info:");
+        OutputHostInfo(kIndentSize, *aHostInfo);
+
+        mInterpreter.OutputLine("Service list:");
+        OutputServiceList(kIndentSize, aServices);
+
+        if (aRemovedServices != nullptr)
+        {
+            mInterpreter.OutputLine("Removed service list:");
+            OutputServiceList(kIndentSize, aRemovedServices);
+        }
+    }
+
+    // Go through removed services and mark the corresponding entry in
+    // `mServicePool` as "not in use".
+
+    for (const otSrpClientService *service = aRemovedServices; service != nullptr; service = next)
+    {
+        next = service->mNext;
+
+        for (Service &poolEntry : mServicePool)
+        {
+            if (service == &poolEntry.mService)
+            {
+                poolEntry.MarkAsNotInUse();
+                break;
+            }
+        }
+    }
+}
+
+} // namespace Cli
+} // namespace ot
+
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
diff --git a/src/cli/cli_srp_client.hpp b/src/cli/cli_srp_client.hpp
new file mode 100644
index 0000000..ef541c1
--- /dev/null
+++ b/src/cli/cli_srp_client.hpp
@@ -0,0 +1,157 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file contains definitions for a simple CLI to control SRP Client.
+ */
+
+#ifndef CLI_SRP_CLIENT_HPP_
+#define CLI_SRP_CLIENT_HPP_
+
+#include "openthread-core-config.h"
+
+#include <openthread/srp_client.h>
+
+#include "cli/cli_config.h"
+#include "utils/lookup_table.hpp"
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+
+namespace ot {
+namespace Cli {
+
+class Interpreter;
+
+/**
+ * This class implements the SRP Client CLI interpreter.
+ *
+ */
+class SrpClient
+{
+public:
+    /**
+     * Constructor
+     *
+     * @param[in]  aInterpreter  The CLI interpreter.
+     *
+     */
+    explicit SrpClient(Interpreter &aInterpreter);
+
+    /**
+     * This method interprets a list of CLI arguments.
+     *
+     * @param[in]  aArgsLength  The number of elements in @p aArgs.
+     * @param[in]  aArgs        A pointer to an array of command line arguments.
+     *
+     */
+    otError Process(uint8_t aArgsLength, char *aArgs[]);
+
+private:
+    enum : uint8_t
+    {
+        kMaxServices      = OPENTHREAD_CONFIG_CLI_SRP_CLIENT_MAX_SERVICES,
+        kMaxHostAddresses = OPENTHREAD_CONFIG_CLI_SRP_CLIENT_MAX_HOST_ADDRESSES,
+        kNameSize         = 64,
+        kTxtSize          = 255,
+        kIndentSize       = 4,
+    };
+
+    struct Service
+    {
+        void MarkAsNotInUse(void) { mService.mNext = &mService; }
+        bool IsInUse(void) const { return (mService.mNext != &mService); }
+
+        otSrpClientService mService;
+        otDnsTxtEntry      mTxtEntry;
+        char               mInstanceName[kNameSize];
+        char               mServiceName[kNameSize];
+        uint8_t            mTxtBuffer[kTxtSize];
+    };
+
+    struct Command
+    {
+        const char *mName;
+        otError (SrpClient::*mHandler)(uint8_t aArgsLength, char *aArgs[]);
+    };
+
+    otError ProcessAutoStart(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessCallback(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessHelp(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessHost(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessLeaseInterval(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessKeyLeaseInterval(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessServer(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessService(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessStart(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessState(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessStop(uint8_t aArgsLength, char *aArgs[]);
+
+    void OutputHostInfo(uint8_t aIndentSize, const otSrpClientHostInfo &aHostInfo);
+    void OutputServiceList(uint8_t aIndentSize, const otSrpClientService *aServices);
+    void OutputService(uint8_t aIndentSize, const otSrpClientService &aService);
+
+    static void HandleCallback(otError                    aError,
+                               const otSrpClientHostInfo *aHostInfo,
+                               const otSrpClientService * aServices,
+                               const otSrpClientService * aRemovedServices,
+                               void *                     aContext);
+    void        HandleCallback(otError                    aError,
+                               const otSrpClientHostInfo *aHostInfo,
+                               const otSrpClientService * aServices,
+                               const otSrpClientService * aRemovedServices);
+
+    static constexpr Command sCommands[] = {
+        {"autostart", &SrpClient::ProcessAutoStart},
+        {"callback", &SrpClient::ProcessCallback},
+        {"help", &SrpClient::ProcessHelp},
+        {"host", &SrpClient::ProcessHost},
+        {"keyleaseinterval", &SrpClient::ProcessKeyLeaseInterval},
+        {"leaseinterval", &SrpClient::ProcessLeaseInterval},
+        {"server", &SrpClient::ProcessServer},
+        {"service", &SrpClient::ProcessService},
+        {"start", &SrpClient::ProcessStart},
+        {"state", &SrpClient::ProcessState},
+        {"stop", &SrpClient::ProcessStop},
+    };
+
+    static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted");
+
+    Interpreter &mInterpreter;
+    bool         mCallbackEnabled;
+    char         mHostName[kNameSize];
+    otIp6Address mHostAddresses[kMaxHostAddresses];
+    Service      mServicePool[kMaxServices];
+};
+
+} // namespace Cli
+} // namespace ot
+
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+
+#endif // CLI_SRP_CLIENT_HPP_
diff --git a/src/cli/cli_srp_server.cpp b/src/cli/cli_srp_server.cpp
new file mode 100644
index 0000000..3a5266e
--- /dev/null
+++ b/src/cli/cli_srp_server.cpp
@@ -0,0 +1,250 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file implements a simple CLI for the SRP server.
+ */
+
+#include "cli_srp_server.hpp"
+
+#include <inttypes.h>
+
+#include "cli/cli.hpp"
+#include "common/string.hpp"
+#include "utils/parse_cmdline.hpp"
+
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+
+namespace ot {
+namespace Cli {
+
+constexpr SrpServer::Command SrpServer::sCommands[];
+
+otError SrpServer::Process(uint8_t aArgsLength, char *aArgs[])
+{
+    otError        error = OT_ERROR_INVALID_COMMAND;
+    const Command *command;
+
+    VerifyOrExit(aArgsLength != 0, IgnoreError(ProcessHelp(0, nullptr)));
+
+    command = Utils::LookupTable::Find(aArgs[0], sCommands);
+    VerifyOrExit(command != nullptr);
+
+    error = (this->*command->mHandler)(aArgsLength, aArgs);
+
+exit:
+    return error;
+}
+
+otError SrpServer::ProcessDomain(uint8_t aArgsLength, char *aArgs[])
+{
+    otError error = OT_ERROR_NONE;
+
+    if (aArgsLength > 1)
+    {
+        SuccessOrExit(error = otSrpServerSetDomain(mInterpreter.mInstance, aArgs[1]));
+    }
+    else
+    {
+        mInterpreter.OutputLine("%s", otSrpServerGetDomain(mInterpreter.mInstance));
+    }
+
+exit:
+    return error;
+}
+
+otError SrpServer::ProcessEnable(uint8_t aArgsLength, char *aArgs[])
+{
+    OT_UNUSED_VARIABLE(aArgsLength);
+    OT_UNUSED_VARIABLE(aArgs);
+
+    otSrpServerSetEnabled(mInterpreter.mInstance, /* aEnabled */ true);
+
+    return OT_ERROR_NONE;
+}
+
+otError SrpServer::ProcessDisable(uint8_t aArgsLength, char *aArgs[])
+{
+    OT_UNUSED_VARIABLE(aArgsLength);
+    OT_UNUSED_VARIABLE(aArgs);
+
+    otSrpServerSetEnabled(mInterpreter.mInstance, /* aEnabled */ false);
+
+    return OT_ERROR_NONE;
+}
+
+otError SrpServer::ProcessLease(uint8_t aArgsLength, char *aArgs[])
+{
+    otError  error = OT_ERROR_NONE;
+    uint32_t minLease;
+    uint32_t maxLease;
+    uint32_t minKeyLease;
+    uint32_t maxKeyLease;
+
+    VerifyOrExit(aArgsLength == 5, error = OT_ERROR_INVALID_ARGS);
+    SuccessOrExit(error = Utils::CmdLineParser::ParseAsUint32(aArgs[1], minLease));
+    SuccessOrExit(error = Utils::CmdLineParser::ParseAsUint32(aArgs[2], maxLease));
+    SuccessOrExit(error = Utils::CmdLineParser::ParseAsUint32(aArgs[3], minKeyLease));
+    SuccessOrExit(error = Utils::CmdLineParser::ParseAsUint32(aArgs[4], maxKeyLease));
+
+    error = otSrpServerSetLeaseRange(mInterpreter.mInstance, minLease, maxLease, minKeyLease, maxKeyLease);
+
+exit:
+    return error;
+}
+
+otError SrpServer::ProcessHost(uint8_t aArgsLength, char *aArgs[])
+{
+    OT_UNUSED_VARIABLE(aArgs);
+
+    otError                error = OT_ERROR_NONE;
+    const otSrpServerHost *host;
+
+    VerifyOrExit(aArgsLength <= 1, error = OT_ERROR_INVALID_ARGS);
+
+    host = nullptr;
+    while ((host = otSrpServerGetNextHost(mInterpreter.mInstance, host)) != nullptr)
+    {
+        const otIp6Address *addresses;
+        uint8_t             addressesNum;
+        bool                isDeleted = otSrpServerHostIsDeleted(host);
+
+        mInterpreter.OutputLine("%s", otSrpServerHostGetFullName(host));
+        mInterpreter.OutputLine(Interpreter::kIndentSize, "deleted: %s", isDeleted ? "true" : "false");
+        if (isDeleted)
+        {
+            continue;
+        }
+
+        mInterpreter.OutputSpaces(Interpreter::kIndentSize);
+        mInterpreter.OutputFormat("addresses: [");
+
+        addresses = otSrpServerHostGetAddresses(host, &addressesNum);
+
+        for (uint8_t i = 0; i < addressesNum; ++i)
+        {
+            mInterpreter.OutputIp6Address(addresses[i]);
+            if (i < addressesNum - 1)
+            {
+                mInterpreter.OutputFormat(", ");
+            }
+        }
+
+        mInterpreter.OutputFormat("]\r\n");
+    }
+
+exit:
+    return error;
+}
+
+void SrpServer::OutputHostAddresses(const otSrpServerHost *aHost)
+{
+    const otIp6Address *addresses;
+    uint8_t             addressesNum;
+
+    addresses = otSrpServerHostGetAddresses(aHost, &addressesNum);
+
+    mInterpreter.OutputFormat("[");
+    for (uint8_t i = 0; i < addressesNum; ++i)
+    {
+        if (i != 0)
+        {
+            mInterpreter.OutputFormat(", ");
+        }
+
+        mInterpreter.OutputIp6Address(addresses[i]);
+    }
+    mInterpreter.OutputFormat("]");
+}
+
+otError SrpServer::ProcessService(uint8_t aArgsLength, char *aArgs[])
+{
+    OT_UNUSED_VARIABLE(aArgs);
+
+    otError                error = OT_ERROR_NONE;
+    const otSrpServerHost *host;
+
+    VerifyOrExit(aArgsLength <= 1, error = OT_ERROR_INVALID_ARGS);
+
+    host = nullptr;
+    while ((host = otSrpServerGetNextHost(mInterpreter.mInstance, host)) != nullptr)
+    {
+        const otSrpServerService *service = nullptr;
+
+        while ((service = otSrpServerHostGetNextService(host, service)) != nullptr)
+        {
+            bool           isDeleted = otSrpServerServiceIsDeleted(service);
+            const uint8_t *txtData;
+            uint16_t       txtDataLength;
+
+            mInterpreter.OutputLine("%s", otSrpServerServiceGetFullName(service));
+            mInterpreter.OutputLine(Interpreter::kIndentSize, "deleted: %s", isDeleted ? "true" : "false");
+            if (isDeleted)
+            {
+                continue;
+            }
+
+            mInterpreter.OutputLine(Interpreter::kIndentSize, "port: %hu", otSrpServerServiceGetPort(service));
+            mInterpreter.OutputLine(Interpreter::kIndentSize, "priority: %hu", otSrpServerServiceGetPriority(service));
+            mInterpreter.OutputLine(Interpreter::kIndentSize, "weight: %hu", otSrpServerServiceGetWeight(service));
+
+            txtData = otSrpServerServiceGetTxtData(service, &txtDataLength);
+            mInterpreter.OutputFormat(Interpreter::kIndentSize, "TXT: ");
+            mInterpreter.OutputDnsTxtData(txtData, txtDataLength);
+            mInterpreter.OutputLine("");
+
+            mInterpreter.OutputLine(Interpreter::kIndentSize, "host: %s", otSrpServerHostGetFullName(host));
+
+            mInterpreter.OutputFormat(Interpreter::kIndentSize, "addresses: ");
+            OutputHostAddresses(host);
+            mInterpreter.OutputLine("");
+        }
+    }
+
+exit:
+    return error;
+}
+
+otError SrpServer::ProcessHelp(uint8_t aArgsLength, char *aArgs[])
+{
+    OT_UNUSED_VARIABLE(aArgsLength);
+    OT_UNUSED_VARIABLE(aArgs);
+
+    for (const Command &command : sCommands)
+    {
+        mInterpreter.OutputLine(command.mName);
+    }
+
+    return OT_ERROR_NONE;
+}
+
+} // namespace Cli
+} // namespace ot
+
+#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
diff --git a/src/cli/cli_srp_server.hpp b/src/cli/cli_srp_server.hpp
new file mode 100644
index 0000000..a619ee8
--- /dev/null
+++ b/src/cli/cli_srp_server.hpp
@@ -0,0 +1,114 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file contains definitions for a simple CLI to control the SRP server.
+ */
+
+#ifndef CLI_SRP_SERVER_HPP_
+#define CLI_SRP_SERVER_HPP_
+
+#include "openthread-core-config.h"
+
+#include <openthread/srp_server.h>
+
+#include "utils/lookup_table.hpp"
+
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+
+namespace ot {
+namespace Cli {
+
+class Interpreter;
+
+/**
+ * This class implements the SRP Server CLI interpreter.
+ *
+ */
+class SrpServer
+{
+public:
+    /**
+     * Constructor
+     *
+     * @param[in]  aInterpreter  The CLI interpreter.
+     *
+     */
+    explicit SrpServer(Interpreter &aInterpreter)
+        : mInterpreter(aInterpreter)
+    {
+    }
+
+    /**
+     * This method interprets a list of CLI arguments.
+     *
+     * @param[in]  aArgsLength  The number of elements in @p aArgs.
+     * @param[in]  aArgs        A pointer to an array of command line arguments.
+     *
+     * @retval  OT_ERROR_NONE  Successfully executed the CLI command.
+     * @retval  ...            Failed to execute the CLI command.
+     *
+     */
+    otError Process(uint8_t aArgsLength, char *aArgs[]);
+
+private:
+    struct Command
+    {
+        const char *mName;
+        otError (SrpServer::*mHandler)(uint8_t aArgsLength, char *aArgs[]);
+    };
+
+    otError ProcessDomain(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessEnable(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessDisable(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessLease(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessHost(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessService(uint8_t aArgsLength, char *aArgs[]);
+    otError ProcessHelp(uint8_t aArgsLength, char *aArgs[]);
+
+    void OutputHostAddresses(const otSrpServerHost *aHost);
+
+    static constexpr Command sCommands[] = {
+        {"disable", &SrpServer::ProcessDisable}, {"domain", &SrpServer::ProcessDomain},
+        {"enable", &SrpServer::ProcessEnable},   {"help", &SrpServer::ProcessHelp},
+        {"host", &SrpServer::ProcessHost},       {"lease", &SrpServer::ProcessLease},
+        {"service", &SrpServer::ProcessService},
+    };
+
+    static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted");
+
+    Interpreter &mInterpreter;
+};
+
+} // namespace Cli
+} // namespace ot
+
+#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+
+#endif // CLI_SRP_SERVER_HPP_
diff --git a/src/cli/cli_uart.cpp b/src/cli/cli_uart.cpp
deleted file mode 100644
index da408b0..0000000
--- a/src/cli/cli_uart.cpp
+++ /dev/null
@@ -1,347 +0,0 @@
-/*
- *  Copyright (c) 2016, The OpenThread Authors.
- *  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
- *   This file implements the CLI interpreter on the UART service.
- */
-
-#include "cli_uart.hpp"
-
-#if OPENTHREAD_CONFIG_CLI_TRANSPORT == OT_CLI_TRANSPORT_UART
-
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#if OPENTHREAD_POSIX
-#include <signal.h>
-#include <sys/types.h>
-#endif
-
-#include <openthread/cli.h>
-#include <openthread/platform/logging.h>
-#include <openthread/platform/uart.h>
-#if OPENTHREAD_CONFIG_ENABLE_DEBUG_UART
-#include <openthread/platform/debug_uart.h>
-#endif
-
-#include "cli/cli.hpp"
-#include "common/code_utils.hpp"
-#include "common/encoding.hpp"
-#include "common/logging.hpp"
-#include "common/new.hpp"
-#include "common/tasklet.hpp"
-
-#ifdef OT_CLI_UART_LOCK_HDR_FILE
-
-#include OT_CLI_UART_LOCK_HDR_FILE
-
-#else
-
-/**
- * Macro to acquire an exclusive lock of uart cli output
- * Default implementation does nothing
- *
- */
-#ifndef OT_CLI_UART_OUTPUT_LOCK
-#define OT_CLI_UART_OUTPUT_LOCK() \
-    do                            \
-    {                             \
-    } while (0)
-#endif
-
-/**
- * Macro to release the exclusive lock of uart cli output
- * Default implementation does nothing
- *
- */
-#ifndef OT_CLI_UART_OUTPUT_UNLOCK
-#define OT_CLI_UART_OUTPUT_UNLOCK() \
-    do                              \
-    {                               \
-    } while (0)
-#endif
-
-#endif // OT_CLI_UART_LOCK_HDR_FILE
-
-#if OPENTHREAD_CONFIG_DIAG_ENABLE
-static_assert(OPENTHREAD_CONFIG_DIAG_OUTPUT_BUFFER_SIZE <= OPENTHREAD_CONFIG_CLI_UART_TX_BUFFER_SIZE,
-              "diag output buffer should be smaller than CLI UART tx buffer");
-static_assert(OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE <= OPENTHREAD_CONFIG_CLI_UART_RX_BUFFER_SIZE,
-              "diag command line should be smaller than CLI UART rx buffer");
-#endif
-
-static_assert(OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH <= OPENTHREAD_CONFIG_CLI_UART_RX_BUFFER_SIZE,
-              "command line should be should be smaller than CLI rx buffer");
-
-namespace ot {
-namespace Cli {
-
-static OT_DEFINE_ALIGNED_VAR(sCliUartRaw, sizeof(Uart), uint64_t);
-
-void Uart::Initialize(otInstance *aInstance)
-{
-    Instance *instance        = static_cast<Instance *>(aInstance);
-    Interpreter::sInterpreter = new (&sCliUartRaw) Uart(instance);
-}
-
-Uart::Uart(Instance *aInstance)
-    : Interpreter(aInstance)
-{
-    mRxLength   = 0;
-    mTxHead     = 0;
-    mTxLength   = 0;
-    mSendLength = 0;
-
-    IgnoreError(otPlatUartEnable());
-}
-
-void Uart::ReceiveTask(const uint8_t *aBuf, uint16_t aBufLength)
-{
-#if !OPENTHREAD_CONFIG_UART_CLI_RAW
-    static const char sEraseString[] = {'\b', ' ', '\b'};
-    static const char CRNL[]         = {'\r', '\n'};
-#endif
-    static const char sCommandPrompt[] = {'>', ' '};
-    const uint8_t *   end;
-
-    end = aBuf + aBufLength;
-
-    for (; aBuf < end; aBuf++)
-    {
-        switch (*aBuf)
-        {
-        case '\r':
-        case '\n':
-#if !OPENTHREAD_CONFIG_UART_CLI_RAW
-            Output(CRNL, sizeof(CRNL));
-#endif
-            if (mRxLength > 0)
-            {
-                mRxBuffer[mRxLength] = '\0';
-                IgnoreError(ProcessCommand());
-            }
-
-            Output(sCommandPrompt, sizeof(sCommandPrompt));
-
-            break;
-
-#if !OPENTHREAD_CONFIG_UART_CLI_RAW
-#if OPENTHREAD_POSIX
-
-        case 0x03: // ASCII for Ctrl-C
-            kill(0, SIGINT);
-            break;
-
-        case 0x04: // ASCII for Ctrl-D
-            exit(EXIT_SUCCESS);
-            break;
-#endif
-
-        case '\b':
-        case 127:
-            if (mRxLength > 0)
-            {
-                Output(sEraseString, sizeof(sEraseString));
-                mRxBuffer[--mRxLength] = '\0';
-            }
-
-            break;
-#endif // !OPENTHREAD_CONFIG_UART_CLI_RAW
-
-        default:
-            if (mRxLength < kRxBufferSize - 1)
-            {
-#if !OPENTHREAD_CONFIG_UART_CLI_RAW
-                Output(reinterpret_cast<const char *>(aBuf), 1);
-#endif
-                mRxBuffer[mRxLength++] = static_cast<char>(*aBuf);
-            }
-
-            break;
-        }
-    }
-}
-
-otError Uart::ProcessCommand(void)
-{
-    otError error = OT_ERROR_NONE;
-
-    while (mRxBuffer[mRxLength - 1] == '\n' || mRxBuffer[mRxLength - 1] == '\r')
-    {
-        mRxBuffer[--mRxLength] = '\0';
-    }
-
-#if OPENTHREAD_CONFIG_LOG_OUTPUT != OPENTHREAD_CONFIG_LOG_OUTPUT_NONE
-    /*
-     * Note this is here for this reason:
-     *
-     * TEXT (command) input ... in a test automation script occurs
-     * rapidly and often without gaps between the command and the
-     * terminal CR
-     *
-     * In contrast as a human is typing there is a delay between the
-     * last character of a command and the terminal CR which executes
-     * a command.
-     *
-     * During that human induced delay a tasklet may be scheduled and
-     * the LOG becomes confusing and it is hard to determine when
-     * something happened.  Which happened first? the command-CR or
-     * the tasklet.
-     *
-     * Yes, while rare it is a race condition that is hard to debug.
-     *
-     * Thus this is here to affirmatively LOG exactly when the CLI
-     * command is being executed.
-     */
-#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
-    /* TODO: how exactly do we get the instance here? */
-#else
-    otLogInfoCli("execute command: %s", mRxBuffer);
-#endif
-#endif
-    if (mRxLength > 0)
-    {
-        ProcessLine(mRxBuffer, mRxLength);
-    }
-
-    mRxLength = 0;
-
-    return error;
-}
-
-int Interpreter::Output(const char *aBuf, uint16_t aBufLength)
-{
-    return static_cast<Uart *>(this)->Output(aBuf, aBufLength);
-}
-
-int Uart::Output(const char *aBuf, uint16_t aBufLength)
-{
-    OT_CLI_UART_OUTPUT_LOCK();
-    uint16_t sent = 0;
-
-    while (aBufLength > 0)
-    {
-        uint16_t remaining = kTxBufferSize - mTxLength;
-        uint16_t tail;
-        uint16_t sendLength = aBufLength;
-
-        if (sendLength > remaining)
-        {
-            sendLength = remaining;
-        }
-
-        for (uint16_t i = 0; i < sendLength; i++)
-        {
-            tail            = (mTxHead + mTxLength) % kTxBufferSize;
-            mTxBuffer[tail] = *aBuf++;
-            aBufLength--;
-            mTxLength++;
-        }
-
-        Send();
-
-        sent += sendLength;
-
-        if (aBufLength > 0)
-        {
-            // More to send, so flush what's waiting now
-            otError err = otPlatUartFlush();
-
-            if (err == OT_ERROR_NONE)
-            {
-                // Flush successful, reset the pointers
-                SendDoneTask();
-            }
-            else
-            {
-                // Flush did not succeed, so abort here.
-                break;
-            }
-        }
-    }
-
-    OT_CLI_UART_OUTPUT_UNLOCK();
-
-    return sent;
-}
-
-void Uart::Send(void)
-{
-    VerifyOrExit(mSendLength == 0);
-
-    if (mTxLength > kTxBufferSize - mTxHead)
-    {
-        mSendLength = kTxBufferSize - mTxHead;
-    }
-    else
-    {
-        mSendLength = mTxLength;
-    }
-
-    if (mSendLength > 0)
-    {
-#if OPENTHREAD_CONFIG_ENABLE_DEBUG_UART
-        /* duplicate the output to the debug uart */
-        otPlatDebugUart_write_bytes(reinterpret_cast<uint8_t *>(mTxBuffer + mTxHead), mSendLength);
-#endif
-        IgnoreError(otPlatUartSend(reinterpret_cast<uint8_t *>(mTxBuffer + mTxHead), mSendLength));
-    }
-
-exit:
-    return;
-}
-
-void Uart::SendDoneTask(void)
-{
-    mTxHead = (mTxHead + mSendLength) % kTxBufferSize;
-    mTxLength -= mSendLength;
-    mSendLength = 0;
-
-    Send();
-}
-
-extern "C" void otCliUartInit(otInstance *aInstance)
-{
-    Uart::Initialize(aInstance);
-}
-
-extern "C" void otPlatUartReceived(const uint8_t *aBuf, uint16_t aBufLength)
-{
-    static_cast<Uart &>(Interpreter::GetInterpreter()).ReceiveTask(aBuf, aBufLength);
-}
-
-extern "C" void otPlatUartSendDone(void)
-{
-    static_cast<Uart &>(Interpreter::GetInterpreter()).SendDoneTask();
-}
-
-} // namespace Cli
-} // namespace ot
-
-#endif // OPENTHREAD_CONFIG_CLI_TRANSPORT == OT_CLI_TRANSPORT_UART
diff --git a/src/cli/cli_uart.hpp b/src/cli/cli_uart.hpp
deleted file mode 100644
index 33164d5..0000000
--- a/src/cli/cli_uart.hpp
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- *  Copyright (c) 2016, The OpenThread Authors.
- *  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
- *   This file contains definitions for a CLI interpreter on the UART service.
- */
-
-#ifndef CLI_UART_HPP_
-#define CLI_UART_HPP_
-
-#include "openthread-core-config.h"
-
-#include "cli/cli.hpp"
-#include "common/instance.hpp"
-#include "common/tasklet.hpp"
-
-namespace ot {
-namespace Cli {
-
-/**
- * This class implements the CLI interpreter on top of the UART platform abstraction.
- *
- */
-class Uart : public Interpreter
-{
-public:
-    static void Initialize(otInstance *aInstance);
-
-    /**
-     * This method delivers raw characters to the client.
-     *
-     * @param[in]  aBuf        A pointer to a buffer.
-     * @param[in]  aBufLength  Number of bytes in the buffer.
-     *
-     * @returns The number of bytes placed in the output queue.
-     *
-     */
-    int Output(const char *aBuf, uint16_t aBufLength);
-
-    void ReceiveTask(const uint8_t *aBuf, uint16_t aBufLength);
-    void SendDoneTask(void);
-
-private:
-    /**
-     * Constructor
-     *
-     * @param[in]  aInstance  The OpenThread instance structure.
-     *
-     */
-    explicit Uart(Instance *aInstance);
-
-    enum
-    {
-        kRxBufferSize = OPENTHREAD_CONFIG_CLI_UART_RX_BUFFER_SIZE,
-        kTxBufferSize = OPENTHREAD_CONFIG_CLI_UART_TX_BUFFER_SIZE,
-    };
-
-    otError ProcessCommand(void);
-    void    Send(void);
-
-    char     mRxBuffer[kRxBufferSize];
-    uint16_t mRxLength;
-
-    char     mTxBuffer[kTxBufferSize];
-    uint16_t mTxHead;
-    uint16_t mTxLength;
-
-    uint16_t     mSendLength;
-    static Uart *sUart;
-
-    friend class Interpreter;
-};
-
-} // namespace Cli
-} // namespace ot
-
-#endif // CLI_UART_HPP_
diff --git a/src/cli/cli_udp.cpp b/src/cli/cli_udp.cpp
index 501d29a..136df36 100644
--- a/src/cli/cli_udp.cpp
+++ b/src/cli/cli_udp.cpp
@@ -214,7 +214,7 @@
 
     if (aArgsLength == 0)
     {
-        mInterpreter.OutputLine(mLinkSecurityEnabled ? "Enabled" : "Disabled");
+        mInterpreter.OutputEnabledDisabledStatus(mLinkSecurityEnabled);
     }
     else if (strcmp(aArgs[0], "enable") == 0)
     {
diff --git a/src/cli/ftd.cmake b/src/cli/ftd.cmake
index e0385bb..3d50d0c 100644
--- a/src/cli/ftd.cmake
+++ b/src/cli/ftd.cmake
@@ -38,8 +38,6 @@
 target_compile_definitions(openthread-cli-ftd
     PRIVATE
         OPENTHREAD_FTD=1
-    PUBLIC
-        "OPENTHREAD_CONFIG_CLI_TRANSPORT=OT_CLI_TRANSPORT_${OT_CLI_TRANSPORT}"
 )
 
 target_compile_options(openthread-cli-ftd PRIVATE
diff --git a/src/cli/mtd.cmake b/src/cli/mtd.cmake
index dabb8b1..9c06fb3 100644
--- a/src/cli/mtd.cmake
+++ b/src/cli/mtd.cmake
@@ -38,8 +38,6 @@
 target_compile_definitions(openthread-cli-mtd
     PRIVATE
         OPENTHREAD_MTD=1
-    PUBLIC
-        "OPENTHREAD_CONFIG_CLI_TRANSPORT=OT_CLI_TRANSPORT_${OT_CLI_TRANSPORT}"
 )
 
 target_compile_options(openthread-cli-mtd PRIVATE
diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn
index 7d0aa52..42cf45f 100644
--- a/src/core/BUILD.gn
+++ b/src/core/BUILD.gn
@@ -74,6 +74,10 @@
       defines += [ "OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE=1" ]
     }
 
+    if (openthread_config_border_routing_enable) {
+      defines += [ "OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE=1" ]
+    }
+
     if (openthread_external_mbedtls != "") {
       defines += [ "OPENTHREAD_CONFIG_ENABLE_BUILTIN_MBEDTLS=0" ]
     } else if (!openthread_config_enable_builtin_mbedtls_management) {
@@ -128,6 +132,10 @@
       defines += [ "OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE=1" ]
     }
 
+    if (openthread_config_dnssd_server_enable) {
+      defines += [ "OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE=1" ]
+    }
+
     if (openthread_config_ecdsa_enable) {
       defines += [ "OPENTHREAD_CONFIG_ECDSA_ENABLE=1" ]
     }
@@ -220,6 +228,18 @@
       defines += [ "OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE=1" ]
     }
 
+    if (openthread_config_srp_client_enable) {
+      defines += [ "OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE=1" ]
+    }
+
+    if (openthread_config_srp_server_enable) {
+      defines += [ "OPENTHREAD_CONFIG_SRP_SERVER_ENABLE=1" ]
+    }
+
+    if (openthread_config_ping_sender) {
+      defines += [ "OPENTHREAD_CONFIG_PING_SENDER_ENABLE=1" ]
+    }
+
     if (openthread_config_time_sync_enable) {
       defines += [ "OPENTHREAD_CONFIG_TIME_SYNC_ENABLE=1" ]
     }
@@ -229,28 +249,9 @@
     }
 
     if (openthread_config_full_logs) {
-      defines += [
-        "OPENTHREAD_CONFIG_LOG_LEVEL=OT_LOG_LEVEL_DEBG",
-        "OPENTHREAD_CONFIG_LOG_API=1",
-        "OPENTHREAD_CONFIG_LOG_ARP=1",
-        "OPENTHREAD_CONFIG_LOG_BBR=1",
-        "OPENTHREAD_CONFIG_LOG_CLI=1",
-        "OPENTHREAD_CONFIG_LOG_COAP=1",
-        "OPENTHREAD_CONFIG_LOG_DUA=1",
-        "OPENTHREAD_CONFIG_LOG_ICMP=1",
-        "OPENTHREAD_CONFIG_LOG_IP6=1",
-        "OPENTHREAD_CONFIG_LOG_MAC=1",
-        "OPENTHREAD_CONFIG_LOG_MEM=1",
-        "OPENTHREAD_CONFIG_LOG_MESHCOP=1",
-        "OPENTHREAD_CONFIG_LOG_MLE=1",
-        "OPENTHREAD_CONFIG_LOG_MLR=1",
-        "OPENTHREAD_CONFIG_LOG_NETDATA=1",
-        "OPENTHREAD_CONFIG_LOG_NETDIAG=1",
-        "OPENTHREAD_CONFIG_LOG_PKT_DUMP=1",
-        "OPENTHREAD_CONFIG_LOG_PLATFORM=1",
-        "OPENTHREAD_CONFIG_LOG_PREPEND_LEVEL=1",
-        "OPENTHREAD_CONFIG_LOG_PREPEND_REGION=1",
-      ]
+      defines += [ "OPENTHREAD_CONFIG_LOG_LEVEL=OT_LOG_LEVEL_DEBG" ]
+      defines += [ "OPENTHREAD_CONFIG_LOG_PREPEND_LEVEL=1" ]
+      defines += [ "OPENTHREAD_CONFIG_LOG_PREPEND_REGION=1" ]
     }
 
     if (openthread_config_otns_enable) {
@@ -307,6 +308,7 @@
   "api/diags_api.cpp",
   "api/dns_api.cpp",
   "api/entropy_api.cpp",
+  "api/error_api.cpp",
   "api/heap_api.cpp",
   "api/icmp6_api.cpp",
   "api/instance_api.cpp",
@@ -322,10 +324,13 @@
   "api/netdata_api.cpp",
   "api/netdiag_api.cpp",
   "api/network_time_api.cpp",
+  "api/ping_sender_api.cpp",
   "api/random_crypto_api.cpp",
   "api/random_noncrypto_api.cpp",
   "api/server_api.cpp",
   "api/sntp_api.cpp",
+  "api/srp_client_api.cpp",
+  "api/srp_server_api.cpp",
   "api/tasklet_api.cpp",
   "api/thread_api.cpp",
   "api/thread_ftd_api.cpp",
@@ -342,6 +347,11 @@
   "backbone_router/multicast_listeners_table.hpp",
   "backbone_router/ndproxy_table.cpp",
   "backbone_router/ndproxy_table.hpp",
+  "border_router/infra_if_platform.cpp",
+  "border_router/router_advertisement.cpp",
+  "border_router/router_advertisement.hpp",
+  "border_router/routing_manager.cpp",
+  "border_router/routing_manager.hpp",
   "coap/coap.cpp",
   "coap/coap.hpp",
   "coap/coap_message.cpp",
@@ -357,9 +367,12 @@
   "common/debug.hpp",
   "common/encoding.hpp",
   "common/equatable.hpp",
+  "common/error.cpp",
+  "common/error.hpp",
   "common/extension.hpp",
   "common/instance.cpp",
   "common/instance.hpp",
+  "common/iterator_utils.hpp",
   "common/linked_list.hpp",
   "common/locator-getters.hpp",
   "common/locator.hpp",
@@ -405,7 +418,7 @@
   "crypto/mbedtls.cpp",
   "crypto/mbedtls.hpp",
   "crypto/pbkdf2_cmac.cpp",
-  "crypto/pbkdf2_cmac.h",
+  "crypto/pbkdf2_cmac.hpp",
   "crypto/sha256.cpp",
   "crypto/sha256.hpp",
   "diags/factory_diags.cpp",
@@ -444,6 +457,8 @@
   "meshcop/dataset_manager.cpp",
   "meshcop/dataset_manager.hpp",
   "meshcop/dataset_manager_ftd.cpp",
+  "meshcop/dataset_updater.cpp",
+  "meshcop/dataset_updater.hpp",
   "meshcop/dtls.cpp",
   "meshcop/dtls.hpp",
   "meshcop/energy_scan_client.cpp",
@@ -471,8 +486,10 @@
   "net/dhcp6_server.hpp",
   "net/dns_client.cpp",
   "net/dns_client.hpp",
-  "net/dns_headers.cpp",
-  "net/dns_headers.hpp",
+  "net/dns_types.cpp",
+  "net/dns_types.hpp",
+  "net/dnssd_server.cpp",
+  "net/dnssd_server.hpp",
   "net/icmp6.cpp",
   "net/icmp6.hpp",
   "net/ip6.cpp",
@@ -489,10 +506,16 @@
   "net/netif.hpp",
   "net/sntp_client.cpp",
   "net/sntp_client.hpp",
+  "net/socket.cpp",
   "net/socket.hpp",
+  "net/srp_client.cpp",
+  "net/srp_client.hpp",
+  "net/srp_server.cpp",
+  "net/srp_server.hpp",
   "net/tcp.hpp",
   "net/udp6.cpp",
   "net/udp6.hpp",
+  "radio/max_power_table.hpp",
   "radio/radio.cpp",
   "radio/radio.hpp",
   "radio/radio_callbacks.cpp",
@@ -558,6 +581,8 @@
   "thread/network_data_local.hpp",
   "thread/network_data_notifier.cpp",
   "thread/network_data_notifier.hpp",
+  "thread/network_data_service.cpp",
+  "thread/network_data_service.hpp",
   "thread/network_data_tlvs.hpp",
   "thread/network_diagnostic.cpp",
   "thread/network_diagnostic.hpp",
@@ -587,8 +612,6 @@
   "utils/channel_monitor.hpp",
   "utils/child_supervision.cpp",
   "utils/child_supervision.hpp",
-  "utils/dataset_updater.cpp",
-  "utils/dataset_updater.hpp",
   "utils/flash.cpp",
   "utils/flash.hpp",
   "utils/heap.cpp",
@@ -601,17 +624,21 @@
   "utils/otns.hpp",
   "utils/parse_cmdline.cpp",
   "utils/parse_cmdline.hpp",
+  "utils/ping_sender.cpp",
+  "utils/ping_sender.hpp",
   "utils/slaac_address.cpp",
   "utils/slaac_address.hpp",
 ]
 
 openthread_radio_sources = [
   "api/diags_api.cpp",
+  "api/error_api.cpp",
   "api/instance_api.cpp",
   "api/link_raw_api.cpp",
   "api/logging_api.cpp",
   "api/random_noncrypto_api.cpp",
   "api/tasklet_api.cpp",
+  "common/error.hpp",
   "common/instance.cpp",
   "common/logging.cpp",
   "common/random_manager.cpp",
@@ -657,6 +684,8 @@
     "config/dhcp6_server.h",
     "config/diag.h",
     "config/dns_client.h",
+    "config/dnssd_server.h",
+    "config/dtls.h",
     "config/ip6.h",
     "config/joiner.h",
     "config/link_quality.h",
@@ -667,9 +696,12 @@
     "config/openthread-core-config-check.h",
     "config/openthread-core-default-config.h",
     "config/parent_search.h",
+    "config/ping_sender.h",
     "config/platform.h",
     "config/radio_link.h",
     "config/sntp_client.h",
+    "config/srp_client.h",
+    "config/srp_server.h",
     "config/time_sync.h",
     "config/tmf.h",
     "openthread-core-config.h",
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index f1b9455..e1dafa0 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -33,6 +33,7 @@
 set(COMMON_SOURCES
     api/backbone_router_api.cpp
     api/backbone_router_ftd_api.cpp
+    api/border_agent_api.cpp
     api/border_router_api.cpp
     api/channel_manager_api.cpp
     api/channel_monitor_api.cpp
@@ -47,6 +48,7 @@
     api/diags_api.cpp
     api/dns_api.cpp
     api/entropy_api.cpp
+    api/error_api.cpp
     api/heap_api.cpp
     api/icmp6_api.cpp
     api/instance_api.cpp
@@ -62,10 +64,13 @@
     api/netdata_api.cpp
     api/netdiag_api.cpp
     api/network_time_api.cpp
+    api/ping_sender_api.cpp
     api/random_crypto_api.cpp
     api/random_noncrypto_api.cpp
     api/server_api.cpp
     api/sntp_api.cpp
+    api/srp_client_api.cpp
+    api/srp_server_api.cpp
     api/tasklet_api.cpp
     api/thread_api.cpp
     api/thread_ftd_api.cpp
@@ -76,10 +81,14 @@
     backbone_router/bbr_manager.cpp
     backbone_router/multicast_listeners_table.cpp
     backbone_router/ndproxy_table.cpp
+    border_router/infra_if_platform.cpp
+    border_router/router_advertisement.cpp
+    border_router/routing_manager.cpp
     coap/coap.cpp
     coap/coap_message.cpp
     coap/coap_secure.cpp
     common/crc16.cpp
+    common/error.cpp
     common/instance.cpp
     common/logging.cpp
     common/message.cpp
@@ -119,6 +128,7 @@
     meshcop/dataset_local.cpp
     meshcop/dataset_manager.cpp
     meshcop/dataset_manager_ftd.cpp
+    meshcop/dataset_updater.cpp
     meshcop/dtls.cpp
     meshcop/energy_scan_client.cpp
     meshcop/joiner.cpp
@@ -132,7 +142,8 @@
     net/dhcp6_client.cpp
     net/dhcp6_server.cpp
     net/dns_client.cpp
-    net/dns_headers.cpp
+    net/dns_types.cpp
+    net/dnssd_server.cpp
     net/icmp6.cpp
     net/ip6.cpp
     net/ip6_address.cpp
@@ -141,6 +152,9 @@
     net/ip6_mpl.cpp
     net/netif.cpp
     net/sntp_client.cpp
+    net/socket.cpp
+    net/srp_client.cpp
+    net/srp_server.cpp
     net/udp6.cpp
     radio/radio.cpp
     radio/radio_callbacks.cpp
@@ -174,6 +188,7 @@
     thread/network_data_leader_ftd.cpp
     thread/network_data_local.cpp
     thread/network_data_notifier.cpp
+    thread/network_data_service.cpp
     thread/network_diagnostic.cpp
     thread/panid_query_server.cpp
     thread/radio_selector.cpp
@@ -187,13 +202,13 @@
     utils/channel_manager.cpp
     utils/channel_monitor.cpp
     utils/child_supervision.cpp
-    utils/dataset_updater.cpp
     utils/flash.cpp
     utils/heap.cpp
     utils/jam_detector.cpp
     utils/lookup_table.cpp
     utils/otns.cpp
     utils/parse_cmdline.cpp
+    utils/ping_sender.cpp
     utils/slaac_address.cpp
 )
 
diff --git a/src/core/Makefile.am b/src/core/Makefile.am
index 8daddd7..01c5f33 100644
--- a/src/core/Makefile.am
+++ b/src/core/Makefile.am
@@ -110,6 +110,7 @@
 SOURCES_COMMON                                  = \
     api/backbone_router_api.cpp                   \
     api/backbone_router_ftd_api.cpp               \
+    api/border_agent_api.cpp                      \
     api/border_router_api.cpp                     \
     api/channel_manager_api.cpp                   \
     api/channel_monitor_api.cpp                   \
@@ -124,6 +125,7 @@
     api/diags_api.cpp                             \
     api/dns_api.cpp                               \
     api/entropy_api.cpp                           \
+    api/error_api.cpp                             \
     api/heap_api.cpp                              \
     api/icmp6_api.cpp                             \
     api/instance_api.cpp                          \
@@ -139,10 +141,13 @@
     api/netdata_api.cpp                           \
     api/netdiag_api.cpp                           \
     api/network_time_api.cpp                      \
+    api/ping_sender_api.cpp                       \
     api/random_crypto_api.cpp                     \
     api/random_noncrypto_api.cpp                  \
     api/server_api.cpp                            \
     api/sntp_api.cpp                              \
+    api/srp_client_api.cpp                        \
+    api/srp_server_api.cpp                        \
     api/tasklet_api.cpp                           \
     api/thread_api.cpp                            \
     api/thread_ftd_api.cpp                        \
@@ -153,10 +158,14 @@
     backbone_router/bbr_manager.cpp               \
     backbone_router/multicast_listeners_table.cpp \
     backbone_router/ndproxy_table.cpp             \
+    border_router/infra_if_platform.cpp           \
+    border_router/router_advertisement.cpp        \
+    border_router/routing_manager.cpp             \
     coap/coap.cpp                                 \
     coap/coap_message.cpp                         \
     coap/coap_secure.cpp                          \
     common/crc16.cpp                              \
+    common/error.cpp                              \
     common/instance.cpp                           \
     common/logging.cpp                            \
     common/message.cpp                            \
@@ -196,6 +205,7 @@
     meshcop/dataset_local.cpp                     \
     meshcop/dataset_manager.cpp                   \
     meshcop/dataset_manager_ftd.cpp               \
+    meshcop/dataset_updater.cpp                   \
     meshcop/dtls.cpp                              \
     meshcop/energy_scan_client.cpp                \
     meshcop/joiner.cpp                            \
@@ -209,7 +219,8 @@
     net/dhcp6_client.cpp                          \
     net/dhcp6_server.cpp                          \
     net/dns_client.cpp                            \
-    net/dns_headers.cpp                           \
+    net/dns_types.cpp                             \
+    net/dnssd_server.cpp                          \
     net/icmp6.cpp                                 \
     net/ip6.cpp                                   \
     net/ip6_address.cpp                           \
@@ -218,6 +229,9 @@
     net/ip6_mpl.cpp                               \
     net/netif.cpp                                 \
     net/sntp_client.cpp                           \
+    net/socket.cpp                                \
+    net/srp_client.cpp                            \
+    net/srp_server.cpp                            \
     net/udp6.cpp                                  \
     radio/radio.cpp                               \
     radio/radio_callbacks.cpp                     \
@@ -251,6 +265,7 @@
     thread/network_data_leader_ftd.cpp            \
     thread/network_data_local.cpp                 \
     thread/network_data_notifier.cpp              \
+    thread/network_data_service.cpp               \
     thread/network_diagnostic.cpp                 \
     thread/panid_query_server.cpp                 \
     thread/radio_selector.cpp                     \
@@ -264,13 +279,13 @@
     utils/channel_manager.cpp                     \
     utils/channel_monitor.cpp                     \
     utils/child_supervision.cpp                   \
-    utils/dataset_updater.cpp                     \
     utils/flash.cpp                               \
     utils/heap.cpp                                \
     utils/jam_detector.cpp                        \
     utils/lookup_table.cpp                        \
     utils/otns.cpp                                \
     utils/parse_cmdline.cpp                       \
+    utils/ping_sender.cpp                         \
     utils/slaac_address.cpp                       \
     $(NULL)
 
@@ -280,12 +295,14 @@
 
 libopenthread_radio_a_SOURCES              = \
     api/diags_api.cpp                        \
+    api/error_api.cpp                        \
     api/heap_api.cpp                         \
     api/instance_api.cpp                     \
     api/link_raw_api.cpp                     \
     api/logging_api.cpp                      \
     api/random_noncrypto_api.cpp             \
     api/tasklet_api.cpp                      \
+    common/error.cpp                         \
     common/instance.cpp                      \
     common/logging.cpp                       \
     common/random_manager.cpp                \
@@ -339,13 +356,14 @@
 endif # OPENTHREAD_ENABLE_VENDOR_EXTENSION
 
 HEADERS_COMMON                                  = \
-    openthread-core-config.h                      \
     backbone_router/backbone_tmf.hpp              \
     backbone_router/bbr_leader.hpp                \
     backbone_router/bbr_local.hpp                 \
     backbone_router/bbr_manager.hpp               \
     backbone_router/multicast_listeners_table.hpp \
     backbone_router/ndproxy_table.hpp             \
+    border_router/router_advertisement.hpp        \
+    border_router/routing_manager.hpp             \
     coap/coap.hpp                                 \
     coap/coap_message.hpp                         \
     coap/coap_secure.hpp                          \
@@ -357,11 +375,13 @@
     common/debug.hpp                              \
     common/encoding.hpp                           \
     common/equatable.hpp                          \
+    common/error.hpp                              \
     common/extension.hpp                          \
     common/instance.hpp                           \
+    common/iterator_utils.hpp                     \
     common/linked_list.hpp                        \
-    common/locator.hpp                            \
     common/locator-getters.hpp                    \
+    common/locator.hpp                            \
     common/logging.hpp                            \
     common/message.hpp                            \
     common/new.hpp                                \
@@ -393,6 +413,8 @@
     config/dhcp6_server.h                         \
     config/diag.h                                 \
     config/dns_client.h                           \
+    config/dnssd_server.h                         \
+    config/dtls.h                                 \
     config/ip6.h                                  \
     config/joiner.h                               \
     config/link_quality.h                         \
@@ -403,9 +425,12 @@
     config/openthread-core-config-check.h         \
     config/openthread-core-default-config.h       \
     config/parent_search.h                        \
+    config/ping_sender.h                          \
     config/platform.h                             \
     config/radio_link.h                           \
     config/sntp_client.h                          \
+    config/srp_client.h                           \
+    config/srp_server.h                           \
     config/time_sync.h                            \
     config/tmf.h                                  \
     crypto/aes_ccm.hpp                            \
@@ -414,7 +439,7 @@
     crypto/hkdf_sha256.hpp                        \
     crypto/hmac_sha256.hpp                        \
     crypto/mbedtls.hpp                            \
-    crypto/pbkdf2_cmac.h                          \
+    crypto/pbkdf2_cmac.hpp                        \
     crypto/sha256.hpp                             \
     diags/factory_diags.hpp                       \
     mac/channel_mask.hpp                          \
@@ -433,6 +458,7 @@
     meshcop/dataset.hpp                           \
     meshcop/dataset_local.hpp                     \
     meshcop/dataset_manager.hpp                   \
+    meshcop/dataset_updater.hpp                   \
     meshcop/dtls.hpp                              \
     meshcop/energy_scan_client.hpp                \
     meshcop/joiner.hpp                            \
@@ -447,7 +473,8 @@
     net/dhcp6_client.hpp                          \
     net/dhcp6_server.hpp                          \
     net/dns_client.hpp                            \
-    net/dns_headers.hpp                           \
+    net/dns_types.hpp                             \
+    net/dnssd_server.hpp                          \
     net/icmp6.hpp                                 \
     net/ip6.hpp                                   \
     net/ip6_address.hpp                           \
@@ -457,8 +484,12 @@
     net/netif.hpp                                 \
     net/sntp_client.hpp                           \
     net/socket.hpp                                \
+    net/srp_client.hpp                            \
+    net/srp_server.hpp                            \
     net/tcp.hpp                                   \
     net/udp6.hpp                                  \
+    openthread-core-config.h                      \
+    radio/max_power_table.hpp                     \
     radio/radio.hpp                               \
     radio/trel_interface.hpp                      \
     radio/trel_link.hpp                           \
@@ -492,6 +523,7 @@
     thread/network_data_leader_ftd.hpp            \
     thread/network_data_local.hpp                 \
     thread/network_data_notifier.hpp              \
+    thread/network_data_service.hpp               \
     thread/network_data_tlvs.hpp                  \
     thread/network_diagnostic.hpp                 \
     thread/network_diagnostic_tlvs.hpp            \
@@ -508,13 +540,13 @@
     utils/channel_manager.hpp                     \
     utils/channel_monitor.hpp                     \
     utils/child_supervision.hpp                   \
-    utils/dataset_updater.hpp                     \
     utils/flash.hpp                               \
     utils/heap.hpp                                \
     utils/jam_detector.hpp                        \
     utils/lookup_table.hpp                        \
     utils/otns.hpp                                \
     utils/parse_cmdline.hpp                       \
+    utils/ping_sender.hpp                         \
     utils/slaac_address.hpp                       \
     $(NULL)
 
diff --git a/src/core/api/backbone_router_ftd_api.cpp b/src/core/api/backbone_router_ftd_api.cpp
index 82bd376..79ccdf5 100644
--- a/src/core/api/backbone_router_ftd_api.cpp
+++ b/src/core/api/backbone_router_ftd_api.cpp
@@ -75,16 +75,9 @@
 
 otError otBackboneRouterRegister(otInstance *aInstance)
 {
-    otError error = OT_ERROR_NONE;
-
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    SuccessOrExit(error = instance.Get<BackboneRouter::Local>().AddService(true /* Force registration */));
-
-    instance.Get<NetworkData::Notifier>().HandleServerDataUpdated();
-
-exit:
-    return error;
+    return instance.Get<BackboneRouter::Local>().AddService(true /* Force registration */);
 }
 
 uint8_t otBackboneRouterGetRegistrationJitter(otInstance *aInstance)
diff --git a/src/core/api/border_router_api.cpp b/src/core/api/border_router_api.cpp
index bf5b66f..10f9d5e 100644
--- a/src/core/api/border_router_api.cpp
+++ b/src/core/api/border_router_api.cpp
@@ -37,11 +37,32 @@
 
 #include <openthread/border_router.h>
 
+#include "border_router/routing_manager.hpp"
 #include "common/debug.hpp"
 #include "common/instance.hpp"
 
 using namespace ot;
 
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+otError otBorderRoutingInit(otInstance *        aInstance,
+                            uint32_t            aInfraIfIndex,
+                            bool                aInfraIfIsRunning,
+                            const otIp6Address *aInfraIfLinkLocalAddress)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<BorderRouter::RoutingManager>().Init(
+        aInfraIfIndex, aInfraIfIsRunning, static_cast<const Ip6::Address *>(aInfraIfLinkLocalAddress));
+}
+
+otError otBorderRoutingSetEnabled(otInstance *aInstance, bool aEnabled)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<BorderRouter::RoutingManager>().SetEnabled(aEnabled);
+}
+#endif
+
 otError otBorderRouterGetNetData(otInstance *aInstance, bool aStable, uint8_t *aData, uint8_t *aDataLength)
 {
     Instance &instance = *static_cast<Instance *>(aInstance);
@@ -53,7 +74,7 @@
 
 otError otBorderRouterAddOnMeshPrefix(otInstance *aInstance, const otBorderRouterConfig *aConfig)
 {
-    otError                                error;
+    Error                                  error;
     Instance &                             instance = *static_cast<Instance *>(aInstance);
     const NetworkData::OnMeshPrefixConfig *config   = static_cast<const NetworkData::OnMeshPrefixConfig *>(aConfig);
 
@@ -78,7 +99,7 @@
 
 otError otBorderRouterRemoveOnMeshPrefix(otInstance *aInstance, const otIp6Prefix *aPrefix)
 {
-    otError            error    = OT_ERROR_NONE;
+    Error              error    = kErrorNone;
     Instance &         instance = *static_cast<Instance *>(aInstance);
     const Ip6::Prefix *prefix   = static_cast<const Ip6::Prefix *>(aPrefix);
 
@@ -87,7 +108,7 @@
 #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
     error = instance.Get<BackboneRouter::Local>().RemoveDomainPrefix(*prefix);
 
-    if (error == OT_ERROR_NOT_FOUND)
+    if (error == kErrorNotFound)
 #endif
     {
         error = instance.Get<NetworkData::Local>().RemoveOnMeshPrefix(*prefix);
@@ -145,7 +166,7 @@
 
     instance.Get<NetworkData::Notifier>().HandleServerDataUpdated();
 
-    return OT_ERROR_NONE;
+    return kErrorNone;
 }
 
 #endif // OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
diff --git a/src/core/api/coap_api.cpp b/src/core/api/coap_api.cpp
index b84ec18..8507fcb 100644
--- a/src/core/api/coap_api.cpp
+++ b/src/core/api/coap_api.cpp
@@ -101,18 +101,18 @@
     return static_cast<Coap::Message *>(aMessage)->AppendUriPathOptions(aUriPath);
 }
 
-uint16_t otCoapBlockSizeFromExponent(otCoapBlockSize aSize)
+uint16_t otCoapBlockSizeFromExponent(otCoapBlockSzx aSize)
 {
     return static_cast<uint16_t>(
         1 << (static_cast<uint8_t>(aSize) + static_cast<uint8_t>(Coap::Message::kBlockSzxBase)));
 }
 
-otError otCoapMessageAppendBlock2Option(otMessage *aMessage, uint32_t aNum, bool aMore, otCoapBlockSize aSize)
+otError otCoapMessageAppendBlock2Option(otMessage *aMessage, uint32_t aNum, bool aMore, otCoapBlockSzx aSize)
 {
     return static_cast<Coap::Message *>(aMessage)->AppendBlockOption(Coap::Message::kBlockType2, aNum, aMore, aSize);
 }
 
-otError otCoapMessageAppendBlock1Option(otMessage *aMessage, uint32_t aNum, bool aMore, otCoapBlockSize aSize)
+otError otCoapMessageAppendBlock1Option(otMessage *aMessage, uint32_t aNum, bool aMore, otCoapBlockSzx aSize)
 {
     return static_cast<Coap::Message *>(aMessage)->AppendBlockOption(Coap::Message::kBlockType1, aNum, aMore, aSize);
 }
@@ -214,6 +214,34 @@
     return static_cast<Coap::Option::Iterator *>(aIterator)->ReadOptionValue(aValue);
 }
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+otError otCoapSendRequestBlockWiseWithParameters(otInstance *                aInstance,
+                                                 otMessage *                 aMessage,
+                                                 const otMessageInfo *       aMessageInfo,
+                                                 otCoapResponseHandler       aHandler,
+                                                 void *                      aContext,
+                                                 const otCoapTxParameters *  aTxParameters,
+                                                 otCoapBlockwiseTransmitHook aTransmitHook,
+                                                 otCoapBlockwiseReceiveHook  aReceiveHook)
+{
+    Error                     error;
+    Instance &                instance     = *static_cast<Instance *>(aInstance);
+    const Coap::TxParameters &txParameters = Coap::TxParameters::From(aTxParameters);
+
+    if (aTxParameters != nullptr)
+    {
+        VerifyOrExit(txParameters.IsValid(), error = kErrorInvalidArgs);
+    }
+
+    error = instance.GetApplicationCoap().SendMessage(*static_cast<Coap::Message *>(aMessage),
+                                                      *static_cast<const Ip6::MessageInfo *>(aMessageInfo),
+                                                      txParameters, aHandler, aContext, aTransmitHook, aReceiveHook);
+
+exit:
+    return error;
+}
+#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+
 otError otCoapSendRequestWithParameters(otInstance *              aInstance,
                                         otMessage *               aMessage,
                                         const otMessageInfo *     aMessageInfo,
@@ -221,13 +249,13 @@
                                         void *                    aContext,
                                         const otCoapTxParameters *aTxParameters)
 {
-    otError                   error;
+    Error                     error;
     Instance &                instance     = *static_cast<Instance *>(aInstance);
     const Coap::TxParameters &txParameters = Coap::TxParameters::From(aTxParameters);
 
     if (aTxParameters != nullptr)
     {
-        VerifyOrExit(txParameters.IsValid(), error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit(txParameters.IsValid(), error = kErrorInvalidArgs);
     }
 
     error = instance.GetApplicationCoap().SendMessage(*static_cast<Coap::Message *>(aMessage),
@@ -252,6 +280,22 @@
     return instance.GetApplicationCoap().Stop();
 }
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+void otCoapAddBlockWiseResource(otInstance *aInstance, otCoapBlockwiseResource *aResource)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    instance.GetApplicationCoap().AddBlockWiseResource(*static_cast<Coap::ResourceBlockWise *>(aResource));
+}
+
+void otCoapRemoveBlockWiseResource(otInstance *aInstance, otCoapBlockwiseResource *aResource)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    instance.GetApplicationCoap().RemoveBlockWiseResource(*static_cast<Coap::ResourceBlockWise *>(aResource));
+}
+#endif
+
 void otCoapAddResource(otInstance *aInstance, otCoapResource *aResource)
 {
     Instance &instance = *static_cast<Instance *>(aInstance);
@@ -273,6 +317,22 @@
     instance.GetApplicationCoap().SetDefaultHandler(aHandler, aContext);
 }
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+otError otCoapSendResponseBlockWiseWithParameters(otInstance *                aInstance,
+                                                  otMessage *                 aMessage,
+                                                  const otMessageInfo *       aMessageInfo,
+                                                  const otCoapTxParameters *  aTxParameters,
+                                                  void *                      aContext,
+                                                  otCoapBlockwiseTransmitHook aTransmitHook)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.GetApplicationCoap().SendMessage(
+        *static_cast<Coap::Message *>(aMessage), *static_cast<const Ip6::MessageInfo *>(aMessageInfo),
+        Coap::TxParameters::From(aTxParameters), nullptr, aContext, aTransmitHook, nullptr);
+}
+#endif
+
 otError otCoapSendResponseWithParameters(otInstance *              aInstance,
                                          otMessage *               aMessage,
                                          const otMessageInfo *     aMessageInfo,
diff --git a/src/core/api/coap_secure_api.cpp b/src/core/api/coap_secure_api.cpp
index 942bc78..6991316 100644
--- a/src/core/api/coap_secure_api.cpp
+++ b/src/core/api/coap_secure_api.cpp
@@ -151,6 +151,21 @@
     instance.GetApplicationCoapSecure().Stop();
 }
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+otError otCoapSecureSendRequestBlockWise(otInstance *                aInstance,
+                                         otMessage *                 aMessage,
+                                         otCoapResponseHandler       aHandler,
+                                         void *                      aContext,
+                                         otCoapBlockwiseTransmitHook aTransmitHook,
+                                         otCoapBlockwiseReceiveHook  aReceiveHook)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.GetApplicationCoapSecure().SendMessage(*static_cast<Coap::Message *>(aMessage), aHandler, aContext,
+                                                           aTransmitHook, aReceiveHook);
+}
+#endif
+
 otError otCoapSecureSendRequest(otInstance *          aInstance,
                                 otMessage *           aMessage,
                                 otCoapResponseHandler aHandler,
@@ -161,6 +176,22 @@
     return instance.GetApplicationCoapSecure().SendMessage(*static_cast<Coap::Message *>(aMessage), aHandler, aContext);
 }
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+void otCoapSecureAddBlockWiseResource(otInstance *aInstance, otCoapBlockwiseResource *aResource)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    instance.GetApplicationCoapSecure().AddBlockWiseResource(*static_cast<Coap::ResourceBlockWise *>(aResource));
+}
+
+void otCoapSecureRemoveBlockWiseResource(otInstance *aInstance, otCoapBlockwiseResource *aResource)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    instance.GetApplicationCoapSecure().RemoveBlockWiseResource(*static_cast<Coap::ResourceBlockWise *>(aResource));
+}
+#endif
+
 void otCoapSecureAddResource(otInstance *aInstance, otCoapResource *aResource)
 {
     Instance &instance = *static_cast<Instance *>(aInstance);
@@ -191,6 +222,21 @@
     instance.GetApplicationCoapSecure().SetDefaultHandler(aHandler, aContext);
 }
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+otError otCoapSecureSendResponseBlockWise(otInstance *                aInstance,
+                                          otMessage *                 aMessage,
+                                          const otMessageInfo *       aMessageInfo,
+                                          void *                      aContext,
+                                          otCoapBlockwiseTransmitHook aTransmitHook)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.GetApplicationCoapSecure().SendMessage(*static_cast<Coap::Message *>(aMessage),
+                                                           *static_cast<const Ip6::MessageInfo *>(aMessageInfo),
+                                                           nullptr, aContext, aTransmitHook);
+}
+#endif
+
 otError otCoapSecureSendResponse(otInstance *aInstance, otMessage *aMessage, const otMessageInfo *aMessageInfo)
 {
     Instance &instance = *static_cast<Instance *>(aInstance);
diff --git a/src/core/api/commissioner_api.cpp b/src/core/api/commissioner_api.cpp
index ae5c2cd..cda781b 100644
--- a/src/core/api/commissioner_api.cpp
+++ b/src/core/api/commissioner_api.cpp
@@ -60,7 +60,7 @@
 
 otError otCommissionerAddJoiner(otInstance *aInstance, const otExtAddress *aEui64, const char *aPskd, uint32_t aTimeout)
 {
-    otError                error;
+    Error                  error;
     MeshCoP::Commissioner &commissioner = static_cast<Instance *>(aInstance)->Get<MeshCoP::Commissioner>();
 
     if (aEui64 == nullptr)
@@ -95,7 +95,7 @@
 
 otError otCommissionerRemoveJoiner(otInstance *aInstance, const otExtAddress *aEui64)
 {
-    otError                error;
+    Error                  error;
     MeshCoP::Commissioner &commissioner = static_cast<Instance *>(aInstance)->Get<MeshCoP::Commissioner>();
 
     if (aEui64 == nullptr)
diff --git a/src/core/api/dataset_updater_api.cpp b/src/core/api/dataset_updater_api.cpp
index 63a61d0..fd9b862 100644
--- a/src/core/api/dataset_updater_api.cpp
+++ b/src/core/api/dataset_updater_api.cpp
@@ -37,7 +37,7 @@
 
 #include "common/instance.hpp"
 #include "common/locator-getters.hpp"
-#include "utils/dataset_updater.hpp"
+#include "meshcop/dataset_updater.hpp"
 
 using namespace ot;
 
@@ -46,27 +46,26 @@
 otError otDatasetUpdaterRequestUpdate(otInstance *                aInstance,
                                       const otOperationalDataset *aDataset,
                                       otDatasetUpdaterCallback    aCallback,
-                                      void *                      aContext,
-                                      uint32_t                    aReryWaitInterval)
+                                      void *                      aContext)
 {
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    return instance.Get<Utils::DatasetUpdater>().RequestUpdate(*static_cast<const MeshCoP::Dataset::Info *>(aDataset),
-                                                               aCallback, aContext, aReryWaitInterval);
+    return instance.Get<MeshCoP::DatasetUpdater>().RequestUpdate(*static_cast<const MeshCoP::Dataset::Info *>(aDataset),
+                                                                 aCallback, aContext);
 }
 
 void otDatasetUpdaterCancelUpdate(otInstance *aInstance)
 {
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    instance.Get<Utils::DatasetUpdater>().CancelUpdate();
+    instance.Get<MeshCoP::DatasetUpdater>().CancelUpdate();
 }
 
 bool otDatasetUpdaterIsUpdateOngoing(otInstance *aInstance)
 {
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    return instance.Get<Utils::DatasetUpdater>().IsUpdateOngoing();
+    return instance.Get<MeshCoP::DatasetUpdater>().IsUpdateOngoing();
 }
 
 #endif // OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE && OPENTHREAD_FTD
diff --git a/src/core/api/dns_api.cpp b/src/core/api/dns_api.cpp
index b8bc4fb..5038a47 100644
--- a/src/core/api/dns_api.cpp
+++ b/src/core/api/dns_api.cpp
@@ -33,19 +33,179 @@
 
 #include "openthread-core-config.h"
 
-#include <openthread/dns.h>
+#include <openthread/dns_client.h>
 
 #include "common/instance.hpp"
 #include "common/locator-getters.hpp"
 #include "net/dns_client.hpp"
+#include "net/dns_types.hpp"
 
 using namespace ot;
 
+void otDnsInitTxtEntryIterator(otDnsTxtEntryIterator *aIterator, const uint8_t *aTxtData, uint16_t aTxtDataLength)
+{
+    static_cast<Dns::TxtEntry::Iterator *>(aIterator)->Init(aTxtData, aTxtDataLength);
+}
+
+otError otDnsGetNextTxtEntry(otDnsTxtEntryIterator *aIterator, otDnsTxtEntry *aEntry)
+{
+    return static_cast<Dns::TxtEntry::Iterator *>(aIterator)->GetNextEntry(*static_cast<Dns::TxtEntry *>(aEntry));
+}
+
 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
-otError otDnsClientQuery(otInstance *aInstance, const otDnsQuery *aQuery, otDnsResponseHandler aHandler, void *aContext)
+
+const otDnsQueryConfig *otDnsClientGetDefaultConfig(otInstance *aInstance)
 {
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    return instance.Get<Dns::Client>().Query(*static_cast<const Dns::Client::QueryInfo *>(aQuery), aHandler, aContext);
+    return &instance.Get<Dns::Client>().GetDefaultConfig();
 }
-#endif
+
+void otDnsClientSetDefaultConfig(otInstance *aInstance, const otDnsQueryConfig *aConfig)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    if (aConfig != nullptr)
+    {
+        instance.Get<Dns::Client>().SetDefaultConfig(*static_cast<const Dns::Client::QueryConfig *>(aConfig));
+    }
+    else
+    {
+        instance.Get<Dns::Client>().ResetDefaultConfig();
+    }
+}
+
+otError otDnsClientResolveAddress(otInstance *            aInstance,
+                                  const char *            aHostName,
+                                  otDnsAddressCallback    aCallback,
+                                  void *                  aContext,
+                                  const otDnsQueryConfig *aConfig)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Dns::Client>().ResolveAddress(aHostName, aCallback, aContext,
+                                                      static_cast<const Dns::Client::QueryConfig *>(aConfig));
+}
+
+otError otDnsAddressResponseGetHostName(const otDnsAddressResponse *aResponse,
+                                        char *                      aNameBuffer,
+                                        uint16_t                    aNameBufferSize)
+{
+    const Dns::Client::AddressResponse &response = *static_cast<const Dns::Client::AddressResponse *>(aResponse);
+
+    return response.GetHostName(aNameBuffer, aNameBufferSize);
+}
+
+otError otDnsAddressResponseGetAddress(const otDnsAddressResponse *aResponse,
+                                       uint16_t                    aIndex,
+                                       otIp6Address *              aAddress,
+                                       uint32_t *                  aTtl)
+{
+    const Dns::Client::AddressResponse &response = *static_cast<const Dns::Client::AddressResponse *>(aResponse);
+    uint32_t                            ttl;
+
+    return response.GetAddress(aIndex, *static_cast<Ip6::Address *>(aAddress), (aTtl != nullptr) ? *aTtl : ttl);
+}
+
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+
+otError otDnsClientBrowse(otInstance *            aInstance,
+                          const char *            aServiceName,
+                          otDnsBrowseCallback     aCallback,
+                          void *                  aContext,
+                          const otDnsQueryConfig *aConfig)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Dns::Client>().Browse(aServiceName, aCallback, aContext,
+                                              static_cast<const Dns::Client::QueryConfig *>(aConfig));
+}
+
+otError otDnsBrowseResponseGetServiceName(const otDnsBrowseResponse *aResponse,
+                                          char *                     aNameBuffer,
+                                          uint16_t                   aNameBufferSize)
+{
+    const Dns::Client::BrowseResponse &response = *static_cast<const Dns::Client::BrowseResponse *>(aResponse);
+
+    return response.GetServiceName(aNameBuffer, aNameBufferSize);
+}
+
+otError otDnsBrowseResponseGetServiceInstance(const otDnsBrowseResponse *aResponse,
+                                              uint16_t                   aIndex,
+                                              char *                     aLabelBuffer,
+                                              uint8_t                    aLabelBufferSize)
+{
+    const Dns::Client::BrowseResponse &response = *static_cast<const Dns::Client::BrowseResponse *>(aResponse);
+
+    return response.GetServiceInstance(aIndex, aLabelBuffer, aLabelBufferSize);
+}
+
+otError otDnsBrowseResponseGetServiceInfo(const otDnsBrowseResponse *aResponse,
+                                          const char *               aInstanceLabel,
+                                          otDnsServiceInfo *         aServiceInfo)
+{
+    const Dns::Client::BrowseResponse &response = *static_cast<const Dns::Client::BrowseResponse *>(aResponse);
+
+    return response.GetServiceInfo(aInstanceLabel, *static_cast<Dns::Client::ServiceInfo *>(aServiceInfo));
+}
+
+otError otDnsBrowseResponseGetHostAddress(const otDnsBrowseResponse *aResponse,
+                                          const char *               aHostName,
+                                          uint16_t                   aIndex,
+                                          otIp6Address *             aAddress,
+                                          uint32_t *                 aTtl)
+{
+    const Dns::Client::BrowseResponse &response = *static_cast<const Dns::Client::BrowseResponse *>(aResponse);
+    uint32_t                           ttl;
+
+    return response.GetHostAddress(aHostName, aIndex, *static_cast<Ip6::Address *>(aAddress),
+                                   aTtl != nullptr ? *aTtl : ttl);
+}
+
+otError otDnsClientResolveService(otInstance *            aInstance,
+                                  const char *            aInstanceLabel,
+                                  const char *            aServiceName,
+                                  otDnsServiceCallback    aCallback,
+                                  void *                  aContext,
+                                  const otDnsQueryConfig *aConfig)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Dns::Client>().ResolveService(aInstanceLabel, aServiceName, aCallback, aContext,
+                                                      static_cast<const Dns::Client::QueryConfig *>(aConfig));
+}
+
+otError otDnsServiceResponseGetServiceName(const otDnsServiceResponse *aResponse,
+                                           char *                      aLabelBuffer,
+                                           uint8_t                     aLabelBufferSize,
+                                           char *                      aNameBuffer,
+                                           uint16_t                    aNameBufferSize)
+{
+    const Dns::Client::ServiceResponse &response = *static_cast<const Dns::Client::ServiceResponse *>(aResponse);
+
+    return response.GetServiceName(aLabelBuffer, aLabelBufferSize, aNameBuffer, aNameBufferSize);
+}
+
+otError otDnsServiceResponseGetServiceInfo(const otDnsServiceResponse *aResponse, otDnsServiceInfo *aServiceInfo)
+{
+    const Dns::Client::ServiceResponse &response = *static_cast<const Dns::Client::ServiceResponse *>(aResponse);
+
+    return response.GetServiceInfo(*static_cast<Dns::Client::ServiceInfo *>(aServiceInfo));
+}
+
+otError otDnsServiceResponseGetHostAddress(const otDnsServiceResponse *aResponse,
+                                           const char *                aHostName,
+                                           uint16_t                    aIndex,
+                                           otIp6Address *              aAddress,
+                                           uint32_t *                  aTtl)
+{
+    const Dns::Client::ServiceResponse &response = *static_cast<const Dns::Client::ServiceResponse *>(aResponse);
+    uint32_t                            ttl;
+
+    return response.GetHostAddress(aHostName, aIndex, *static_cast<Ip6::Address *>(aAddress),
+                                   (aTtl != nullptr) ? *aTtl : ttl);
+}
+
+#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+
+#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
diff --git a/src/core/api/error_api.cpp b/src/core/api/error_api.cpp
new file mode 100644
index 0000000..cc2b18d
--- /dev/null
+++ b/src/core/api/error_api.cpp
@@ -0,0 +1,43 @@
+/*
+ *  Copyright (c) 2017-2021, The OpenThread Authors.
+ *  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
+ *   This file implements the OpenThread error code functions.
+ */
+
+#include "openthread-core-config.h"
+
+#include "common/error.hpp"
+
+using namespace ot;
+
+const char *otThreadErrorToString(otError aError)
+{
+    return ErrorToString(aError);
+}
diff --git a/src/core/api/heap_api.cpp b/src/core/api/heap_api.cpp
index d921954..e3cd90d 100644
--- a/src/core/api/heap_api.cpp
+++ b/src/core/api/heap_api.cpp
@@ -37,8 +37,6 @@
 
 #include "common/instance.hpp"
 
-#if !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE || OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
-
 #if OPENTHREAD_RADIO
 
 void *otHeapCAlloc(size_t aCount, size_t aSize)
@@ -61,25 +59,14 @@
     OT_ASSERT(false);
 }
 
-#else // OPENTHREAD_RADIO
-
+#else  // OPENTHREAD_RADIO
 void *otHeapCAlloc(size_t aCount, size_t aSize)
 {
-    return ot::Instance::Get().HeapCAlloc(aCount, aSize);
+    return ot::Instance::HeapCAlloc(aCount, aSize);
 }
 
 void otHeapFree(void *aPointer)
 {
-    ot::Instance::Get().HeapFree(aPointer);
+    ot::Instance::HeapFree(aPointer);
 }
-
 #endif // OPENTHREAD_RADIO
-
-#endif // !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE || OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
-
-#if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE && !OPENTHREAD_RADIO
-void otHeapSetCAllocFree(otHeapCAllocFn aCAlloc, otHeapFreeFn aFree)
-{
-    ot::Instance::HeapSetCAllocFree(aCAlloc, aFree);
-}
-#endif // OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
diff --git a/src/core/api/instance_api.cpp b/src/core/api/instance_api.cpp
index 2ee1083..e6482d5 100644
--- a/src/core/api/instance_api.cpp
+++ b/src/core/api/instance_api.cpp
@@ -42,6 +42,14 @@
 #include "common/new.hpp"
 #include "radio/radio.hpp"
 
+#ifdef __ANDROID__
+#ifdef OPENTHREAD_ENABLE_ANDROID_NDK
+#include <sys/system_properties.h>
+#else
+#include <cutils/properties.h>
+#endif
+#endif
+
 using namespace ot;
 
 #if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
@@ -134,6 +142,24 @@
      * image will be undefined and may change.
      */
 
+#ifdef __ANDROID__
+
+#ifdef OPENTHREAD_ENABLE_ANDROID_NDK
+    static char sVersion[100 + PROP_VALUE_MAX];
+    char        dateTime[PROP_VALUE_MAX];
+
+    __system_property_get("ro.build.date", dateTime);
+#else
+    static char sVersion[100 + PROPERTY_VALUE_MAX];
+    char        dateTime[PROPERTY_VALUE_MAX];
+
+    property_get("ro.build.date", dateTime, "Thu Jan 1 1970 UTC 00:00:00");
+#endif
+
+    snprintf(sVersion, sizeof(sVersion), "%s/%s ;%s ; %s", PACKAGE_NAME, PACKAGE_VERSION,
+             OPENTHREAD_CONFIG_PLATFORM_INFO, dateTime);
+#else
+
 #ifdef PLATFORM_VERSION_ATTR_PREFIX
     PLATFORM_VERSION_ATTR_PREFIX
 #else
@@ -148,6 +174,8 @@
 #endif
         ; // Trailing semicolon to end statement.
 
+#endif
+
     return sVersion;
 }
 
diff --git a/src/core/api/ip6_api.cpp b/src/core/api/ip6_api.cpp
index 65f2ad4..7439265 100644
--- a/src/core/api/ip6_api.cpp
+++ b/src/core/api/ip6_api.cpp
@@ -46,11 +46,11 @@
 
 otError otIp6SetEnabled(otInstance *aInstance, bool aEnabled)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE
-    VerifyOrExit(!instance.Get<Mac::LinkRaw>().IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(!instance.Get<Mac::LinkRaw>().IsEnabled(), error = kErrorInvalidState);
 #endif
 
     if (aEnabled)
@@ -246,12 +246,12 @@
 
 otError otIp6SelectSourceAddress(otInstance *aInstance, otMessageInfo *aMessageInfo)
 {
-    otError                         error    = OT_ERROR_NONE;
+    Error                           error    = kErrorNone;
     Instance &                      instance = *static_cast<Instance *>(aInstance);
     const Ip6::NetifUnicastAddress *netifAddr;
 
     netifAddr = instance.Get<Ip6::Ip6>().SelectSourceAddress(*static_cast<Ip6::MessageInfo *>(aMessageInfo));
-    VerifyOrExit(netifAddr != nullptr, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(netifAddr != nullptr, error = kErrorNotFound);
     aMessageInfo->mSockAddr = netifAddr->GetAddress();
 
 exit:
diff --git a/src/core/api/jam_detection_api.cpp b/src/core/api/jam_detection_api.cpp
index 6adcb14..c1a5bc5 100644
--- a/src/core/api/jam_detection_api.cpp
+++ b/src/core/api/jam_detection_api.cpp
@@ -48,7 +48,7 @@
 
     instance.Get<Utils::JamDetector>().SetRssiThreshold(aRssiThreshold);
 
-    return OT_ERROR_NONE;
+    return kErrorNone;
 }
 
 int8_t otJamDetectionGetRssiThreshold(otInstance *aInstance)
diff --git a/src/core/api/joiner_api.cpp b/src/core/api/joiner_api.cpp
index 9d82fbb..5562f41 100644
--- a/src/core/api/joiner_api.cpp
+++ b/src/core/api/joiner_api.cpp
@@ -80,7 +80,7 @@
 
 otError otJoinerSetDiscerner(otInstance *aInstance, otJoinerDiscerner *aDiscerner)
 {
-    otError          error  = OT_ERROR_NONE;
+    Error            error  = kErrorNone;
     MeshCoP::Joiner &joiner = static_cast<Instance *>(aInstance)->Get<MeshCoP::Joiner>();
 
     if (aDiscerner != nullptr)
diff --git a/src/core/api/link_api.cpp b/src/core/api/link_api.cpp
index 8c9f43a..7db8934 100644
--- a/src/core/api/link_api.cpp
+++ b/src/core/api/link_api.cpp
@@ -63,7 +63,7 @@
 
 otError otLinkSetChannel(otInstance *aInstance, uint8_t aChannel)
 {
-    otError   error;
+    Error     error;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE
@@ -74,7 +74,7 @@
     }
 #endif
 
-    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = kErrorInvalidState);
 
     SuccessOrExit(error = instance.Get<Mac::Mac>().SetPanChannel(aChannel));
     instance.Get<MeshCoP::ActiveDataset>().Clear();
@@ -93,10 +93,10 @@
 
 otError otLinkSetSupportedChannelMask(otInstance *aInstance, uint32_t aChannelMask)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = kErrorInvalidState);
 
     instance.Get<Mac::Mac>().SetSupportedChannelMask(static_cast<Mac::ChannelMask>(aChannelMask));
 
@@ -113,11 +113,11 @@
 
 otError otLinkSetExtendedAddress(otInstance *aInstance, const otExtAddress *aExtAddress)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
     OT_ASSERT(aExtAddress != nullptr);
-    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = kErrorInvalidState);
 
     instance.Get<Mac::Mac>().SetExtAddress(*static_cast<const Mac::ExtAddress *>(aExtAddress));
 
@@ -143,10 +143,10 @@
 
 otError otLinkSetPanId(otInstance *aInstance, otPanId aPanId)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = kErrorInvalidState);
 
     instance.Get<Mac::Mac>().SetPanId(aPanId);
     instance.Get<MeshCoP::ActiveDataset>().Clear();
@@ -378,11 +378,11 @@
 
 otError otLinkSetPromiscuous(otInstance *aInstance, bool aPromiscuous)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
     // cannot enable IEEE 802.15.4 promiscuous mode if the Thread interface is enabled
-    VerifyOrExit(!instance.Get<ThreadNetif>().IsUp(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(!instance.Get<ThreadNetif>().IsUp(), error = kErrorInvalidState);
 
     instance.Get<Mac::Mac>().SetPromiscuous(aPromiscuous);
 
@@ -392,11 +392,11 @@
 
 otError otLinkSetEnabled(otInstance *aInstance, bool aEnable)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
     // cannot disable the link layer if the Thread interface is enabled
-    VerifyOrExit(!instance.Get<ThreadNetif>().IsUp(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(!instance.Get<ThreadNetif>().IsUp(), error = kErrorInvalidState);
 
     instance.Get<Mac::Mac>().SetEnabled(aEnable);
 
@@ -490,10 +490,10 @@
 
 otError otLinkCslSetChannel(otInstance *aInstance, uint8_t aChannel)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(Radio::IsCslChannelValid(aChannel), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(Radio::IsCslChannelValid(aChannel), error = kErrorInvalidArgs);
 
     instance.Get<Mac::Mac>().SetCslChannel(aChannel);
 
@@ -508,10 +508,10 @@
 
 otError otLinkCslSetPeriod(otInstance *aInstance, uint16_t aPeriod)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit((aPeriod == 0 || kMinCslPeriod <= aPeriod), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit((aPeriod == 0 || kMinCslPeriod <= aPeriod), error = kErrorInvalidArgs);
     instance.Get<Mac::Mac>().SetCslPeriod(aPeriod);
 
 exit:
@@ -525,10 +525,10 @@
 
 otError otLinkCslSetTimeout(otInstance *aInstance, uint32_t aTimeout)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(kMaxCslTimeout >= aTimeout, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(kMaxCslTimeout >= aTimeout, error = kErrorInvalidArgs);
     instance.Get<Mac::Mac>().SetCslTimeout(aTimeout);
 
 exit:
diff --git a/src/core/api/link_raw_api.cpp b/src/core/api/link_raw_api.cpp
index 9bf138e..6d92243 100644
--- a/src/core/api/link_raw_api.cpp
+++ b/src/core/api/link_raw_api.cpp
@@ -71,10 +71,10 @@
 
 otError otLinkRawSetPromiscuous(otInstance *aInstance, bool aEnable)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = kErrorInvalidState);
     instance.Get<Radio>().SetPromiscuous(aEnable);
 
 exit:
@@ -83,10 +83,10 @@
 
 otError otLinkRawSleep(otInstance *aInstance)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = kErrorInvalidState);
 
     error = instance.Get<Radio>().Sleep();
 
@@ -129,10 +129,10 @@
 
 otError otLinkRawSrcMatchEnable(otInstance *aInstance, bool aEnable)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = kErrorInvalidState);
 
     instance.Get<Radio>().EnableSrcMatch(aEnable);
 
@@ -142,10 +142,10 @@
 
 otError otLinkRawSrcMatchAddShortEntry(otInstance *aInstance, uint16_t aShortAddress)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = kErrorInvalidState);
 
     error = instance.Get<Radio>().AddSrcMatchShortEntry(aShortAddress);
 
@@ -156,10 +156,10 @@
 otError otLinkRawSrcMatchAddExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress)
 {
     Mac::ExtAddress address;
-    otError         error    = OT_ERROR_NONE;
+    Error           error    = kErrorNone;
     Instance &      instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = kErrorInvalidState);
 
     address.Set(aExtAddress->m8, Mac::ExtAddress::kReverseByteOrder);
     error = instance.Get<Radio>().AddSrcMatchExtEntry(address);
@@ -170,10 +170,10 @@
 
 otError otLinkRawSrcMatchClearShortEntry(otInstance *aInstance, uint16_t aShortAddress)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = kErrorInvalidState);
     error = instance.Get<Radio>().ClearSrcMatchShortEntry(aShortAddress);
 
 exit:
@@ -183,10 +183,10 @@
 otError otLinkRawSrcMatchClearExtEntry(otInstance *aInstance, const otExtAddress *aExtAddress)
 {
     Mac::ExtAddress address;
-    otError         error    = OT_ERROR_NONE;
+    Error           error    = kErrorNone;
     Instance &      instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = kErrorInvalidState);
 
     address.Set(aExtAddress->m8, Mac::ExtAddress::kReverseByteOrder);
     error = instance.Get<Radio>().ClearSrcMatchExtEntry(address);
@@ -197,10 +197,10 @@
 
 otError otLinkRawSrcMatchClearShortEntries(otInstance *aInstance)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = kErrorInvalidState);
 
     instance.Get<Radio>().ClearSrcMatchShortEntries();
 
@@ -210,10 +210,10 @@
 
 otError otLinkRawSrcMatchClearExtEntries(otInstance *aInstance)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mac::LinkRaw>().IsEnabled(), error = kErrorInvalidState);
 
     instance.Get<Radio>().ClearSrcMatchExtEntries();
 
diff --git a/src/core/api/logging_api.cpp b/src/core/api/logging_api.cpp
index 571ae1d..c3851c3 100644
--- a/src/core/api/logging_api.cpp
+++ b/src/core/api/logging_api.cpp
@@ -51,7 +51,7 @@
 #if OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE
 otError otLoggingSetLevel(otLogLevel aLogLevel)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     if (aLogLevel <= OT_LOG_LEVEL_DEBG && aLogLevel >= OT_LOG_LEVEL_NONE)
     {
@@ -59,7 +59,7 @@
     }
     else
     {
-        error = OT_ERROR_INVALID_ARGS;
+        error = kErrorInvalidArgs;
     }
 
     return error;
diff --git a/src/core/api/multi_radio_api.cpp b/src/core/api/multi_radio_api.cpp
index 9be05c2..dd70ea6 100644
--- a/src/core/api/multi_radio_api.cpp
+++ b/src/core/api/multi_radio_api.cpp
@@ -49,13 +49,13 @@
                                     const otExtAddress *      aExtAddress,
                                     otMultiRadioNeighborInfo *aInfo)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
     Neighbor *neighbor;
 
     neighbor = instance.Get<NeighborTable>().FindNeighbor(*static_cast<const Mac::ExtAddress *>(aExtAddress),
                                                           Neighbor::kInStateAnyExceptInvalid);
-    VerifyOrExit(neighbor != NULL, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(neighbor != NULL, error = kErrorNotFound);
 
     neighbor->PopulateMultiRadioInfo(*aInfo);
 
diff --git a/src/core/api/netdata_api.cpp b/src/core/api/netdata_api.cpp
index 8afa0be..17d79b2 100644
--- a/src/core/api/netdata_api.cpp
+++ b/src/core/api/netdata_api.cpp
@@ -53,11 +53,11 @@
                                      otNetworkDataIterator *aIterator,
                                      otBorderRouterConfig * aConfig)
 {
-    otError                          error    = OT_ERROR_NONE;
+    Error                            error    = kErrorNone;
     Instance &                       instance = *static_cast<Instance *>(aInstance);
     NetworkData::OnMeshPrefixConfig *config   = static_cast<NetworkData::OnMeshPrefixConfig *>(aConfig);
 
-    VerifyOrExit(aIterator && aConfig, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aIterator && aConfig, error = kErrorInvalidArgs);
 
     error = instance.Get<NetworkData::Leader>().GetNextOnMeshPrefix(*aIterator, *config);
 
@@ -67,10 +67,10 @@
 
 otError otNetDataGetNextRoute(otInstance *aInstance, otNetworkDataIterator *aIterator, otExternalRouteConfig *aConfig)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(aIterator && aConfig, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aIterator && aConfig, error = kErrorInvalidArgs);
 
     error = instance.Get<NetworkData::Leader>().GetNextExternalRoute(
         *aIterator, *static_cast<NetworkData::ExternalRouteConfig *>(aConfig));
@@ -81,10 +81,10 @@
 
 otError otNetDataGetNextService(otInstance *aInstance, otNetworkDataIterator *aIterator, otServiceConfig *aConfig)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(aIterator && aConfig, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aIterator && aConfig, error = kErrorInvalidArgs);
 
     error = instance.Get<NetworkData::Leader>().GetNextService(*aIterator,
                                                                *static_cast<NetworkData::ServiceConfig *>(aConfig));
diff --git a/src/core/api/netdiag_api.cpp b/src/core/api/netdiag_api.cpp
index 5b75e0e..3e8b836 100644
--- a/src/core/api/netdiag_api.cpp
+++ b/src/core/api/netdiag_api.cpp
@@ -49,24 +49,17 @@
                                                                 *aIterator, *aNetworkDiagTlv);
 }
 
-void otThreadSetReceiveDiagnosticGetCallback(otInstance *                   aInstance,
-                                             otReceiveDiagnosticGetCallback aCallback,
-                                             void *                         aCallbackContext)
-{
-    Instance &instance = *static_cast<Instance *>(aInstance);
-
-    instance.Get<NetworkDiagnostic::NetworkDiagnostic>().SetReceiveDiagnosticGetCallback(aCallback, aCallbackContext);
-}
-
-otError otThreadSendDiagnosticGet(otInstance *        aInstance,
-                                  const otIp6Address *aDestination,
-                                  const uint8_t       aTlvTypes[],
-                                  uint8_t             aCount)
+otError otThreadSendDiagnosticGet(otInstance *                   aInstance,
+                                  const otIp6Address *           aDestination,
+                                  const uint8_t                  aTlvTypes[],
+                                  uint8_t                        aCount,
+                                  otReceiveDiagnosticGetCallback aCallback,
+                                  void *                         aCallbackContext)
 {
     Instance &instance = *static_cast<Instance *>(aInstance);
 
     return instance.Get<NetworkDiagnostic::NetworkDiagnostic>().SendDiagnosticGet(
-        *static_cast<const Ip6::Address *>(aDestination), aTlvTypes, aCount);
+        *static_cast<const Ip6::Address *>(aDestination), aTlvTypes, aCount, aCallback, aCallbackContext);
 }
 
 otError otThreadSendDiagnosticReset(otInstance *        aInstance,
diff --git a/src/core/api/network_time_api.cpp b/src/core/api/network_time_api.cpp
index f555532..eef1c92 100644
--- a/src/core/api/network_time_api.cpp
+++ b/src/core/api/network_time_api.cpp
@@ -51,10 +51,10 @@
 
 otError otNetworkTimeSetSyncPeriod(otInstance *aInstance, uint16_t aTimeSyncPeriod)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = kErrorInvalidState);
 
     instance.Get<TimeSync>().SetTimeSyncPeriod(aTimeSyncPeriod);
 
@@ -71,10 +71,10 @@
 
 otError otNetworkTimeSetXtalThreshold(otInstance *aInstance, uint16_t aXtalThreshold)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = kErrorInvalidState);
 
     instance.Get<TimeSync>().SetXtalThreshold(aXtalThreshold);
 
diff --git a/src/core/api/ping_sender_api.cpp b/src/core/api/ping_sender_api.cpp
new file mode 100644
index 0000000..13bb45b
--- /dev/null
+++ b/src/core/api/ping_sender_api.cpp
@@ -0,0 +1,59 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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
+ *   This file implements the OpenThread ping sender APIs.
+ */
+
+#include "openthread-core-config.h"
+
+#include <openthread/ping_sender.h>
+
+#include "common/instance.hpp"
+#include "common/locator-getters.hpp"
+
+using namespace ot;
+
+#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
+
+otError otPingSenderPing(otInstance *aInstance, const otPingSenderConfig *aConfig)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Utils::PingSender>().Ping(*static_cast<const Utils::PingSender::Config *>(aConfig));
+}
+
+void otPingSenderStop(otInstance *aInstance)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    instance.Get<Utils::PingSender>().Stop();
+}
+
+#endif // OPENTHREAD_CONFIG_PING_SENDER_ENABLE
diff --git a/src/core/api/server_api.cpp b/src/core/api/server_api.cpp
index d6b94ce..1db358a 100644
--- a/src/core/api/server_api.cpp
+++ b/src/core/api/server_api.cpp
@@ -73,10 +73,10 @@
 
 otError otServerGetNextService(otInstance *aInstance, otNetworkDataIterator *aIterator, otServiceConfig *aConfig)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(aIterator && aConfig, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aIterator && aConfig, error = kErrorInvalidArgs);
 
     error = instance.Get<NetworkData::Local>().GetNextService(*aIterator,
                                                               *static_cast<NetworkData::ServiceConfig *>(aConfig));
@@ -91,7 +91,7 @@
 
     instance.Get<NetworkData::Notifier>().HandleServerDataUpdated();
 
-    return OT_ERROR_NONE;
+    return kErrorNone;
 }
 
 #endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
diff --git a/src/core/api/srp_client_api.cpp b/src/core/api/srp_client_api.cpp
new file mode 100644
index 0000000..0417d75
--- /dev/null
+++ b/src/core/api/srp_client_api.cpp
@@ -0,0 +1,212 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file implements the OpenThread SRP client APIs.
+ */
+
+#include "openthread-core-config.h"
+
+#include <openthread/srp_client.h>
+
+#include "common/instance.hpp"
+#include "common/locator-getters.hpp"
+#include "net/srp_client.hpp"
+
+using namespace ot;
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+
+otError otSrpClientStart(otInstance *aInstance, const otSockAddr *aServerSockAddr)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Client>().Start(*static_cast<const Ip6::SockAddr *>(aServerSockAddr));
+}
+
+void otSrpClientStop(otInstance *aInstance)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Client>().Stop();
+}
+
+bool otSrpClientIsRunning(otInstance *aInstance)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Client>().IsRunning();
+}
+
+const otSockAddr *otSrpClientGetServerAddress(otInstance *aInstance)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return &instance.Get<Srp::Client>().GetServerAddress();
+}
+
+void otSrpClientSetCallback(otInstance *aInstance, otSrpClientCallback aCallback, void *aContext)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    instance.Get<Srp::Client>().SetCallback(aCallback, aContext);
+}
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+void otSrpClientEnableAutoStartMode(otInstance *aInstance, otSrpClientAutoStartCallback aCallback, void *aContext)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    instance.Get<Srp::Client>().EnableAutoStartMode(aCallback, aContext);
+}
+
+void otSrpClientDisableAutoStartMode(otInstance *aInstance)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    instance.Get<Srp::Client>().DisableAutoStartMode();
+}
+
+bool otSrpClientIsAutoStartModeEnabled(otInstance *aInstance)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Client>().IsAutoStartModeEnabled();
+}
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+
+uint32_t otSrpClientGetLeaseInterval(otInstance *aInstance)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Client>().GetLeaseInterval();
+}
+
+void otSrpClientSetLeaseInterval(otInstance *aInstance, uint32_t aInterval)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Client>().SetLeaseInterval(aInterval);
+}
+
+uint32_t otSrpClientGetKeyLeaseInterval(otInstance *aInstance)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Client>().GetKeyLeaseInterval();
+}
+
+void otSrpClientSetKeyLeaseInterval(otInstance *aInstance, uint32_t aInterval)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Client>().SetKeyLeaseInterval(aInterval);
+}
+
+const otSrpClientHostInfo *otSrpClientGetHostInfo(otInstance *aInstance)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return &instance.Get<Srp::Client>().GetHostInfo();
+}
+
+otError otSrpClientSetHostName(otInstance *aInstance, const char *aName)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Client>().SetHostName(aName);
+}
+
+otError otSrpClientSetHostAddresses(otInstance *aInstance, const otIp6Address *aIp6Addresses, uint8_t aNumAddresses)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Client>().SetHostAddresses(static_cast<const Ip6::Address *>(aIp6Addresses),
+                                                        aNumAddresses);
+}
+
+otError otSrpClientAddService(otInstance *aInstance, otSrpClientService *aService)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Client>().AddService(*static_cast<Srp::Client::Service *>(aService));
+}
+
+otError otSrpClientRemoveService(otInstance *aInstance, otSrpClientService *aService)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Client>().RemoveService(*static_cast<Srp::Client::Service *>(aService));
+}
+
+const otSrpClientService *otSrpClientGetServices(otInstance *aInstance)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Client>().GetServices().GetHead();
+}
+
+otError otSrpClientRemoveHostAndServices(otInstance *aInstance, bool aRemoveKeyLease)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Client>().RemoveHostAndServices(aRemoveKeyLease);
+}
+
+void otSrpClientClearHostAndServices(otInstance *aInstance)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    instance.Get<Srp::Client>().ClearHostAndServices();
+}
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE
+const char *otSrpClientGetDomainName(otInstance *aInstance)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Client>().GetDomainName();
+}
+
+otError otSrpClientSetDomainName(otInstance *aInstance, const char *aDomainName)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Client>().SetDomainName(aDomainName);
+}
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE
+
+const char *otSrpClientItemStateToString(otSrpClientItemState aItemState)
+{
+    OT_ASSERT(aItemState <= OT_SRP_CLIENT_ITEM_STATE_REMOVED);
+
+    return Srp::Client::ItemStateToString(static_cast<Srp::Client::ItemState>(aItemState));
+}
+
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
diff --git a/src/core/api/srp_server_api.cpp b/src/core/api/srp_server_api.cpp
new file mode 100644
index 0000000..57ddd64
--- /dev/null
+++ b/src/core/api/srp_server_api.cpp
@@ -0,0 +1,164 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *  This file defines the OpenThread SRP server API.
+ */
+
+#include "openthread-core-config.h"
+
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+
+#include <openthread/srp_server.h>
+
+#include "common/instance.hpp"
+#include "common/locator-getters.hpp"
+
+using namespace ot;
+
+const char *otSrpServerGetDomain(otInstance *aInstance)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Server>().GetDomain();
+}
+
+otError otSrpServerSetDomain(otInstance *aInstance, const char *aDomain)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Server>().SetDomain(aDomain);
+}
+
+void otSrpServerSetEnabled(otInstance *aInstance, bool aEnabled)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    instance.Get<Srp::Server>().SetEnabled(aEnabled);
+}
+
+otError otSrpServerSetLeaseRange(otInstance *aInstance,
+                                 uint32_t    aMinLease,
+                                 uint32_t    aMaxLease,
+                                 uint32_t    aMinKeyLease,
+                                 uint32_t    aMaxKeyLease)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Server>().SetLeaseRange(aMinLease, aMaxLease, aMinKeyLease, aMaxKeyLease);
+}
+
+void otSrpServerSetServiceUpdateHandler(otInstance *                    aInstance,
+                                        otSrpServerServiceUpdateHandler aServiceHandler,
+                                        void *                          aContext)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    instance.Get<Srp::Server>().SetServiceHandler(aServiceHandler, aContext);
+}
+
+void otSrpServerHandleServiceUpdateResult(otInstance *aInstance, const otSrpServerHost *aHost, otError aError)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    instance.Get<Srp::Server>().HandleServiceUpdateResult(static_cast<const Srp::Server::Host *>(aHost), aError);
+}
+
+const otSrpServerHost *otSrpServerGetNextHost(otInstance *aInstance, const otSrpServerHost *aHost)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<Srp::Server>().GetNextHost(static_cast<const Srp::Server::Host *>(aHost));
+}
+
+bool otSrpServerHostIsDeleted(const otSrpServerHost *aHost)
+{
+    return static_cast<const Srp::Server::Host *>(aHost)->IsDeleted();
+}
+
+const char *otSrpServerHostGetFullName(const otSrpServerHost *aHost)
+{
+    return static_cast<const Srp::Server::Host *>(aHost)->GetFullName();
+}
+
+const otIp6Address *otSrpServerHostGetAddresses(const otSrpServerHost *aHost, uint8_t *aAddressesNum)
+{
+    auto host = static_cast<const Srp::Server::Host *>(aHost);
+
+    return host->GetAddresses(*aAddressesNum);
+}
+
+const otSrpServerService *otSrpServerHostGetNextService(const otSrpServerHost *   aHost,
+                                                        const otSrpServerService *aService)
+{
+    auto host = static_cast<const Srp::Server::Host *>(aHost);
+
+    return host->GetNextService(static_cast<const Srp::Server::Service *>(aService));
+}
+
+bool otSrpServerServiceIsDeleted(const otSrpServerService *aService)
+{
+    return static_cast<const Srp::Server::Service *>(aService)->IsDeleted();
+}
+
+const char *otSrpServerServiceGetFullName(const otSrpServerService *aService)
+{
+    return static_cast<const Srp::Server::Service *>(aService)->GetFullName();
+}
+
+uint16_t otSrpServerServiceGetPort(const otSrpServerService *aService)
+{
+    return static_cast<const Srp::Server::Service *>(aService)->GetPort();
+}
+
+uint16_t otSrpServerServiceGetWeight(const otSrpServerService *aService)
+{
+    return static_cast<const Srp::Server::Service *>(aService)->GetWeight();
+}
+
+uint16_t otSrpServerServiceGetPriority(const otSrpServerService *aService)
+{
+    return static_cast<const Srp::Server::Service *>(aService)->GetPriority();
+}
+
+const uint8_t *otSrpServerServiceGetTxtData(const otSrpServerService *aService, uint16_t *aDataLength)
+{
+    const Srp::Server::Service &service = *static_cast<const Srp::Server::Service *>(aService);
+
+    *aDataLength = service.GetTxtDataLength();
+
+    return service.GetTxtData();
+}
+
+const otSrpServerHost *otSrpServerServiceGetHost(const otSrpServerService *aService)
+{
+    return &static_cast<const Srp::Server::Service *>(aService)->GetHost();
+}
+
+#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
diff --git a/src/core/api/thread_api.cpp b/src/core/api/thread_api.cpp
index 2a2607c..1469336 100644
--- a/src/core/api/thread_api.cpp
+++ b/src/core/api/thread_api.cpp
@@ -66,12 +66,12 @@
 
 otError otThreadSetExtendedPanId(otInstance *aInstance, const otExtendedPanId *aExtendedPanId)
 {
-    otError                   error    = OT_ERROR_NONE;
+    Error                     error    = kErrorNone;
     Instance &                instance = *static_cast<Instance *>(aInstance);
     const Mac::ExtendedPanId &extPanId = *static_cast<const Mac::ExtendedPanId *>(aExtendedPanId);
     Mle::MeshLocalPrefix      prefix;
 
-    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = kErrorInvalidState);
 
     instance.Get<Mac::Mac>().SetExtendedPanId(extPanId);
 
@@ -120,12 +120,12 @@
 
 otError otThreadSetMasterKey(otInstance *aInstance, const otMasterKey *aKey)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
     OT_ASSERT(aKey != nullptr);
 
-    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = kErrorInvalidState);
 
     error = instance.Get<KeyManager>().SetMasterKey(*static_cast<const MasterKey *>(aKey));
     instance.Get<MeshCoP::ActiveDataset>().Clear();
@@ -158,10 +158,10 @@
 
 otError otThreadSetMeshLocalPrefix(otInstance *aInstance, const otMeshLocalPrefix *aMeshLocalPrefix)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = kErrorInvalidState);
 
     instance.Get<Mle::MleRouter>().SetMeshLocalPrefix(*static_cast<const Mle::MeshLocalPrefix *>(aMeshLocalPrefix));
     instance.Get<MeshCoP::ActiveDataset>().Clear();
@@ -187,10 +187,10 @@
 
 otError otThreadSetNetworkName(otInstance *aInstance, const char *aNetworkName)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = kErrorInvalidState);
 
     error = instance.Get<Mac::Mac>().SetNetworkName(aNetworkName);
     instance.Get<MeshCoP::ActiveDataset>().Clear();
@@ -210,10 +210,10 @@
 
 otError otThreadSetDomainName(otInstance *aInstance, const char *aDomainName)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = kErrorInvalidState);
 
     error = instance.Get<Mac::Mac>().SetDomainName(aDomainName);
 
@@ -225,7 +225,7 @@
 otError otThreadSetFixedDuaInterfaceIdentifier(otInstance *aInstance, const otIp6InterfaceIdentifier *aIid)
 {
     Instance &instance = *static_cast<Instance *>(aInstance);
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
 
     if (aIid)
     {
@@ -317,11 +317,11 @@
 otError otThreadGetLeaderData(otInstance *aInstance, otLeaderData *aLeaderData)
 {
     Instance &instance = *static_cast<Instance *>(aInstance);
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
 
     OT_ASSERT(aLeaderData != nullptr);
 
-    VerifyOrExit(instance.Get<Mle::MleRouter>().IsAttached(), error = OT_ERROR_DETACHED);
+    VerifyOrExit(instance.Get<Mle::MleRouter>().IsAttached(), error = kErrorDetached);
     *aLeaderData = instance.Get<Mle::MleRouter>().GetLeaderData();
 
 exit:
@@ -359,14 +359,14 @@
 otError otThreadGetParentInfo(otInstance *aInstance, otRouterInfo *aParentInfo)
 {
     Instance &instance = *static_cast<Instance *>(aInstance);
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Router *  parent;
 
     OT_ASSERT(aParentInfo != nullptr);
 
     // Reference device needs get the original parent's info even after the node state changed.
 #if !OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
-    VerifyOrExit(instance.Get<Mle::MleRouter>().IsChild(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mle::MleRouter>().IsChild(), error = kErrorInvalidState);
 #endif
 
     parent = &instance.Get<Mle::MleRouter>().GetParent();
@@ -390,14 +390,14 @@
 
 otError otThreadGetParentAverageRssi(otInstance *aInstance, int8_t *aParentRssi)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
     OT_ASSERT(aParentRssi != nullptr);
 
     *aParentRssi = instance.Get<Mle::MleRouter>().GetParent().GetLinkInfo().GetAverageRss();
 
-    VerifyOrExit(*aParentRssi != OT_RADIO_RSSI_INVALID, error = OT_ERROR_FAILED);
+    VerifyOrExit(*aParentRssi != OT_RADIO_RSSI_INVALID, error = kErrorFailed);
 
 exit:
     return error;
@@ -405,14 +405,14 @@
 
 otError otThreadGetParentLastRssi(otInstance *aInstance, int8_t *aLastRssi)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
     OT_ASSERT(aLastRssi != nullptr);
 
     *aLastRssi = instance.Get<Mle::MleRouter>().GetParent().GetLinkInfo().GetLastRss();
 
-    VerifyOrExit(*aLastRssi != OT_RADIO_RSSI_INVALID, error = OT_ERROR_FAILED);
+    VerifyOrExit(*aLastRssi != OT_RADIO_RSSI_INVALID, error = kErrorFailed);
 
 exit:
     return error;
@@ -420,7 +420,7 @@
 
 otError otThreadSetEnabled(otInstance *aInstance, bool aEnabled)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
     if (aEnabled)
diff --git a/src/core/api/thread_ftd_api.cpp b/src/core/api/thread_ftd_api.cpp
index eb5dc3a..221ddda 100644
--- a/src/core/api/thread_ftd_api.cpp
+++ b/src/core/api/thread_ftd_api.cpp
@@ -139,7 +139,7 @@
 
     instance.Get<MeshCoP::JoinerRouter>().SetJoinerUdpPort(aJoinerUdpPort);
 
-    return OT_ERROR_NONE;
+    return kErrorNone;
 }
 
 uint32_t otThreadGetContextIdReuseDelay(otInstance *aInstance)
@@ -186,10 +186,10 @@
 
 otError otThreadReleaseRouterId(otInstance *aInstance, uint8_t aRouterId)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(aRouterId <= Mle::kMaxRouterId, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aRouterId <= Mle::kMaxRouterId, error = kErrorInvalidArgs);
 
     error = instance.Get<RouterTable>().Release(aRouterId);
 
@@ -199,7 +199,7 @@
 
 otError otThreadBecomeRouter(otInstance *aInstance)
 {
-    otError   error    = OT_ERROR_INVALID_STATE;
+    Error     error    = kErrorInvalidState;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
     switch (instance.Get<Mle::MleRouter>().GetRole())
@@ -214,7 +214,7 @@
 
     case Mle::kRoleRouter:
     case Mle::kRoleLeader:
-        error = OT_ERROR_NONE;
+        error = kErrorNone;
         break;
     }
 
@@ -279,20 +279,20 @@
                                        otChildIp6AddressIterator *aIterator,
                                        otIp6Address *             aAddress)
 {
-    otError      error    = OT_ERROR_NONE;
+    Error        error    = kErrorNone;
     Instance &   instance = *static_cast<Instance *>(aInstance);
     const Child *child;
 
     OT_ASSERT(aIterator != nullptr && aAddress != nullptr);
 
     child = instance.Get<ChildTable>().GetChildAtIndex(aChildIndex);
-    VerifyOrExit(child != nullptr, error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit(child->IsStateValidOrRestoring(), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(child != nullptr, error = kErrorInvalidArgs);
+    VerifyOrExit(child->IsStateValidOrRestoring(), error = kErrorInvalidArgs);
 
     {
         Child::AddressIterator iter(*child, *aIterator);
 
-        VerifyOrExit(!iter.IsDone(), error = OT_ERROR_NOT_FOUND);
+        VerifyOrExit(!iter.IsDone(), error = kErrorNotFound);
         *aAddress = *iter.GetAddress();
 
         iter++;
@@ -352,10 +352,10 @@
 
 otError otThreadSetPskc(otInstance *aInstance, const otPskc *aPskc)
 {
-    otError   error    = OT_ERROR_NONE;
+    Error     error    = kErrorNone;
     Instance &instance = *static_cast<Instance *>(aInstance);
 
-    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(instance.Get<Mle::MleRouter>().IsDisabled(), error = kErrorInvalidState);
 
     instance.Get<KeyManager>().SetPskc(*static_cast<const Pskc *>(aPskc));
     instance.Get<MeshCoP::ActiveDataset>().Clear();
diff --git a/src/core/api/udp_api.cpp b/src/core/api/udp_api.cpp
index bd907e7..7bd961b 100644
--- a/src/core/api/udp_api.cpp
+++ b/src/core/api/udp_api.cpp
@@ -68,7 +68,7 @@
     Instance &instance = *static_cast<Instance *>(aInstance);
 
     return instance.Get<Ip6::Udp>().Bind(*static_cast<Ip6::Udp::SocketHandle *>(aSocket),
-                                         *static_cast<const Ip6::SockAddr *>(aSockName));
+                                         *static_cast<const Ip6::SockAddr *>(aSockName), OT_NETIF_THREAD);
 }
 
 otError otUdpConnect(otInstance *aInstance, otUdpSocket *aSocket, const otSockAddr *aSockName)
diff --git a/src/core/backbone_router/backbone_tmf.cpp b/src/core/backbone_router/backbone_tmf.cpp
index 75ff364..ebc0f23 100644
--- a/src/core/backbone_router/backbone_tmf.cpp
+++ b/src/core/backbone_router/backbone_tmf.cpp
@@ -36,13 +36,14 @@
 #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
 
 #include "common/locator-getters.hpp"
+#include "common/logging.hpp"
 
 namespace ot {
 namespace BackboneRouter {
 
-otError BackboneTmfAgent::Start(void)
+Error BackboneTmfAgent::Start(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     SuccessOrExit(error = Coap::Start(kBackboneUdpPort, OT_NETIF_BACKBONE));
     SubscribeMulticast(Get<Local>().GetAllNetworkBackboneRoutersAddress());
@@ -51,14 +52,11 @@
     return error;
 }
 
-otError BackboneTmfAgent::Filter(const ot::Coap::Message &aMessage,
-                                 const Ip6::MessageInfo & aMessageInfo,
-                                 void *                   aContext)
+Error BackboneTmfAgent::Filter(const ot::Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo, void *aContext)
 {
     OT_UNUSED_VARIABLE(aMessage);
 
-    return static_cast<BackboneTmfAgent *>(aContext)->IsBackboneTmfMessage(aMessageInfo) ? OT_ERROR_NONE
-                                                                                         : OT_ERROR_NOT_TMF;
+    return static_cast<BackboneTmfAgent *>(aContext)->IsBackboneTmfMessage(aMessageInfo) ? kErrorNone : kErrorNotTmf;
 }
 
 bool BackboneTmfAgent::IsBackboneTmfMessage(const Ip6::MessageInfo &aMessageInfo) const
@@ -79,34 +77,20 @@
 
 void BackboneTmfAgent::SubscribeMulticast(const Ip6::Address &aAddress)
 {
-    otError error;
+    Error error;
 
     error = mSocket.JoinNetifMulticastGroup(OT_NETIF_BACKBONE, aAddress);
 
-    if (error != OT_ERROR_NONE)
-    {
-        otLogDebgBbr("Backbone TMF subscribes %s: %s", aAddress.ToString().AsCString(), otThreadErrorToString(error));
-    }
-    else
-    {
-        otLogCritBbr("Backbone TMF subscribes %s: %s", aAddress.ToString().AsCString(), otThreadErrorToString(error));
-    }
+    otLogResultBbr(error, "Backbone TMF subscribes %s", aAddress.ToString().AsCString());
 }
 
 void BackboneTmfAgent::UnsubscribeMulticast(const Ip6::Address &aAddress)
 {
-    otError error;
+    Error error;
 
     error = mSocket.LeaveNetifMulticastGroup(OT_NETIF_BACKBONE, aAddress);
 
-    if (error == OT_ERROR_NONE)
-    {
-        otLogDebgBbr("Backbone TMF unsubscribes %s: %s", aAddress.ToString().AsCString(), otThreadErrorToString(error));
-    }
-    else
-    {
-        otLogCritBbr("Backbone TMF unsubscribes %s: %s", aAddress.ToString().AsCString(), otThreadErrorToString(error));
-    }
+    otLogResultBbr(error, "Backbone TMF unsubscribes %s", aAddress.ToString().AsCString());
 }
 
 } // namespace BackboneRouter
diff --git a/src/core/backbone_router/backbone_tmf.hpp b/src/core/backbone_router/backbone_tmf.hpp
index 5137045..dddf14f 100644
--- a/src/core/backbone_router/backbone_tmf.hpp
+++ b/src/core/backbone_router/backbone_tmf.hpp
@@ -66,11 +66,11 @@
     /**
      * This method starts the Backbone TMF agent.
      *
-     * @retval OT_ERROR_NONE    Successfully started the CoAP service.
-     * @retval OT_ERROR_FAILED  Failed to start the Backbone TMF agent.
+     * @retval kErrorNone    Successfully started the CoAP service.
+     * @retval kErrorFailed  Failed to start the Backbone TMF agent.
      *
      */
-    otError Start(void);
+    Error Start(void);
 
     /**
      * This method returns whether @p aMessageInfo meets Backbone Thread Management Framework Addressing Rules.
@@ -98,7 +98,7 @@
     void UnsubscribeMulticast(const Ip6::Address &aAddress);
 
 private:
-    static otError Filter(const ot::Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo, void *aContext);
+    static Error Filter(const ot::Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo, void *aContext);
 };
 
 } // namespace BackboneRouter
diff --git a/src/core/backbone_router/bbr_leader.cpp b/src/core/backbone_router/bbr_leader.cpp
index 806f200..1ccfa46 100644
--- a/src/core/backbone_router/bbr_leader.cpp
+++ b/src/core/backbone_router/bbr_leader.cpp
@@ -57,11 +57,11 @@
     mDomainPrefix.SetLength(0);
 }
 
-otError Leader::GetConfig(BackboneRouterConfig &aConfig) const
+Error Leader::GetConfig(BackboneRouterConfig &aConfig) const
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(HasPrimary(), error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(HasPrimary(), error = kErrorNotFound);
 
     aConfig = mConfig;
 
@@ -69,15 +69,13 @@
     return error;
 }
 
-otError Leader::GetServiceId(uint8_t &aServiceId) const
+Error Leader::GetServiceId(uint8_t &aServiceId) const
 {
-    otError error       = OT_ERROR_NONE;
-    uint8_t serviceData = NetworkData::ServiceTlv::kServiceDataBackboneRouter;
+    Error error = kErrorNone;
 
-    VerifyOrExit(HasPrimary(), error = OT_ERROR_NOT_FOUND);
-
-    error = Get<NetworkData::Leader>().GetServiceId(NetworkData::ServiceTlv::kThreadEnterpriseNumber, &serviceData,
-                                                    sizeof(serviceData), true, aServiceId);
+    VerifyOrExit(HasPrimary(), error = kErrorNotFound);
+    error = Get<NetworkData::Service::Manager>().GetServiceId<NetworkData::Service::BackboneRouter>(
+        /* aServerStable */ true, aServiceId);
 
 exit:
     return error;
@@ -182,7 +180,7 @@
     State                state;
     uint32_t             origMlrTimeout;
 
-    IgnoreError(Get<NetworkData::Leader>().GetBackboneRouterPrimary(config));
+    Get<NetworkData::Service::Manager>().GetBackboneRouterPrimary(config);
 
     if (config.mServer16 != mConfig.mServer16)
     {
@@ -258,7 +256,7 @@
     DomainPrefixState               state;
     bool                            found = false;
 
-    while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, config) == OT_ERROR_NONE)
+    while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, config) == kErrorNone)
     {
         if (config.mDp)
         {
diff --git a/src/core/backbone_router/bbr_leader.hpp b/src/core/backbone_router/bbr_leader.hpp
index 53e373c..f437d9d 100644
--- a/src/core/backbone_router/bbr_leader.hpp
+++ b/src/core/backbone_router/bbr_leader.hpp
@@ -106,22 +106,22 @@
      *
      * @param[out]  aConfig        The Primary Backbone Router information.
      *
-     * @retval OT_ERROR_NONE       Successfully got the Primary Backbone Router information.
-     * @retval OT_ERROR_NOT_FOUND  No Backbone Router in the Thread Network.
+     * @retval kErrorNone          Successfully got the Primary Backbone Router information.
+     * @retval kErrorNotFound      No Backbone Router in the Thread Network.
      *
      */
-    otError GetConfig(BackboneRouterConfig &aConfig) const;
+    Error GetConfig(BackboneRouterConfig &aConfig) const;
 
     /**
      * This method gets the Backbone Router Service ID.
      *
      * @param[out]  aServiceId     The reference whether to put the Backbone Router Service ID.
      *
-     * @retval OT_ERROR_NONE       Successfully got the Backbone Router Service ID.
-     * @retval OT_ERROR_NOT_FOUND  Backbone Router service doesn't exist.
+     * @retval kErrorNone          Successfully got the Backbone Router Service ID.
+     * @retval kErrorNotFound      Backbone Router service doesn't exist.
      *
      */
-    otError GetServiceId(uint8_t &aServiceId) const;
+    Error GetServiceId(uint8_t &aServiceId) const;
 
     /**
      * This method gets the short address of the Primary Backbone Router.
diff --git a/src/core/backbone_router/bbr_local.cpp b/src/core/backbone_router/bbr_local.cpp
index 19a3a2f..6a0d9bd 100644
--- a/src/core/backbone_router/bbr_local.cpp
+++ b/src/core/backbone_router/bbr_local.cpp
@@ -92,12 +92,10 @@
     else
     {
         RemoveDomainPrefixFromNetworkData();
-        IgnoreError(RemoveService());
+        RemoveService();
         SetState(OT_BACKBONE_ROUTER_STATE_DISABLED);
     }
 
-    Get<NetworkData::Notifier>().HandleServerDataUpdated();
-
 exit:
     return;
 }
@@ -106,10 +104,7 @@
 {
     VerifyOrExit(mState != OT_BACKBONE_ROUTER_STATE_DISABLED);
 
-    if (RemoveService() == OT_ERROR_NONE)
-    {
-        Get<NetworkData::Notifier>().HandleServerDataUpdated();
-    }
+    RemoveService();
 
     if (mState == OT_BACKBONE_ROUTER_STATE_PRIMARY)
     {
@@ -130,19 +125,19 @@
     aConfig.mMlrTimeout          = mMlrTimeout;
 }
 
-otError Local::SetConfig(const BackboneRouterConfig &aConfig)
+Error Local::SetConfig(const BackboneRouterConfig &aConfig)
 {
-    otError error  = OT_ERROR_NONE;
-    bool    update = false;
+    Error error  = kErrorNone;
+    bool  update = false;
 
     VerifyOrExit(aConfig.mMlrTimeout >= Mle::kMlrTimeoutMin && aConfig.mMlrTimeout <= Mle::kMlrTimeoutMax,
-                 error = OT_ERROR_INVALID_ARGS);
+                 error = kErrorInvalidArgs);
     // Validate configuration according to Thread 1.2.1 Specification 5.21.3.3:
     // "The Reregistration Delay in seconds MUST be lower than (0.5 * MLR Timeout). It MUST be at least 1."
-    VerifyOrExit(aConfig.mReregistrationDelay >= 1, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aConfig.mReregistrationDelay >= 1, error = kErrorInvalidArgs);
     static_assert(sizeof(aConfig.mReregistrationDelay) < sizeof(aConfig.mMlrTimeout),
                   "the calculation below might overflow");
-    VerifyOrExit(aConfig.mReregistrationDelay * 2 < aConfig.mMlrTimeout, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aConfig.mReregistrationDelay * 2 < aConfig.mMlrTimeout, error = kErrorInvalidArgs);
 
     if (aConfig.mReregistrationDelay != mReregistrationDelay)
     {
@@ -166,10 +161,7 @@
     {
         Get<Notifier>().Signal(kEventThreadBackboneRouterLocalChanged);
 
-        if (AddService() == OT_ERROR_NONE)
-        {
-            Get<NetworkData::Notifier>().HandleServerDataUpdated();
-        }
+        IgnoreError(AddService());
     }
 
 exit:
@@ -177,11 +169,10 @@
     return error;
 }
 
-otError Local::AddService(bool aForce)
+Error Local::AddService(bool aForce)
 {
-    otError                               error       = OT_ERROR_INVALID_STATE;
-    uint8_t                               serviceData = NetworkData::ServiceTlv::kServiceDataBackboneRouter;
-    NetworkData::BackboneRouterServerData serverData;
+    Error                                            error = kErrorInvalidState;
+    NetworkData::Service::BackboneRouter::ServerData serverData;
 
     VerifyOrExit(mState != OT_BACKBONE_ROUTER_STATE_DISABLED && Get<Mle::Mle>().IsAttached());
 
@@ -195,10 +186,7 @@
     serverData.SetReregistrationDelay(mReregistrationDelay);
     serverData.SetMlrTimeout(mMlrTimeout);
 
-    SuccessOrExit(error = Get<NetworkData::Local>().AddService(
-                      NetworkData::ServiceTlv::kThreadEnterpriseNumber, &serviceData, sizeof(serviceData), true,
-                      reinterpret_cast<const uint8_t *>(&serverData), sizeof(serverData)));
-
+    SuccessOrExit(error = Get<NetworkData::Service::Manager>().Add<NetworkData::Service::BackboneRouter>(serverData));
     mIsServiceAdded = true;
 
 exit:
@@ -206,19 +194,15 @@
     return error;
 }
 
-otError Local::RemoveService(void)
+void Local::RemoveService(void)
 {
-    otError error;
-    uint8_t serviceData = NetworkData::ServiceTlv::kServiceDataBackboneRouter;
+    Error error;
 
-    SuccessOrExit(error = Get<NetworkData::Local>().RemoveService(NetworkData::ServiceTlv::kThreadEnterpriseNumber,
-                                                                  &serviceData, sizeof(serviceData)));
-
+    SuccessOrExit(error = Get<NetworkData::Service::Manager>().Remove<NetworkData::Service::BackboneRouter>());
     mIsServiceAdded = false;
 
 exit:
     LogBackboneRouterService("Remove", error);
-    return error;
 }
 
 void Local::SetState(BackboneRouterState aState)
@@ -282,7 +266,7 @@
         mReregistrationDelay = aConfig.mReregistrationDelay;
         mMlrTimeout          = aConfig.mMlrTimeout;
         Get<Notifier>().Signal(kEventThreadBackboneRouterLocalChanged);
-        if (AddService(true /* Force registration to refresh and restore Primary state */) == OT_ERROR_NONE)
+        if (AddService(true /* Force registration to refresh and restore Primary state */) == kErrorNone)
         {
             Get<NetworkData::Notifier>().HandleServerDataUpdated();
         }
@@ -296,11 +280,11 @@
     return;
 }
 
-otError Local::GetDomainPrefix(NetworkData::OnMeshPrefixConfig &aConfig)
+Error Local::GetDomainPrefix(NetworkData::OnMeshPrefixConfig &aConfig)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(mDomainPrefixConfig.GetPrefix().GetLength() > 0, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(mDomainPrefixConfig.GetPrefix().GetLength() > 0, error = kErrorNotFound);
 
     aConfig = mDomainPrefixConfig;
 
@@ -308,12 +292,12 @@
     return error;
 }
 
-otError Local::RemoveDomainPrefix(const Ip6::Prefix &aPrefix)
+Error Local::RemoveDomainPrefix(const Ip6::Prefix &aPrefix)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(aPrefix.GetLength() > 0, error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit(mDomainPrefixConfig.GetPrefix() == aPrefix, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(aPrefix.GetLength() > 0, error = kErrorInvalidArgs);
+    VerifyOrExit(mDomainPrefixConfig.GetPrefix() == aPrefix, error = kErrorNotFound);
 
     if (IsEnabled())
     {
@@ -334,7 +318,7 @@
     }
 
     mDomainPrefixConfig = aConfig;
-    LogDomainPrefix("Set", OT_ERROR_NONE);
+    LogDomainPrefix("Set", kErrorNone);
 
     if (IsEnabled())
     {
@@ -406,7 +390,7 @@
 
 void Local::RemoveDomainPrefixFromNetworkData(void)
 {
-    otError error = OT_ERROR_NOT_FOUND; // only used for logging.
+    Error error = kErrorNotFound; // only used for logging.
 
     if (mDomainPrefixConfig.mPrefix.mLength > 0)
     {
@@ -418,7 +402,7 @@
 
 void Local::AddDomainPrefixToNetworkData(void)
 {
-    otError error = OT_ERROR_NOT_FOUND; // only used for logging.
+    Error error = kErrorNotFound; // only used for logging.
 
     if (mDomainPrefixConfig.GetPrefix().GetLength() > 0)
     {
@@ -429,16 +413,16 @@
 }
 
 #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_BBR == 1)
-void Local::LogDomainPrefix(const char *aAction, otError aError)
+void Local::LogDomainPrefix(const char *aAction, Error aError)
 {
     otLogInfoBbr("%s Domain Prefix: %s, %s", aAction, mDomainPrefixConfig.GetPrefix().ToString().AsCString(),
-                 otThreadErrorToString(aError));
+                 ErrorToString(aError));
 }
 
-void Local::LogBackboneRouterService(const char *aAction, otError aError)
+void Local::LogBackboneRouterService(const char *aAction, Error aError)
 {
     otLogInfoBbr("%s BBR Service: seqno (%d), delay (%ds), timeout (%ds), %s", aAction, mSequenceNumber,
-                 mReregistrationDelay, mMlrTimeout, otThreadErrorToString(aError));
+                 mReregistrationDelay, mMlrTimeout, ErrorToString(aError));
 }
 #endif
 
diff --git a/src/core/backbone_router/bbr_local.hpp b/src/core/backbone_router/bbr_local.hpp
index 7d1b828..ba6d274 100644
--- a/src/core/backbone_router/bbr_local.hpp
+++ b/src/core/backbone_router/bbr_local.hpp
@@ -37,6 +37,15 @@
 #include "openthread-core-config.h"
 
 #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
+
+#if (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2)
+#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE."
+#endif
+
+#if !OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
+#error "OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE is required for OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE."
+#endif
+
 #include <openthread/backbone_router.h>
 #include <openthread/backbone_router_ftd.h>
 
@@ -105,11 +114,11 @@
      *
      * @param[in]  aConfig  The configuration to set.
      *
-     * @retval OT_ERROR_NONE          Successfully updated configuration.
-     * @retval OT_ERROR_INVALID_ARGS  The configuration in @p aConfig is invalid.
+     * @retval kErrorNone         Successfully updated configuration.
+     * @retval kErrorInvalidArgs  The configuration in @p aConfig is invalid.
      *
      */
-    otError SetConfig(const BackboneRouterConfig &aConfig);
+    Error SetConfig(const BackboneRouterConfig &aConfig);
 
     /**
      * This method registers Backbone Router Dataset to Leader.
@@ -118,12 +127,12 @@
      *                    False to decide based on current BackboneRouterState.
      *
      *
-     * @retval OT_ERROR_NONE             Successfully added the Service entry.
-     * @retval OT_ERROR_INVALID_STATE    Not in the ready state to register.
-     * @retval OT_ERROR_NO_BUFS          Insufficient space to add the Service entry.
+     * @retval kErrorNone            Successfully added the Service entry.
+     * @retval kErrorInvalidState    Not in the ready state to register.
+     * @retval kErrorNoBufs          Insufficient space to add the Service entry.
      *
      */
-    otError AddService(bool aForce = false);
+    Error AddService(bool aForce = false);
 
     /**
      * This method indicates whether or not the Backbone Router is Primary.
@@ -173,23 +182,23 @@
      *
      * @param[out]  aConfig  A reference to the Domain Prefix configuration.
      *
-     * @retval OT_ERROR_NONE       Successfully got the Domain Prefix configuration.
-     * @retval OT_ERROR_NOT_FOUND  No Domain Prefix was configured.
+     * @retval kErrorNone      Successfully got the Domain Prefix configuration.
+     * @retval kErrorNotFound  No Domain Prefix was configured.
      *
      */
-    otError GetDomainPrefix(NetworkData::OnMeshPrefixConfig &aConfig);
+    Error GetDomainPrefix(NetworkData::OnMeshPrefixConfig &aConfig);
 
     /**
      * This method removes the local Domain Prefix configuration.
      *
      * @param[in]  aPrefix A reference to the IPv6 Domain Prefix.
      *
-     * @retval OT_ERROR_NONE          Successfully removed the Domain Prefix.
-     * @retval OT_ERROR_INVALID_ARGS  @p aPrefix is invalid.
-     * @retval OT_ERROR_NOT_FOUND     No Domain Prefix was configured or @p aPrefix doesn't match.
+     * @retval kErrorNone         Successfully removed the Domain Prefix.
+     * @retval kErrorInvalidArgs  @p aPrefix is invalid.
+     * @retval kErrorNotFound     No Domain Prefix was configured or @p aPrefix doesn't match.
      *
      */
-    otError RemoveDomainPrefix(const Ip6::Prefix &aPrefix);
+    Error RemoveDomainPrefix(const Ip6::Prefix &aPrefix);
 
     /**
      * This method sets the local Domain Prefix configuration.
@@ -239,16 +248,16 @@
     void SetDomainPrefixCallback(otBackboneRouterDomainPrefixCallback aCallback, void *aContext);
 
 private:
-    void    SetState(BackboneRouterState aState);
-    otError RemoveService(void);
-    void    AddDomainPrefixToNetworkData(void);
-    void    RemoveDomainPrefixFromNetworkData(void);
+    void SetState(BackboneRouterState aState);
+    void RemoveService(void);
+    void AddDomainPrefixToNetworkData(void);
+    void RemoveDomainPrefixFromNetworkData(void);
 #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_BBR == 1)
-    void LogBackboneRouterService(const char *aAction, otError aError);
-    void LogDomainPrefix(const char *aAction, otError aError);
+    void LogBackboneRouterService(const char *aAction, Error aError);
+    void LogDomainPrefix(const char *aAction, Error aError);
 #else
-    void LogBackboneRouterService(const char *, otError) {}
-    void LogDomainPrefix(const char *, otError) {}
+    void LogBackboneRouterService(const char *, Error) {}
+    void LogDomainPrefix(const char *, Error) {}
 #endif
 
     BackboneRouterState mState;
diff --git a/src/core/backbone_router/bbr_manager.cpp b/src/core/backbone_router/bbr_manager.cpp
index 43519f3..e300a69 100644
--- a/src/core/backbone_router/bbr_manager.cpp
+++ b/src/core/backbone_router/bbr_manager.cpp
@@ -63,7 +63,7 @@
 #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE
     , mMulticastListenersTable(aInstance)
 #endif
-    , mTimer(aInstance, Manager::HandleTimer, this)
+    , mTimer(aInstance, Manager::HandleTimer)
     , mBackboneTmfAgent(aInstance)
 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
 #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_DUA_NDPROXYING_ENABLE
@@ -88,7 +88,7 @@
 
 void Manager::HandleNotifierEvents(Events aEvents)
 {
-    otError error;
+    Error error;
 
     if (aEvents.Contains(kEventThreadBackboneRouterStateChanged))
     {
@@ -105,13 +105,13 @@
 
             error = mBackboneTmfAgent.Stop();
 
-            if (error != OT_ERROR_NONE)
+            if (error != kErrorNone)
             {
-                otLogWarnBbr("Stop Backbone TMF agent: %s", otThreadErrorToString(error));
+                otLogWarnBbr("Stop Backbone TMF agent: %s", ErrorToString(error));
             }
             else
             {
-                otLogInfoBbr("Stop Backbone TMF agent: %s", otThreadErrorToString(error));
+                otLogInfoBbr("Stop Backbone TMF agent: %s", ErrorToString(error));
             }
         }
         else
@@ -129,18 +129,16 @@
 
             error = mBackboneTmfAgent.Start();
 
-            if (error != OT_ERROR_NONE)
-            {
-                otLogCritBbr("Start Backbone TMF agent: %s", otThreadErrorToString(error));
-            }
-            else
-            {
-                otLogInfoBbr("Start Backbone TMF agent: %s", otThreadErrorToString(error));
-            }
+            otLogResultBbr(error, "Start Backbone TMF agent");
         }
     }
 }
 
+void Manager::HandleTimer(Timer &aTimer)
+{
+    aTimer.Get<Manager>().HandleTimer();
+}
+
 void Manager::HandleTimer(void)
 {
 #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE
@@ -157,14 +155,14 @@
 #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE
 void Manager::HandleMulticastListenerRegistration(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError                    error     = OT_ERROR_NONE;
+    Error                      error     = kErrorNone;
     bool                       isPrimary = Get<BackboneRouter::Local>().IsPrimary();
     ThreadStatusTlv::MlrStatus status    = ThreadStatusTlv::kMlrSuccess;
     BackboneRouterConfig       config;
 
     uint16_t     addressesOffset, addressesLength;
     Ip6::Address address;
-    Ip6::Address addresses[kIPv6AddressesNumMax];
+    Ip6::Address addresses[kIp6AddressesNumMax];
     uint8_t      failedAddressNum  = 0;
     uint8_t      successAddressNum = 0;
     TimeMilli    expireTime;
@@ -173,7 +171,7 @@
     bool         hasCommissionerSessionIdTlv = false;
     bool         processTimeoutTlv           = false;
 
-    VerifyOrExit(aMessage.IsConfirmablePostRequest(), error = OT_ERROR_PARSE);
+    VerifyOrExit(aMessage.IsConfirmablePostRequest(), error = kErrorParse);
 
 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
     // Required by Test Specification 5.10.22 DUA-TC-26, only for certification purpose
@@ -188,7 +186,7 @@
 
     // TODO: (MLR) send configured MLR response for Reference Device
 
-    if (Tlv::Find<ThreadCommissionerSessionIdTlv>(aMessage, commissionerSessionId) == OT_ERROR_NONE)
+    if (Tlv::Find<ThreadCommissionerSessionIdTlv>(aMessage, commissionerSessionId) == kErrorNone)
     {
         const MeshCoP::CommissionerSessionIdTlv *commissionerSessionIdTlv =
             static_cast<const MeshCoP::CommissionerSessionIdTlv *>(
@@ -201,14 +199,13 @@
         hasCommissionerSessionIdTlv = true;
     }
 
-    processTimeoutTlv =
-        hasCommissionerSessionIdTlv && (Tlv::Find<ThreadTimeoutTlv>(aMessage, timeout) == OT_ERROR_NONE);
+    processTimeoutTlv = hasCommissionerSessionIdTlv && (Tlv::Find<ThreadTimeoutTlv>(aMessage, timeout) == kErrorNone);
 
-    VerifyOrExit(Tlv::FindTlvValueOffset(aMessage, IPv6AddressesTlv::kIPv6Addresses, addressesOffset,
-                                         addressesLength) == OT_ERROR_NONE,
-                 error = OT_ERROR_PARSE);
+    VerifyOrExit(Tlv::FindTlvValueOffset(aMessage, Ip6AddressesTlv::kIp6Addresses, addressesOffset, addressesLength) ==
+                     kErrorNone,
+                 error = kErrorParse);
     VerifyOrExit(addressesLength % sizeof(Ip6::Address) == 0, status = ThreadStatusTlv::kMlrGeneralFailure);
-    VerifyOrExit(addressesLength / sizeof(Ip6::Address) <= kIPv6AddressesNumMax,
+    VerifyOrExit(addressesLength / sizeof(Ip6::Address) <= kIp6AddressesNumMax,
                  status = ThreadStatusTlv::kMlrGeneralFailure);
 
     if (!processTimeoutTlv)
@@ -251,16 +248,16 @@
 
             switch (mMulticastListenersTable.Add(address, expireTime))
             {
-            case OT_ERROR_NONE:
+            case kErrorNone:
                 failed = false;
                 break;
-            case OT_ERROR_INVALID_ARGS:
+            case kErrorInvalidArgs:
                 if (status == ThreadStatusTlv::kMlrSuccess)
                 {
                     status = ThreadStatusTlv::kMlrInvalid;
                 }
                 break;
-            case OT_ERROR_NO_BUFS:
+            case kErrorNoBufs:
                 if (status == ThreadStatusTlv::kMlrSuccess)
                 {
                     status = ThreadStatusTlv::kMlrNoResources;
@@ -277,20 +274,20 @@
             else
             {
                 // Put successfully registered addresses at the end of `addresses`.
-                addresses[kIPv6AddressesNumMax - (++successAddressNum)] = address;
+                addresses[kIp6AddressesNumMax - (++successAddressNum)] = address;
             }
         }
     }
 
 exit:
-    if (error == OT_ERROR_NONE)
+    if (error == kErrorNone)
     {
         SendMulticastListenerRegistrationResponse(aMessage, aMessageInfo, status, addresses, failedAddressNum);
     }
 
     if (successAddressNum > 0)
     {
-        SendBackboneMulticastListenerRegistration(&addresses[kIPv6AddressesNumMax - successAddressNum],
+        SendBackboneMulticastListenerRegistration(&addresses[kIp6AddressesNumMax - successAddressNum],
                                                   successAddressNum, timeout);
     }
 }
@@ -301,10 +298,10 @@
                                                         Ip6::Address *             aFailedAddresses,
                                                         uint8_t                    aFailedAddressNum)
 {
-    otError        error   = OT_ERROR_NONE;
+    Error          error   = kErrorNone;
     Coap::Message *message = nullptr;
 
-    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(message->SetDefaultResponseHeader(aMessage));
     SuccessOrExit(message->SetPayloadMarker());
@@ -313,7 +310,7 @@
 
     if (aFailedAddressNum > 0)
     {
-        IPv6AddressesTlv addressesTlv;
+        Ip6AddressesTlv addressesTlv;
 
         addressesTlv.Init();
         addressesTlv.SetLength(sizeof(Ip6::Address) * aFailedAddressNum);
@@ -329,22 +326,22 @@
 
 exit:
     FreeMessageOnError(message, error);
-    otLogInfoBbr("Sent MLR.rsp (status=%d): %s", aStatus, otThreadErrorToString(error));
+    otLogInfoBbr("Sent MLR.rsp (status=%d): %s", aStatus, ErrorToString(error));
 }
 
 void Manager::SendBackboneMulticastListenerRegistration(const Ip6::Address *aAddresses,
                                                         uint8_t             aAddressNum,
                                                         uint32_t            aTimeout)
 {
-    otError           error   = OT_ERROR_NONE;
+    Error             error   = kErrorNone;
     Coap::Message *   message = nullptr;
     Ip6::MessageInfo  messageInfo;
-    IPv6AddressesTlv  addressesTlv;
+    Ip6AddressesTlv   addressesTlv;
     BackboneTmfAgent &backboneTmf = Get<BackboneRouter::BackboneTmfAgent>();
 
-    OT_ASSERT(aAddressNum >= kIPv6AddressesNumMin && aAddressNum <= kIPv6AddressesNumMax);
+    OT_ASSERT(aAddressNum >= kIp6AddressesNumMin && aAddressNum <= kIp6AddressesNumMax);
 
-    VerifyOrExit((message = backboneTmf.NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = backboneTmf.NewMessage()) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsNonConfirmablePost(UriPath::kBackboneMlr));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -366,14 +363,14 @@
 
 exit:
     FreeMessageOnError(message, error);
-    otLogInfoBbr("Sent BMLR.ntf: %s", otThreadErrorToString(error));
+    otLogInfoBbr("Sent BMLR.ntf: %s", ErrorToString(error));
 }
 #endif // OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE
 
 #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_DUA_NDPROXYING_ENABLE
 void Manager::HandleDuaRegistration(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError                    error     = OT_ERROR_NONE;
+    Error                      error     = kErrorNone;
     ThreadStatusTlv::DuaStatus status    = ThreadStatusTlv::kDuaSuccess;
     bool                       isPrimary = Get<BackboneRouter::Local>().IsPrimary();
     uint32_t                   lastTransactionTime;
@@ -384,8 +381,8 @@
     Coap::Code duaRespCoapCode = Coap::kCodeEmpty;
 #endif
 
-    VerifyOrExit(aMessageInfo.GetPeerAddr().GetIid().IsRoutingLocator(), error = OT_ERROR_DROP);
-    VerifyOrExit(aMessage.IsConfirmablePostRequest(), error = OT_ERROR_PARSE);
+    VerifyOrExit(aMessageInfo.GetPeerAddr().GetIid().IsRoutingLocator(), error = kErrorDrop);
+    VerifyOrExit(aMessage.IsConfirmablePostRequest(), error = kErrorParse);
 
     SuccessOrExit(error = Tlv::Find<ThreadTargetTlv>(aMessage, target));
     SuccessOrExit(error = Tlv::Find<ThreadMeshLocalEidTlv>(aMessage, meshLocalIid));
@@ -410,19 +407,19 @@
     VerifyOrExit(Get<BackboneRouter::Leader>().HasDomainPrefix(), status = ThreadStatusTlv::kDuaGeneralFailure);
     VerifyOrExit(Get<BackboneRouter::Leader>().IsDomainUnicast(target), status = ThreadStatusTlv::kDuaInvalid);
 
-    hasLastTransactionTime = (Tlv::Find<ThreadLastTransactionTimeTlv>(aMessage, lastTransactionTime) == OT_ERROR_NONE);
+    hasLastTransactionTime = (Tlv::Find<ThreadLastTransactionTimeTlv>(aMessage, lastTransactionTime) == kErrorNone);
 
     switch (mNdProxyTable.Register(target.GetIid(), meshLocalIid, aMessageInfo.GetPeerAddr().GetIid().GetLocator(),
                                    hasLastTransactionTime ? &lastTransactionTime : nullptr))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         // TODO: update its EID-to-RLOC Map Cache based on the pair {DUA, RLOC16-source} which is gleaned from the
         // DUA.req packet according to Thread Spec. 5.23.3.6.2
         break;
-    case OT_ERROR_DUPLICATED:
+    case kErrorDuplicated:
         status = ThreadStatusTlv::kDuaDuplicate;
         break;
-    case OT_ERROR_NO_BUFS:
+    case kErrorNoBufs:
         status = ThreadStatusTlv::kDuaNoResources;
         break;
     default:
@@ -431,9 +428,9 @@
     }
 
 exit:
-    otLogInfoBbr("Received DUA.req on %s: %s", (isPrimary ? "PBBR" : "SBBR"), otThreadErrorToString(error));
+    otLogInfoBbr("Received DUA.req on %s: %s", (isPrimary ? "PBBR" : "SBBR"), ErrorToString(error));
 
-    if (error == OT_ERROR_NONE)
+    if (error == kErrorNone)
     {
 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
         if (duaRespCoapCode != Coap::kCodeEmpty)
@@ -453,10 +450,10 @@
                                           const Ip6::Address &       aTarget,
                                           ThreadStatusTlv::DuaStatus aStatus)
 {
-    otError        error   = OT_ERROR_NONE;
+    Error          error   = kErrorNone;
     Coap::Message *message = nullptr;
 
-    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(message->SetDefaultResponseHeader(aMessage));
     SuccessOrExit(message->SetPayloadMarker());
@@ -469,7 +466,7 @@
 exit:
     FreeMessageOnError(message, error);
     otLogInfoBbr("Sent DUA.rsp for DUA %s, status %d %s", aTarget.ToString().AsCString(), aStatus,
-                 otThreadErrorToString(error));
+                 ErrorToString(error));
 }
 #endif // OPENTHREAD_CONFIG_BACKBONE_ROUTER_DUA_NDPROXYING_ENABLE
 
@@ -511,7 +508,7 @@
 {
     bool              forwardToBackbone = false;
     Mac::ShortAddress rloc16;
-    otError           error;
+    Error             error;
 
     VerifyOrExit(Get<Local>().IsPrimary());
     VerifyOrExit(Get<Leader>().IsDomainUnicast(aAddress));
@@ -519,7 +516,7 @@
     VerifyOrExit(!mNdProxyTable.IsRegistered(aAddress.GetIid()));
 
     error = Get<AddressResolver>().Resolve(aAddress, rloc16, /* aAllowAddressQuery */ false);
-    VerifyOrExit(error != OT_ERROR_NONE || rloc16 == Get<Mle::MleRouter>().GetRloc16());
+    VerifyOrExit(error != kErrorNone || rloc16 == Get<Mle::MleRouter>().GetRloc16());
 
     // TODO: check if the DUA is an address of any Child?
     forwardToBackbone = true;
@@ -528,15 +525,15 @@
     return forwardToBackbone;
 }
 
-otError Manager::SendBackboneQuery(const Ip6::Address &aDua, uint16_t aRloc16)
+Error Manager::SendBackboneQuery(const Ip6::Address &aDua, uint16_t aRloc16)
 {
-    otError          error   = OT_ERROR_NONE;
+    Error            error   = kErrorNone;
     Coap::Message *  message = nullptr;
     Ip6::MessageInfo messageInfo;
 
-    VerifyOrExit(Get<BackboneRouter::Local>().IsPrimary(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(Get<BackboneRouter::Local>().IsPrimary(), error = kErrorInvalidState);
 
-    VerifyOrExit((message = mBackboneTmfAgent.NewPriorityMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = mBackboneTmfAgent.NewPriorityMessage()) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsNonConfirmablePost(UriPath::kBackboneQuery));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -558,7 +555,7 @@
 
 exit:
     otLogInfoBbr("SendBackboneQuery for %s (rloc16=%04x): %s", aDua.ToString().AsCString(), aRloc16,
-                 otThreadErrorToString(error));
+                 ErrorToString(error));
     FreeMessageOnError(message, error);
     return error;
 }
@@ -571,31 +568,31 @@
 
 void Manager::HandleBackboneQuery(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError                error = OT_ERROR_NONE;
+    Error                  error = kErrorNone;
     Ip6::Address           dua;
     uint16_t               rloc16 = Mac::kShortAddrInvalid;
     NdProxyTable::NdProxy *ndProxy;
 
-    VerifyOrExit(aMessageInfo.IsHostInterface(), error = OT_ERROR_DROP);
+    VerifyOrExit(aMessageInfo.IsHostInterface(), error = kErrorDrop);
 
-    VerifyOrExit(Get<Local>().IsPrimary(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(aMessage.IsNonConfirmablePostRequest(), error = OT_ERROR_PARSE);
+    VerifyOrExit(Get<Local>().IsPrimary(), error = kErrorInvalidState);
+    VerifyOrExit(aMessage.IsNonConfirmablePostRequest(), error = kErrorParse);
 
     SuccessOrExit(error = Tlv::Find<ThreadTargetTlv>(aMessage, dua));
 
     error = Tlv::Find<ThreadRloc16Tlv>(aMessage, rloc16);
-    VerifyOrExit(error == OT_ERROR_NONE || error == OT_ERROR_NOT_FOUND);
+    VerifyOrExit(error == kErrorNone || error == kErrorNotFound);
 
     otLogInfoBbr("Received BB.qry from %s for %s (rloc16=%04x)", aMessageInfo.GetPeerAddr().ToString().AsCString(),
                  dua.ToString().AsCString(), rloc16);
 
     ndProxy = mNdProxyTable.ResolveDua(dua);
-    VerifyOrExit(ndProxy != nullptr && !ndProxy->GetDadFlag(), error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(ndProxy != nullptr && !ndProxy->GetDadFlag(), error = kErrorNotFound);
 
     error = SendBackboneAnswer(aMessageInfo, dua, rloc16, *ndProxy);
 
 exit:
-    otLogInfoBbr("HandleBackboneQuery: %s", otThreadErrorToString(error));
+    otLogInfoBbr("HandleBackboneQuery: %s", ErrorToString(error));
 }
 
 void Manager::HandleBackboneAnswer(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
@@ -606,7 +603,7 @@
 
 void Manager::HandleBackboneAnswer(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError                  error = OT_ERROR_NONE;
+    Error                    error = kErrorNone;
     bool                     proactive;
     Ip6::Address             dua;
     Ip6::InterfaceIdentifier meshLocalIid;
@@ -614,10 +611,10 @@
     uint32_t                 timeSinceLastTransaction;
     uint16_t                 srcRloc16 = Mac::kShortAddrInvalid;
 
-    VerifyOrExit(aMessageInfo.IsHostInterface(), error = OT_ERROR_DROP);
+    VerifyOrExit(aMessageInfo.IsHostInterface(), error = kErrorDrop);
 
-    VerifyOrExit(Get<Local>().IsPrimary(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(aMessage.IsPostRequest(), error = OT_ERROR_PARSE);
+    VerifyOrExit(Get<Local>().IsPrimary(), error = kErrorInvalidState);
+    VerifyOrExit(aMessage.IsPostRequest(), error = kErrorParse);
 
     proactive = !aMessage.IsConfirmable();
 
@@ -629,7 +626,7 @@
                       Tlv::FindTlvValueOffset(aMessage, ThreadTlv::kNetworkName, networkNameOffset, networkNameLength));
 
     error = Tlv::Find<ThreadRloc16Tlv>(aMessage, srcRloc16);
-    VerifyOrExit(error == OT_ERROR_NONE || error == OT_ERROR_NOT_FOUND);
+    VerifyOrExit(error == kErrorNone || error == kErrorNotFound);
 
     if (proactive)
     {
@@ -647,40 +644,40 @@
     SuccessOrExit(error = mBackboneTmfAgent.SendEmptyAck(aMessage, aMessageInfo));
 
 exit:
-    otLogInfoBbr("HandleBackboneAnswer: %s", otThreadErrorToString(error));
+    otLogInfoBbr("HandleBackboneAnswer: %s", ErrorToString(error));
 }
 
-otError Manager::SendProactiveBackboneNotification(const Ip6::Address &            aDua,
-                                                   const Ip6::InterfaceIdentifier &aMeshLocalIid,
-                                                   uint32_t                        aTimeSinceLastTransaction)
+Error Manager::SendProactiveBackboneNotification(const Ip6::Address &            aDua,
+                                                 const Ip6::InterfaceIdentifier &aMeshLocalIid,
+                                                 uint32_t                        aTimeSinceLastTransaction)
 {
     return SendBackboneAnswer(Get<BackboneRouter::Local>().GetAllDomainBackboneRoutersAddress(),
                               BackboneRouter::kBackboneUdpPort, aDua, aMeshLocalIid, aTimeSinceLastTransaction,
                               Mac::kShortAddrInvalid);
 }
 
-otError Manager::SendBackboneAnswer(const Ip6::MessageInfo &     aQueryMessageInfo,
-                                    const Ip6::Address &         aDua,
-                                    uint16_t                     aSrcRloc16,
-                                    const NdProxyTable::NdProxy &aNdProxy)
+Error Manager::SendBackboneAnswer(const Ip6::MessageInfo &     aQueryMessageInfo,
+                                  const Ip6::Address &         aDua,
+                                  uint16_t                     aSrcRloc16,
+                                  const NdProxyTable::NdProxy &aNdProxy)
 {
     return SendBackboneAnswer(aQueryMessageInfo.GetPeerAddr(), aQueryMessageInfo.GetPeerPort(), aDua,
                               aNdProxy.GetMeshLocalIid(), aNdProxy.GetTimeSinceLastTransaction(), aSrcRloc16);
 }
 
-otError Manager::SendBackboneAnswer(const Ip6::Address &            aDstAddr,
-                                    uint16_t                        aDstPort,
-                                    const Ip6::Address &            aDua,
-                                    const Ip6::InterfaceIdentifier &aMeshLocalIid,
-                                    uint32_t                        aTimeSinceLastTransaction,
-                                    uint16_t                        aSrcRloc16)
+Error Manager::SendBackboneAnswer(const Ip6::Address &            aDstAddr,
+                                  uint16_t                        aDstPort,
+                                  const Ip6::Address &            aDua,
+                                  const Ip6::InterfaceIdentifier &aMeshLocalIid,
+                                  uint32_t                        aTimeSinceLastTransaction,
+                                  uint16_t                        aSrcRloc16)
 {
-    otError          error   = OT_ERROR_NONE;
+    Error            error   = kErrorNone;
     Coap::Message *  message = nullptr;
     Ip6::MessageInfo messageInfo;
     bool             proactive = aDstAddr.IsMulticast();
 
-    VerifyOrExit((message = mBackboneTmfAgent.NewPriorityMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = mBackboneTmfAgent.NewPriorityMessage()) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->Init(proactive ? Coap::kTypeNonConfirmable : Coap::kTypeConfirmable, Coap::kCodePost,
                                         UriPath::kBackboneAnswer));
@@ -713,7 +710,7 @@
 
 exit:
     otLogInfoBbr("Send %s for %s (rloc16=%04x): %s", proactive ? "PRO_BB.ntf" : "BB.ans", aDua.ToString().AsCString(),
-                 aSrcRloc16, otThreadErrorToString(error));
+                 aSrcRloc16, ErrorToString(error));
 
     FreeMessageOnError(message, error);
     return error;
@@ -721,13 +718,13 @@
 
 void Manager::HandleDadBackboneAnswer(const Ip6::Address &aDua, const Ip6::InterfaceIdentifier &aMeshLocalIid)
 {
-    otError                error     = OT_ERROR_NONE;
+    Error                  error     = kErrorNone;
     NdProxyTable::NdProxy *ndProxy   = mNdProxyTable.ResolveDua(aDua);
     bool                   duplicate = false;
 
     OT_UNUSED_VARIABLE(error);
 
-    VerifyOrExit(ndProxy != nullptr, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(ndProxy != nullptr, error = kErrorNotFound);
 
     duplicate = ndProxy->GetMeshLocalIid() != aMeshLocalIid;
 
@@ -742,7 +739,7 @@
     ot::BackboneRouter::NdProxyTable::NotifyDadComplete(*ndProxy, duplicate);
 
 exit:
-    otLogInfoBbr("HandleDadBackboneAnswer: %s, target=%s, mliid=%s, duplicate=%s", otThreadErrorToString(error),
+    otLogInfoBbr("HandleDadBackboneAnswer: %s, target=%s, mliid=%s, duplicate=%s", ErrorToString(error),
                  aDua.ToString().AsCString(), aMeshLocalIid.ToString().AsCString(), duplicate ? "Y" : "N");
 }
 
@@ -765,12 +762,12 @@
                                                   const Ip6::InterfaceIdentifier &aMeshLocalIid,
                                                   uint32_t                        aTimeSinceLastTransaction)
 {
-    otError                error   = OT_ERROR_NONE;
+    Error                  error   = kErrorNone;
     NdProxyTable::NdProxy *ndProxy = mNdProxyTable.ResolveDua(aDua);
 
     OT_UNUSED_VARIABLE(error);
 
-    VerifyOrExit(ndProxy != nullptr, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(ndProxy != nullptr, error = kErrorNotFound);
 
     if (ndProxy->GetMeshLocalIid() == aMeshLocalIid)
     {
@@ -794,7 +791,7 @@
     }
 
 exit:
-    otLogInfoBbr("HandleProactiveBackboneNotification: %s, target=%s, mliid=%s, LTT=%lds", otThreadErrorToString(error),
+    otLogInfoBbr("HandleProactiveBackboneNotification: %s, target=%s, mliid=%s, LTT=%lds", ErrorToString(error),
                  aDua.ToString().AsCString(), aMeshLocalIid.ToString().AsCString(), aTimeSinceLastTransaction);
 }
 #endif // OPENTHREAD_CONFIG_BACKBONE_ROUTER_DUA_NDPROXYING_ENABLE
diff --git a/src/core/backbone_router/bbr_manager.hpp b/src/core/backbone_router/bbr_manager.hpp
index 597aa03..ae1301b 100644
--- a/src/core/backbone_router/bbr_manager.hpp
+++ b/src/core/backbone_router/bbr_manager.hpp
@@ -145,12 +145,12 @@
      * @param[in]  aRloc16  The short address of the address resolution initiator or `Mac::kShortAddrInvalid` for
      *                      DUA DAD.
      *
-     * @retval OT_ERROR_NONE           Successfully sent BB.qry on backbone link.
-     * @retval OT_ERROR_INVALID_STATE  If the Backbone Router is not primary, or not enabled.
-     * @retval OT_ERROR_NO_BUFS        If insufficient message buffers available.
+     * @retval kErrorNone          Successfully sent BB.qry on backbone link.
+     * @retval kErrorInvalidState  If the Backbone Router is not primary, or not enabled.
+     * @retval kErrorNoBufs        If insufficient message buffers available.
      *
      */
-    otError SendBackboneQuery(const Ip6::Address &aDua, uint16_t aRloc16 = Mac::kShortAddrInvalid);
+    Error SendBackboneQuery(const Ip6::Address &aDua, uint16_t aRloc16 = Mac::kShortAddrInvalid);
 
     /**
      * This method send a Proactive Backbone Notification (PRO_BB.ntf) on the Backbone link.
@@ -159,13 +159,13 @@
      * @param[in] aMeshLocalIid                 The Mesh-Local IID to notify.
      * @param[in] aTimeSinceLastTransaction     Time since last transaction (in seconds).
      *
-     * @retval OT_ERROR_NONE           Successfully sent PRO_BB.ntf on backbone link.
-     * @retval OT_ERROR_NO_BUFS        If insufficient message buffers available.
+     * @retval kErrorNone          Successfully sent PRO_BB.ntf on backbone link.
+     * @retval kErrorNoBufs        If insufficient message buffers available.
      *
      */
-    otError SendProactiveBackboneNotification(const Ip6::Address &            aDua,
-                                              const Ip6::InterfaceIdentifier &aMeshLocalIid,
-                                              uint32_t                        aTimeSinceLastTransaction);
+    Error SendProactiveBackboneNotification(const Ip6::Address &            aDua,
+                                            const Ip6::InterfaceIdentifier &aMeshLocalIid,
+                                            uint32_t                        aTimeSinceLastTransaction);
 
 private:
     enum
@@ -204,11 +204,11 @@
     void        HandleBackboneQuery(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
     static void HandleBackboneAnswer(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
     void        HandleBackboneAnswer(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
-    otError     SendBackboneAnswer(const Ip6::MessageInfo &     aQueryMessageInfo,
+    Error       SendBackboneAnswer(const Ip6::MessageInfo &     aQueryMessageInfo,
                                    const Ip6::Address &         aDua,
                                    uint16_t                     aSrcRloc16,
                                    const NdProxyTable::NdProxy &aNdProxy);
-    otError     SendBackboneAnswer(const Ip6::Address &            aDstAddr,
+    Error       SendBackboneAnswer(const Ip6::Address &            aDstAddr,
                                    uint16_t                        aDstPort,
                                    const Ip6::Address &            aDua,
                                    const Ip6::InterfaceIdentifier &aMeshLocalIid,
@@ -229,7 +229,7 @@
 #endif
     void HandleNotifierEvents(Events aEvents);
 
-    static void HandleTimer(Timer &aTimer) { aTimer.GetOwner<Manager>().HandleTimer(); }
+    static void HandleTimer(Timer &aTimer);
     void        HandleTimer(void);
 
 #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE
diff --git a/src/core/backbone_router/multicast_listeners_table.cpp b/src/core/backbone_router/multicast_listeners_table.cpp
index 83e8d45..6eaf3ff 100644
--- a/src/core/backbone_router/multicast_listeners_table.cpp
+++ b/src/core/backbone_router/multicast_listeners_table.cpp
@@ -48,11 +48,11 @@
 
 namespace BackboneRouter {
 
-otError MulticastListenersTable::Add(const Ip6::Address &aAddress, Time aExpireTime)
+Error MulticastListenersTable::Add(const Ip6::Address &aAddress, Time aExpireTime)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(aAddress.IsMulticastLargerThanRealmLocal(), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aAddress.IsMulticastLargerThanRealmLocal(), error = kErrorInvalidArgs);
 
     for (uint16_t i = 0; i < mNumValidListeners; i++)
     {
@@ -66,7 +66,7 @@
         }
     }
 
-    VerifyOrExit(mNumValidListeners < OT_ARRAY_LENGTH(mListeners), error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(mNumValidListeners < OT_ARRAY_LENGTH(mListeners), error = kErrorNoBufs);
 
     mListeners[mNumValidListeners].SetAddress(aAddress);
     mListeners[mNumValidListeners].SetExpireTime(aExpireTime);
@@ -87,7 +87,7 @@
 
 void MulticastListenersTable::Remove(const Ip6::Address &aAddress)
 {
-    otError error = OT_ERROR_NOT_FOUND;
+    Error error = kErrorNotFound;
 
     for (uint16_t i = 0; i < mNumValidListeners; i++)
     {
@@ -108,7 +108,7 @@
                 mCallback(mCallbackContext, OT_BACKBONE_ROUTER_MULTICAST_LISTENER_REMOVED, &aAddress);
             }
 
-            ExitNow(error = OT_ERROR_NONE);
+            ExitNow(error = kErrorNone);
         }
     }
 
@@ -124,7 +124,7 @@
 
     while (mNumValidListeners > 0 && now >= mListeners[0].GetExpireTime())
     {
-        LogMulticastListenersTable("Expire", mListeners[0].GetAddress(), mListeners[0].GetExpireTime(), OT_ERROR_NONE);
+        LogMulticastListenersTable("Expire", mListeners[0].GetAddress(), mListeners[0].GetExpireTime(), kErrorNone);
         address = mListeners[0].GetAddress();
 
         mNumValidListeners--;
@@ -147,7 +147,7 @@
 void MulticastListenersTable::LogMulticastListenersTable(const char *        aAction,
                                                          const Ip6::Address &aAddress,
                                                          TimeMilli           aExpireTime,
-                                                         otError             aError)
+                                                         Error               aError)
 {
     OT_UNUSED_VARIABLE(aAction);
     OT_UNUSED_VARIABLE(aAddress);
@@ -155,7 +155,7 @@
     OT_UNUSED_VARIABLE(aError);
 
     otLogDebgBbr("MulticastListenersTable: %s %s expire %u: %s", aAction, aAddress.ToString().AsCString(),
-                 aExpireTime.GetValue(), otThreadErrorToString(aError));
+                 aExpireTime.GetValue(), ErrorToString(aError));
 }
 
 void MulticastListenersTable::FixHeap(uint16_t aIndex)
@@ -287,13 +287,13 @@
     }
 }
 
-otError MulticastListenersTable::GetNext(otBackboneRouterMulticastListenerIterator &aIterator,
-                                         otBackboneRouterMulticastListenerInfo &    aListenerInfo)
+Error MulticastListenersTable::GetNext(otBackboneRouterMulticastListenerIterator &aIterator,
+                                       otBackboneRouterMulticastListenerInfo &    aListenerInfo)
 {
-    otError   error = OT_ERROR_NONE;
+    Error     error = kErrorNone;
     TimeMilli now;
 
-    VerifyOrExit(aIterator < mNumValidListeners, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(aIterator < mNumValidListeners, error = kErrorNotFound);
 
     now = TimerMilli::GetNow();
 
diff --git a/src/core/backbone_router/multicast_listeners_table.hpp b/src/core/backbone_router/multicast_listeners_table.hpp
index 0ff0159..56b1b78 100644
--- a/src/core/backbone_router/multicast_listeners_table.hpp
+++ b/src/core/backbone_router/multicast_listeners_table.hpp
@@ -119,12 +119,12 @@
      * @param[in] aAddress     The Multicast Listener address.
      * @param[in] aExpireTime  The Multicast Listener expire time.
      *
-     * @retval OT_ERROR_NONE          If the Multicast Listener was successfully added.
-     * @retval OT_ERROR_INVALID_ARGS  If the Multicast Listener address was invalid.
-     * @retval OT_ERROR_NO_BUFS       No space available to save the Multicast Listener.
+     * @retval kErrorNone         If the Multicast Listener was successfully added.
+     * @retval kErrorInvalidArgs  If the Multicast Listener address was invalid.
+     * @retval kErrorNoBufs       No space available to save the Multicast Listener.
      *
      */
-    otError Add(const Ip6::Address &aAddress, TimeMilli aExpireTime);
+    Error Add(const Ip6::Address &aAddress, TimeMilli aExpireTime);
 
     /**
      * This method removes a given Multicast Listener.
@@ -181,12 +181,12 @@
      * @param[in] aIterator       A pointer to the Multicast Listener Iterator.
      * @param[out] aListenerInfo  A pointer to where the Multicast Listener info is placed.
      *
-     * @retval OT_ERROR_NONE        Successfully found the next Multicast Listener info.
-     * @retval OT_ERROR_NOT_FOUND   No subsequent Multicast Listener was found.
+     * @retval kErrorNone         Successfully found the next Multicast Listener info.
+     * @retval kErrorNotFound     No subsequent Multicast Listener was found.
      *
      */
-    otError GetNext(otBackboneRouterMulticastListenerIterator &aIterator,
-                    otBackboneRouterMulticastListenerInfo &    aListenerInfo);
+    Error GetNext(otBackboneRouterMulticastListenerIterator &aIterator,
+                  otBackboneRouterMulticastListenerInfo &    aListenerInfo);
 
 private:
     enum
@@ -213,7 +213,7 @@
     void LogMulticastListenersTable(const char *        aAction,
                                     const Ip6::Address &aAddress,
                                     TimeMilli           aExpireTime,
-                                    otError             aError);
+                                    Error               aError);
 
     void FixHeap(uint16_t aIndex);
     bool SiftHeapElemDown(uint16_t aIndex);
diff --git a/src/core/backbone_router/ndproxy_table.cpp b/src/core/backbone_router/ndproxy_table.cpp
index d8f3a88..d2630be 100644
--- a/src/core/backbone_router/ndproxy_table.cpp
+++ b/src/core/backbone_router/ndproxy_table.cpp
@@ -95,9 +95,9 @@
 {
     NdProxyTable &table = GetInstance().Get<BackboneRouter::NdProxyTable>();
 
-    mCurrent = &table.mProxies[0];
+    mItem = &table.mProxies[0];
 
-    if (!MatchesFilter(*mCurrent, mFilter))
+    if (!MatchesFilter(*mItem, mFilter))
     {
         Advance();
     }
@@ -107,7 +107,7 @@
     : InstanceLocator(aInstance)
 {
     NdProxyTable &table = GetInstance().Get<BackboneRouter::NdProxyTable>();
-    mCurrent            = OT_ARRAY_END(table.mProxies);
+    mItem               = OT_ARRAY_END(table.mProxies);
 }
 
 void NdProxyTable::Iterator::Advance(void)
@@ -116,8 +116,8 @@
 
     do
     {
-        mCurrent++;
-    } while (mCurrent < OT_ARRAY_END(table.mProxies) && !MatchesFilter(*mCurrent, mFilter));
+        mItem++;
+    } while (mItem < OT_ARRAY_END(table.mProxies) && !MatchesFilter(*mItem, mFilter));
 }
 
 void NdProxyTable::Erase(NdProxy &aNdProxy)
@@ -149,18 +149,18 @@
     otLogNoteBbr("NdProxyTable::Clear!");
 }
 
-otError NdProxyTable::Register(const Ip6::InterfaceIdentifier &aAddressIid,
-                               const Ip6::InterfaceIdentifier &aMeshLocalIid,
-                               uint16_t                        aRloc16,
-                               const uint32_t *                aTimeSinceLastTransaction)
+Error NdProxyTable::Register(const Ip6::InterfaceIdentifier &aAddressIid,
+                             const Ip6::InterfaceIdentifier &aMeshLocalIid,
+                             uint16_t                        aRloc16,
+                             const uint32_t *                aTimeSinceLastTransaction)
 {
-    otError  error                    = OT_ERROR_NONE;
+    Error    error                    = kErrorNone;
     NdProxy *proxy                    = FindByAddressIid(aAddressIid);
     uint32_t timeSinceLastTransaction = aTimeSinceLastTransaction == nullptr ? 0 : *aTimeSinceLastTransaction;
 
     if (proxy != nullptr)
     {
-        VerifyOrExit(proxy->mMeshLocalIid == aMeshLocalIid, error = OT_ERROR_DUPLICATED);
+        VerifyOrExit(proxy->mMeshLocalIid == aMeshLocalIid, error = kErrorDuplicated);
 
         proxy->Update(aRloc16, timeSinceLastTransaction);
         NotifyDuaRegistrationOnBackboneLink(*proxy, /* aIsRenew */ true);
@@ -178,7 +178,7 @@
         proxy = FindInvalid();
 
         // TODO: evict stale DUA entries to have room for this new DUA.
-        VerifyOrExit(proxy != nullptr, error = OT_ERROR_NO_BUFS);
+        VerifyOrExit(proxy != nullptr, error = kErrorNoBufs);
     }
 
     proxy->Init(aAddressIid, aMeshLocalIid, aRloc16, timeSinceLastTransaction);
@@ -186,7 +186,7 @@
 
 exit:
     otLogInfoBbr("NdProxyTable::Register %s MLIID %s RLOC16 %04x LTT %u => %s", aAddressIid.ToString().AsCString(),
-                 aMeshLocalIid.ToString().AsCString(), aRloc16, timeSinceLastTransaction, otThreadErrorToString(error));
+                 aMeshLocalIid.ToString().AsCString(), aRloc16, timeSinceLastTransaction, ErrorToString(error));
     return error;
 }
 
@@ -257,7 +257,7 @@
         {
             mIsAnyDadInProcess = true;
 
-            if (Get<BackboneRouter::Manager>().SendBackboneQuery(GetDua(proxy)) == OT_ERROR_NONE)
+            if (Get<BackboneRouter::Manager>().SendBackboneQuery(GetDua(proxy)) == kErrorNone)
             {
                 proxy.IncreaseDadAttampts();
             }
@@ -335,11 +335,11 @@
     }
 }
 
-otError NdProxyTable::GetInfo(const Ip6::Address &aDua, otBackboneRouterNdProxyInfo &aNdProxyInfo)
+Error NdProxyTable::GetInfo(const Ip6::Address &aDua, otBackboneRouterNdProxyInfo &aNdProxyInfo)
 {
-    otError error = OT_ERROR_NOT_FOUND;
+    Error error = kErrorNotFound;
 
-    VerifyOrExit(Get<Leader>().IsDomainUnicast(aDua), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(Get<Leader>().IsDomainUnicast(aDua), error = kErrorInvalidArgs);
 
     for (NdProxy &proxy : Iterate(kFilterValid))
     {
@@ -349,7 +349,7 @@
             aNdProxyInfo.mTimeSinceLastTransaction = proxy.GetTimeSinceLastTransaction();
             aNdProxyInfo.mRloc16                   = proxy.mRloc16;
 
-            ExitNow(error = OT_ERROR_NONE);
+            ExitNow(error = kErrorNone);
         }
     }
 
diff --git a/src/core/backbone_router/ndproxy_table.hpp b/src/core/backbone_router/ndproxy_table.hpp
index ece89b9..e8d28d5 100644
--- a/src/core/backbone_router/ndproxy_table.hpp
+++ b/src/core/backbone_router/ndproxy_table.hpp
@@ -40,6 +40,7 @@
 #include <openthread/backbone_router_ftd.h>
 
 #include "backbone_router/bbr_leader.hpp"
+#include "common/iterator_utils.hpp"
 #include "common/locator.hpp"
 #include "common/non_copyable.hpp"
 #include "common/time.hpp"
@@ -146,15 +147,15 @@
      * @param[in] aRloc16                    The RLOC16.
      * @param[in] aTimeSinceLastTransaction  Time since last transaction (in seconds).
      *
-     * @retval OT_ERROR_NONE        If registered successfully.
-     * @retval OT_ERROR_DUPLICATED  If the Ip6 address IID is a duplicate.
-     * @retval OT_ERROR_NO_BUFS     Insufficient buffer space available to register.
+     * @retval kErrorNone        If registered successfully.
+     * @retval kErrorDuplicated  If the Ip6 address IID is a duplicate.
+     * @retval kErrorNoBufs      Insufficient buffer space available to register.
      *
      */
-    otError Register(const Ip6::InterfaceIdentifier &aAddressIid,
-                     const Ip6::InterfaceIdentifier &aMeshLocalIid,
-                     uint16_t                        aRloc16,
-                     const uint32_t *                aTimeSinceLastTransaction);
+    Error Register(const Ip6::InterfaceIdentifier &aAddressIid,
+                   const Ip6::InterfaceIdentifier &aMeshLocalIid,
+                   uint16_t                        aRloc16,
+                   const uint32_t *                aTimeSinceLastTransaction);
 
     /**
      * This method checks if a given Ip6 address IID was registered.
@@ -223,11 +224,11 @@
      * @param[in] aDua          The Domain Unicast Address to get info.
      * @param[in] aNdProxyInfo  A pointer to the ND Proxy info.
      *
-     * @retval OT_ERROR_NONE       Successfully retrieve the ND Proxy info.
-     * @retval OT_ERROR_NOT_FOUND  Failed to find the Domain Unicast Address in the ND Proxy table.
+     * @retval kErrorNone       Successfully retrieve the ND Proxy info.
+     * @retval kErrorNotFound   Failed to find the Domain Unicast Address in the ND Proxy table.
      *
      */
-    otError GetInfo(const Ip6::Address &aDua, otBackboneRouterNdProxyInfo &aNdProxyInfo);
+    Error GetInfo(const Ip6::Address &aDua, otBackboneRouterNdProxyInfo &aNdProxyInfo);
 
 private:
     enum
@@ -246,8 +247,9 @@
      * This class represents an iterator for iterating through the NdProxy Table.
      *
      */
-    class Iterator : public InstanceLocator
+    class Iterator : public InstanceLocator, public ItemPtrIterator<NdProxy, Iterator>
     {
+        friend class ItemPtrIterator<NdProxy, Iterator>;
         friend class NdProxyTable;
         friend class IteratorBuilder;
 
@@ -260,18 +262,9 @@
         Iterator(Instance &aInstance, Filter aFilter);
         Iterator(Instance &aInstance, IteratorType);
 
-        bool           IsDone(void) const { return (mCurrent == nullptr); }
-        void           Advance(void);
-        void           operator++(void) { Advance(); }
-        void           operator++(int) { Advance(); }
-        const NdProxy &operator*(void)const { return *mCurrent; }
-        bool           operator==(const Iterator &aOther) const { return mCurrent == aOther.mCurrent; }
-        bool           operator!=(const Iterator &aOther) const { return !(*this == aOther); }
-        NdProxy *      operator->(void) { return mCurrent; }
-        NdProxy &      operator*(void) { return *mCurrent; }
+        void Advance(void);
 
-        NdProxy *mCurrent;
-        Filter   mFilter;
+        Filter mFilter;
     };
 
     class IteratorBuilder : public InstanceLocator
diff --git a/src/core/border_router/infra_if_platform.cpp b/src/core/border_router/infra_if_platform.cpp
new file mode 100644
index 0000000..8a0d783
--- /dev/null
+++ b/src/core/border_router/infra_if_platform.cpp
@@ -0,0 +1,67 @@
+/*
+ *    Copyright (c) 2020, The OpenThread Authors.
+ *    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
+ *   This file implements infrastructure interface platform APIs.
+ */
+
+#include "openthread-core-config.h"
+
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+
+#include <openthread/platform/infra_if.h>
+
+#include "border_router/routing_manager.hpp"
+#include "common/instance.hpp"
+
+using namespace ot;
+
+extern "C" void otPlatInfraIfRecvIcmp6Nd(otInstance *        aInstance,
+                                         uint32_t            aInfraIfIndex,
+                                         const otIp6Address *aSrcAddress,
+                                         const uint8_t *     aBuffer,
+                                         uint16_t            aBufferLength)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    instance.Get<BorderRouter::RoutingManager>().RecvIcmp6Message(
+        aInfraIfIndex, static_cast<const Ip6::Address &>(*aSrcAddress), aBuffer, aBufferLength);
+}
+
+extern "C" otError otPlatInfraIfStateChanged(otInstance *        aInstance,
+                                             uint32_t            aInfraIfIndex,
+                                             bool                aIsRunning,
+                                             const otIp6Address *aLinkLocalAddress)
+{
+    Instance &instance = *static_cast<Instance *>(aInstance);
+
+    return instance.Get<BorderRouter::RoutingManager>().HandleInfraIfStateChanged(
+        aInfraIfIndex, aIsRunning, static_cast<const Ip6::Address *>(aLinkLocalAddress));
+}
+
+#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
diff --git a/src/core/border_router/router_advertisement.cpp b/src/core/border_router/router_advertisement.cpp
new file mode 100644
index 0000000..ea3d11a
--- /dev/null
+++ b/src/core/border_router/router_advertisement.cpp
@@ -0,0 +1,213 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file includes implementations for ICMPv6 Router Advertisement.
+ *
+ */
+
+#include "border_router/router_advertisement.hpp"
+
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+
+namespace ot {
+
+namespace BorderRouter {
+
+namespace RouterAdv {
+
+const Option *Option::GetNextOption(const Option *aCurOption, const uint8_t *aBuffer, uint16_t aBufferLength)
+{
+    const uint8_t *nextOption = nullptr;
+    const uint8_t *bufferEnd  = aBuffer + aBufferLength;
+
+    VerifyOrExit(aBuffer != nullptr, nextOption = nullptr);
+
+    if (aCurOption == nullptr)
+    {
+        nextOption = aBuffer;
+    }
+    else
+    {
+        nextOption = reinterpret_cast<const uint8_t *>(aCurOption) + aCurOption->GetSize();
+    }
+
+    VerifyOrExit(nextOption + sizeof(Option) <= bufferEnd, nextOption = nullptr);
+    VerifyOrExit(reinterpret_cast<const Option *>(nextOption)->GetSize() > 0, nextOption = nullptr);
+    VerifyOrExit(nextOption + reinterpret_cast<const Option *>(nextOption)->GetSize() <= bufferEnd,
+                 nextOption = nullptr);
+
+exit:
+    return reinterpret_cast<const Option *>(nextOption);
+}
+
+PrefixInfoOption::PrefixInfoOption(void)
+    : Option(Type::kPrefixInfo, sizeof(*this) / kLengthUnit)
+    , mPrefixLength(0)
+    , mReserved1(0)
+    , mValidLifetime(0)
+    , mPreferredLifetime(0)
+    , mReserved2(0)
+{
+    OT_UNUSED_VARIABLE(mReserved2);
+
+    mPrefix.Clear();
+}
+
+void PrefixInfoOption::SetOnLink(bool aOnLink)
+{
+    if (aOnLink)
+    {
+        mReserved1 |= kOnLinkFlagMask;
+    }
+    else
+    {
+        mReserved1 &= ~kOnLinkFlagMask;
+    }
+}
+
+void PrefixInfoOption::SetAutoAddrConfig(bool aAutoAddrConfig)
+{
+    if (aAutoAddrConfig)
+    {
+        mReserved1 |= kAutoConfigFlagMask;
+    }
+    else
+    {
+        mReserved1 &= ~kAutoConfigFlagMask;
+    }
+}
+
+void PrefixInfoOption::SetPrefix(const Ip6::Prefix &aPrefix)
+{
+    mPrefixLength = aPrefix.mLength;
+    mPrefix       = static_cast<const Ip6::Address &>(aPrefix.mPrefix);
+}
+
+Ip6::Prefix PrefixInfoOption::GetPrefix(void) const
+{
+    Ip6::Prefix prefix;
+
+    prefix.Set(mPrefix.GetBytes(), mPrefixLength);
+    return prefix;
+}
+
+RouteInfoOption::RouteInfoOption(void)
+    : Option(Type::kRouteInfo, 0)
+    , mPrefixLength(0)
+    , mReserved(0)
+    , mRouteLifetime(0)
+{
+    OT_UNUSED_VARIABLE(mReserved);
+
+    mPrefix.Clear();
+}
+
+void RouteInfoOption::SetPreference(otRoutePreference aPreference)
+{
+    mReserved &= ~kPreferenceMask;
+    mReserved |= (static_cast<uint8_t>(aPreference) << kPreferenceOffset) & kPreferenceMask;
+}
+
+otRoutePreference RouteInfoOption::GetPreference(void) const
+{
+    otRoutePreference preference;
+
+    switch ((mReserved & kPreferenceMask) >> kPreferenceOffset)
+    {
+    case kPreferenceLow:
+        preference = OT_ROUTE_PREFERENCE_LOW;
+        break;
+    case kPreferenceMed:
+        preference = OT_ROUTE_PREFERENCE_MED;
+        break;
+    case kPreferenceHigh:
+        preference = OT_ROUTE_PREFERENCE_HIGH;
+        break;
+    default:
+        preference = OT_ROUTE_PREFERENCE_LOW;
+        break;
+    }
+
+    return preference;
+}
+
+void RouteInfoOption::SetPrefix(const Ip6::Prefix &aPrefix)
+{
+    // The total length (in bytes) of a Router Information Option
+    // is: (8 bytes fixed option header) + (0, 8, or 16 bytes prefix).
+    // Because the length of the option must be padded with 8 bytes,
+    // the length of the prefix (in bits) must be padded with 64 bits.
+    SetLength((aPrefix.mLength + kLengthUnit * CHAR_BIT - 1) / (kLengthUnit * CHAR_BIT) + 1);
+
+    mPrefixLength = aPrefix.mLength;
+    mPrefix       = static_cast<const Ip6::Address &>(aPrefix.mPrefix);
+}
+
+Ip6::Prefix RouteInfoOption::GetPrefix(void) const
+{
+    Ip6::Prefix prefix;
+
+    prefix.Set(mPrefix.GetBytes(), mPrefixLength);
+    return prefix;
+}
+
+bool RouteInfoOption::IsValid(void) const
+{
+    otRoutePreference pref = GetPreference();
+
+    return (GetLength() == 1 || GetLength() == 2 || GetLength() == 3) &&
+           (mPrefixLength <= OT_IP6_ADDRESS_SIZE * CHAR_BIT) &&
+           (pref == OT_ROUTE_PREFERENCE_LOW || pref == OT_ROUTE_PREFERENCE_MED || pref == OT_ROUTE_PREFERENCE_HIGH);
+}
+
+RouterAdvMessage::RouterAdvMessage(void)
+    : mReachableTime(0)
+    , mRetransTimer(0)
+{
+    OT_UNUSED_VARIABLE(mReachableTime);
+    OT_UNUSED_VARIABLE(mRetransTimer);
+
+    mHeader.Clear();
+    mHeader.SetType(Ip6::Icmp::Header::kTypeRouterAdvert);
+}
+
+RouterSolicitMessage::RouterSolicitMessage(void)
+{
+    mHeader.Clear();
+    mHeader.SetType(Ip6::Icmp::Header::kTypeRouterSolicit);
+}
+
+} // namespace RouterAdv
+
+} // namespace BorderRouter
+
+} // namespace ot
+
+#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
diff --git a/src/core/border_router/router_advertisement.hpp b/src/core/border_router/router_advertisement.hpp
new file mode 100644
index 0000000..e346412
--- /dev/null
+++ b/src/core/border_router/router_advertisement.hpp
@@ -0,0 +1,440 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file includes definitions for IPv6 Router Advertisement.
+ *
+ * See RFC 4861: Neighbor Discovery for IP version 6 (https://tools.ietf.org/html/rfc4861).
+ *
+ */
+
+#ifndef ROUTER_ADVERTISEMENT_HPP_
+#define ROUTER_ADVERTISEMENT_HPP_
+
+#include "openthread-core-config.h"
+
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+
+#include <stdint.h>
+
+#include <openthread/netdata.h>
+#include <openthread/platform/toolchain.h>
+
+#include "common/encoding.hpp"
+#include "net/icmp6.hpp"
+#include "net/ip6.hpp"
+
+using ot::Encoding::BigEndian::HostSwap16;
+using ot::Encoding::BigEndian::HostSwap32;
+
+namespace ot {
+
+namespace BorderRouter {
+
+namespace RouterAdv {
+
+/**
+ * This class represents the variable length options in Neighbor
+ * Discovery messages.
+ *
+ * @sa PrefixInfoOption
+ * @sa RouteInfoOption
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class Option
+{
+public:
+    enum class Type : uint8_t
+    {
+        kPrefixInfo = 3,  ///< Prefix Information Option.
+        kRouteInfo  = 24, ///< Route Information Option.
+    };
+
+    enum : uint8_t
+    {
+        kLengthUnit = 8u, ///< The unit of length in octets.
+    };
+
+    /**
+     * This constructor initializes the option with given type and length.
+     *
+     * @param[in]  aType    The type of this option.
+     * @param[in]  aLength  The length of this option in unit of 8 octets.
+     *
+     */
+    explicit Option(Type aType, uint8_t aLength = 0)
+        : mType(aType)
+        , mLength(aLength)
+    {
+    }
+
+    /**
+     * This method returns the type of this option.
+     *
+     * @returns  The option type.
+     *
+     */
+    Type GetType(void) const { return mType; }
+
+    /**
+     * This method sets the size of the option (in bytes).
+     *
+     * Since the option must end on their natural 64-bits boundaries,
+     * the actual length set to the option is padded to (aSize + 7) / 8 * 8.
+     *
+     * @param[in]  aSize  The size of the option in unit of 1 byte.
+     *
+     */
+    void SetSize(uint16_t aSize) { mLength = (aSize + kLengthUnit - 1) / kLengthUnit; }
+
+    /**
+     * This method returns the size of the option (in bytes).
+     *
+     * @returns  The size of the option in unit of 1 byte.
+     *
+     */
+    uint16_t GetSize(void) const { return mLength * kLengthUnit; }
+
+    /**
+     * This method sets the length of the option (in unit of 8 bytes).
+     *
+     * @param[in]  aLength  The length of the option in unit of 8 bytes.
+     *
+     */
+    void SetLength(uint8_t aLength) { mLength = aLength; }
+
+    /**
+     * This method returns the length of the option (in unit of 8 bytes).
+     *
+     * @returns  The length of the option in unit of 8 bytes.
+     *
+     */
+    uint16_t GetLength(void) const { return mLength; }
+
+    /**
+     * This helper method returns a pointer to the next valid option in the buffer.
+     *
+     * @param[in]  aCurOption     The current option. Use nullptr to get the first option.
+     * @param[in]  aBuffer        The buffer within which the options are held.
+     * @param[in]  aBufferLength  The length of the buffer.
+     *
+     * @returns  A pointer to the next option if there are a valid one. Otherwise, nullptr.
+     *
+     */
+    static const Option *GetNextOption(const Option *aCurOption, const uint8_t *aBuffer, uint16_t aBufferLength);
+
+    /**
+     * This method tells whether this option is valid.
+     *
+     * @return  A boolean that indicates whether this option is valid.
+     *
+     */
+    bool IsValid(void) const { return mLength > 0; }
+
+private:
+    Type    mType;   // Type of the option.
+    uint8_t mLength; // Length of the option in unit of 8 octets,
+                     // including the `type` and `length` fields.
+} OT_TOOL_PACKED_END;
+
+/**
+ * This class represents the Prefix Information Option.
+ *
+ * See section 4.6.2 of RFC 4861 for definition of this option.
+ * https://tools.ietf.org/html/rfc4861#section-4.6.2
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class PrefixInfoOption : public Option
+{
+public:
+    /**
+     * This constructor initializes this option with zero prefix
+     * length, valid lifetime and preferred lifetime.
+     *
+     */
+    PrefixInfoOption(void);
+
+    /**
+     * This method sets the on-link (L) flag.
+     *
+     * @param[in]  aOnLink  A boolean indicates whether the prefix is on-link or off-link.
+     *
+     */
+    void SetOnLink(bool aOnLink);
+
+    /**
+     * This method sets the autonomous address-configuration (A) flag.
+     *
+     * @param[in]  aAutoAddrConfig  A boolean indicates whether this prefix can be used
+     *                              for SLAAC.
+     *
+     */
+    void SetAutoAddrConfig(bool aAutoAddrConfig);
+
+    /**
+     * This method set the valid lifetime of the prefix in seconds.
+     *
+     * @param[in]  aValidLifetime  The valid lifetime in seconds.
+     *
+     */
+    void SetValidLifetime(uint32_t aValidLifetime) { mValidLifetime = HostSwap32(aValidLifetime); }
+
+    /**
+     * THis method returns the valid lifetime of the prefix in seconds.
+     *
+     * @returns  The valid lifetime in seconds.
+     *
+     */
+    uint32_t GetValidLifetime(void) const { return HostSwap32(mValidLifetime); }
+
+    /**
+     * This method sets the preferred lifetime of the prefix in seconds.
+     *
+     * @param[in]  aPreferredLifetime  The preferred lifetime in seconds.
+     *
+     */
+    void SetPreferredLifetime(uint32_t aPreferredLifetime) { mPreferredLifetime = HostSwap32(aPreferredLifetime); }
+
+    /**
+     * This method sets the prefix.
+     *
+     * @param[in]  aPrefix  The prefix contained in this option.
+     *
+     */
+    void SetPrefix(const Ip6::Prefix &aPrefix);
+
+    /**
+     * This method returns the prefix in this option.
+     *
+     * @returns  The IPv6 prefix in this option.
+     *
+     */
+    Ip6::Prefix GetPrefix(void) const;
+
+    /**
+     * This method tells whether this option is valid.
+     *
+     * @returns  A boolean indicates whether this option is valid.
+     *
+     */
+    bool IsValid(void) const
+    {
+        return (GetSize() == sizeof(*this)) && (mPrefixLength <= OT_IP6_ADDRESS_SIZE * CHAR_BIT);
+    }
+
+private:
+    enum : uint8_t
+    {
+        kAutoConfigFlagMask = 0x40u, // Bit mask of the Automatic Address Configure flag.
+        kOnLinkFlagMask     = 0x80u, // Bit mask of the On-link flag.
+    };
+
+    uint8_t      mPrefixLength;      // The prefix length in bits.
+    uint8_t      mReserved1;         // The reserved field.
+    uint32_t     mValidLifetime;     // The valid lifetime of the prefix.
+    uint32_t     mPreferredLifetime; // The preferred lifetime of the prefix.
+    uint32_t     mReserved2;         // The reserved field.
+    Ip6::Address mPrefix;            // The prefix.
+} OT_TOOL_PACKED_END;
+
+static_assert(sizeof(PrefixInfoOption) == 32, "invalid PrefixInfoOption structure");
+
+/**
+ * This class represents the Route Information Option.
+ *
+ * See section 2.3 of RFC 4191 for definition of this option.
+ * https://tools.ietf.org/html/rfc4191#section-2.3
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class RouteInfoOption : public Option
+{
+public:
+    /**
+     * This constructor initializes this option with zero prefix length.
+     *
+     */
+    RouteInfoOption(void);
+
+    /**
+     * This method sets the route preference.
+     *
+     * @param[in]  aPreference  The route preference.
+     *
+     */
+    void SetPreference(otRoutePreference aPreference);
+
+    /**
+     * This method returns the route preference.
+     *
+     * @returns  The route preference.
+     *
+     */
+    otRoutePreference GetPreference(void) const;
+
+    /**
+     * This method sets the lifetime of the route in seconds.
+     *
+     * @param[in]  aLifetime  The lifetime of the route in seconds.
+     *
+     */
+    void SetRouteLifetime(uint32_t aLifetime) { mRouteLifetime = HostSwap32(aLifetime); }
+
+    /**
+     * This method returns Route Lifetime in seconds.
+     *
+     * @returns  The Route Lifetime in seconds.
+     *
+     */
+    uint32_t GetRouteLifetime(void) const { return HostSwap32(mRouteLifetime); }
+
+    /**
+     * This method sets the prefix.
+     *
+     * @param[in]  aPrefix  The prefix contained in this option.
+     *
+     */
+    void SetPrefix(const Ip6::Prefix &aPrefix);
+
+    /**
+     * This method returns the prefix in this option.
+     *
+     * @returns  The IPv6 prefix in this option.
+     *
+     */
+    Ip6::Prefix GetPrefix(void) const;
+
+    /**
+     * This method tells whether this option is valid.
+     *
+     * @returns  A boolean indicates whether this option is valid.
+     *
+     */
+    bool IsValid(void) const;
+
+private:
+    enum : uint8_t
+    {
+        kPreferenceMask   = 0x18u,
+        kPreferenceOffset = 3u,
+    };
+
+    enum : uint8_t
+    {
+        kPreferenceLow  = 0x03,
+        kPreferenceMed  = 0x00,
+        kPreferenceHigh = 0x01,
+    };
+
+    uint8_t      mPrefixLength;  // The prefix length in bits.
+    uint8_t      mReserved;      // The reserved field.
+    uint32_t     mRouteLifetime; // The lifetime in seconds.
+    Ip6::Address mPrefix;        // The prefix.
+} OT_TOOL_PACKED_END;
+
+static_assert(sizeof(RouteInfoOption) == 24, "invalid RouteInfoOption structure");
+
+/**
+ * This class implements the Router Advertisement message.
+ *
+ * See section 4.2 of RFC 4861 for definition of this message.
+ * https://tools.ietf.org/html/rfc4861#section-4.2
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class RouterAdvMessage
+{
+public:
+    /**
+     * This constructor initializes the Router Advertisement message with
+     * zero router lifetime, reachable time and retransmission timer.
+     *
+     */
+    RouterAdvMessage(void);
+
+    /**
+     * This method sets the Router Lifetime in seconds.
+     *
+     * Zero Router Lifetime means we are not a default router.
+     *
+     * @param[in]  aRouterLifetime  The router lifetime in seconds.
+     *
+     */
+    void SetRouterLifetime(uint16_t aRouterLifetime)
+    {
+        mHeader.mData.m16[kRouteLifetimeIdx] = HostSwap16(aRouterLifetime);
+    }
+
+private:
+    enum : uint8_t
+    {
+        kRouteLifetimeIdx = 1u, // The index of Route Lifetime in ICMPv6 Header Data. in unit of 2 octets.
+    };
+
+    Ip6::Icmp::Header mHeader;        // The common ICMPv6 header.
+    uint32_t          mReachableTime; // The reachable time. In milliseconds.
+    uint32_t          mRetransTimer;  // The retransmission timer. In milliseconds.
+} OT_TOOL_PACKED_END;
+
+static_assert(sizeof(RouterAdvMessage) == 16, "invalid RouterAdvMessage structure");
+
+/**
+ * This class implements the Router Solicitation message.
+ *
+ * See section 4.1 of RFC 4861 for definition of this message.
+ * https://tools.ietf.org/html/rfc4861#section-4.1
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class RouterSolicitMessage
+{
+public:
+    /**
+     * This constructor initializes the Router Solicitation message.
+     *
+     */
+    RouterSolicitMessage(void);
+
+private:
+    Ip6::Icmp::Header mHeader; // The common ICMPv6 header.
+} OT_TOOL_PACKED_END;
+
+static_assert(sizeof(RouterSolicitMessage) == 8, "invalid RouterSolicitMessage structure");
+
+} // namespace RouterAdv
+
+} // namespace BorderRouter
+
+} // namespace ot
+
+#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+
+#endif // ROUTER_ADVERTISEMENT_HPP_
diff --git a/src/core/border_router/routing_manager.cpp b/src/core/border_router/routing_manager.cpp
new file mode 100644
index 0000000..54459e9
--- /dev/null
+++ b/src/core/border_router/routing_manager.cpp
@@ -0,0 +1,1189 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file includes implementation for the RA-based routing management.
+ *
+ */
+
+#include "border_router/routing_manager.hpp"
+
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+
+#include <string.h>
+
+#include <openthread/platform/infra_if.h>
+
+#include "common/code_utils.hpp"
+#include "common/debug.hpp"
+#include "common/instance.hpp"
+#include "common/locator-getters.hpp"
+#include "common/logging.hpp"
+#include "common/random.hpp"
+#include "common/settings.hpp"
+#include "net/ip6.hpp"
+#include "thread/network_data_leader.hpp"
+#include "thread/network_data_local.hpp"
+#include "thread/network_data_notifier.hpp"
+
+namespace ot {
+
+namespace BorderRouter {
+
+RoutingManager::RoutingManager(Instance &aInstance)
+    : InstanceLocator(aInstance)
+    , mIsRunning(false)
+    , mIsEnabled(false)
+    , mInfraIfIsRunning(false)
+    , mInfraIfIndex(0)
+    , mAdvertisedOmrPrefixNum(0)
+    , mAdvertisedOnLinkPrefix(nullptr)
+    , mDiscoveredPrefixNum(0)
+    , mDiscoveredPrefixInvalidTimer(aInstance, HandleDiscoveredPrefixInvalidTimer)
+    , mRouterAdvertisementTimer(aInstance, HandleRouterAdvertisementTimer)
+    , mRouterAdvertisementCount(0)
+    , mRouterSolicitTimer(aInstance, HandleRouterSolicitTimer)
+    , mRouterSolicitCount(0)
+    , mRoutingPolicyTimer(aInstance, HandleRoutingPolicyTimer)
+{
+    mInfraIfLinkLocalAddress.Clear();
+
+    mLocalOmrPrefix.Clear();
+    memset(mAdvertisedOmrPrefixes, 0, sizeof(mAdvertisedOmrPrefixes));
+
+    mLocalOnLinkPrefix.Clear();
+
+    memset(mDiscoveredPrefixes, 0, sizeof(mDiscoveredPrefixes));
+}
+
+Error RoutingManager::Init(uint32_t aInfraIfIndex, bool aInfraIfIsRunning, const Ip6::Address *aInfraIfLinkLocalAddress)
+{
+    Error error;
+
+    VerifyOrExit(!IsInitialized(), error = kErrorInvalidState);
+    VerifyOrExit(aInfraIfIndex > 0, error = kErrorInvalidArgs);
+
+    SuccessOrExit(error = LoadOrGenerateRandomOmrPrefix());
+    SuccessOrExit(error = LoadOrGenerateRandomOnLinkPrefix());
+
+    mInfraIfIndex = aInfraIfIndex;
+
+    // Initialize the infra interface status.
+    SuccessOrExit(error = HandleInfraIfStateChanged(mInfraIfIndex, aInfraIfIsRunning, aInfraIfLinkLocalAddress));
+
+exit:
+    if (error != kErrorNone)
+    {
+        mInfraIfIndex = 0;
+    }
+    return error;
+}
+
+Error RoutingManager::SetEnabled(bool aEnabled)
+{
+    Error error = kErrorNone;
+
+    VerifyOrExit(IsInitialized(), error = kErrorInvalidState);
+
+    VerifyOrExit(aEnabled != mIsEnabled);
+
+    mIsEnabled = aEnabled;
+    EvaluateState();
+
+exit:
+    return error;
+}
+
+Error RoutingManager::LoadOrGenerateRandomOmrPrefix(void)
+{
+    Error error = kErrorNone;
+
+    if (Get<Settings>().ReadOmrPrefix(mLocalOmrPrefix) != kErrorNone || !IsValidOmrPrefix(mLocalOmrPrefix))
+    {
+        Ip6::NetworkPrefix randomOmrPrefix;
+
+        otLogNoteBr("no valid OMR prefix found in settings, generating new one");
+
+        error = randomOmrPrefix.GenerateRandomUla();
+        if (error != kErrorNone)
+        {
+            otLogCritBr("failed to generate random OMR prefix");
+            ExitNow();
+        }
+
+        mLocalOmrPrefix.Set(randomOmrPrefix);
+        IgnoreError(Get<Settings>().SaveOmrPrefix(mLocalOmrPrefix));
+    }
+
+exit:
+    return error;
+}
+
+Error RoutingManager::LoadOrGenerateRandomOnLinkPrefix(void)
+{
+    Error error = kErrorNone;
+
+    if (Get<Settings>().ReadOnLinkPrefix(mLocalOnLinkPrefix) != kErrorNone || !IsValidOnLinkPrefix(mLocalOnLinkPrefix))
+    {
+        Ip6::NetworkPrefix randomOnLinkPrefix;
+
+        otLogNoteBr("no valid on-link prefix found in settings, generating new one");
+
+        error = randomOnLinkPrefix.GenerateRandomUla();
+        if (error != kErrorNone)
+        {
+            otLogCritBr("failed to generate random on-link prefix");
+            ExitNow();
+        }
+
+        randomOnLinkPrefix.m8[6] = 0;
+        randomOnLinkPrefix.m8[7] = 0;
+        mLocalOnLinkPrefix.Set(randomOnLinkPrefix);
+
+        IgnoreError(Get<Settings>().SaveOnLinkPrefix(mLocalOnLinkPrefix));
+    }
+
+exit:
+    return error;
+}
+
+void RoutingManager::EvaluateState(void)
+{
+    if (mIsEnabled && Get<Mle::MleRouter>().IsAttached() && mInfraIfIsRunning && mInfraIfLinkLocalAddress.IsLinkLocal())
+    {
+        Start();
+    }
+    else
+    {
+        Stop();
+    }
+}
+
+void RoutingManager::Start(void)
+{
+    if (!mIsRunning)
+    {
+        otLogInfoBr("Border Routing manager started");
+
+        mIsRunning = true;
+        StartRouterSolicitationDelay();
+    }
+}
+
+void RoutingManager::Stop(void)
+{
+    VerifyOrExit(mIsRunning);
+
+    UnpublishLocalOmrPrefix();
+    if (mAdvertisedOnLinkPrefix != nullptr)
+    {
+        RemoveExternalRoute(*mAdvertisedOnLinkPrefix);
+    }
+    InvalidateAllDiscoveredPrefixes();
+
+    // Use empty OMR & on-link prefixes to invalidate possible advertised prefixes.
+    SendRouterAdvertisement(nullptr, 0, nullptr);
+
+    memset(mAdvertisedOmrPrefixes, 0, sizeof(mAdvertisedOmrPrefixes));
+    mAdvertisedOmrPrefixNum = 0;
+
+    mAdvertisedOnLinkPrefix = nullptr;
+
+    memset(mDiscoveredPrefixes, 0, sizeof(mDiscoveredPrefixes));
+    mDiscoveredPrefixNum = 0;
+    mDiscoveredPrefixInvalidTimer.Stop();
+
+    mRouterAdvertisementTimer.Stop();
+    mRouterAdvertisementCount = 0;
+
+    mRouterSolicitTimer.Stop();
+    mRouterSolicitCount = 0;
+
+    mRoutingPolicyTimer.Stop();
+
+    otLogInfoBr("Border Routing manager stopped");
+
+    mIsRunning = false;
+
+exit:
+    return;
+}
+
+void RoutingManager::RecvIcmp6Message(uint32_t            aInfraIfIndex,
+                                      const Ip6::Address &aSrcAddress,
+                                      const uint8_t *     aBuffer,
+                                      uint16_t            aBufferLength)
+{
+    Error                    error = kErrorNone;
+    const Ip6::Icmp::Header *icmp6Header;
+    const Ip6::Address *     infraLinkLocalAddr;
+
+    VerifyOrExit(IsInitialized() && mIsRunning, error = kErrorDrop);
+
+    VerifyOrExit(aInfraIfIndex == mInfraIfIndex, error = kErrorDrop);
+    infraLinkLocalAddr = static_cast<const Ip6::Address *>(&mInfraIfLinkLocalAddress);
+
+    // Drop any ICMPv6 messages sent from myself.
+    VerifyOrExit(infraLinkLocalAddr != nullptr && aSrcAddress != *infraLinkLocalAddr, error = kErrorDrop);
+
+    VerifyOrExit(aBuffer != nullptr && aBufferLength >= sizeof(*icmp6Header), error = kErrorParse);
+
+    icmp6Header = reinterpret_cast<const Ip6::Icmp::Header *>(aBuffer);
+
+    switch (icmp6Header->GetType())
+    {
+    case Ip6::Icmp::Header::kTypeRouterAdvert:
+        HandleRouterAdvertisement(aSrcAddress, aBuffer, aBufferLength);
+        break;
+    case Ip6::Icmp::Header::kTypeRouterSolicit:
+        HandleRouterSolicit(aSrcAddress, aBuffer, aBufferLength);
+        break;
+    default:
+        break;
+    }
+
+exit:
+    if (error != kErrorNone)
+    {
+        otLogDebgBr("drop ICMPv6 message: %s", ErrorToString(error));
+    }
+}
+
+Error RoutingManager::HandleInfraIfStateChanged(uint32_t            aInfraIfIndex,
+                                                bool                aIsRunning,
+                                                const Ip6::Address *aLinkLocalAddress)
+{
+    Error error = kErrorNone;
+
+    VerifyOrExit(IsInitialized(), error = kErrorInvalidState);
+    VerifyOrExit(aInfraIfIndex == mInfraIfIndex, error = kErrorInvalidArgs);
+    VerifyOrExit(aLinkLocalAddress == nullptr || aLinkLocalAddress->IsLinkLocal(), error = kErrorInvalidArgs);
+
+    otLogInfoBr("infra interface state changed: %s, link-local-addr=%s", aIsRunning ? "RUNNING" : "NOT RUNNING",
+                (aLinkLocalAddress != nullptr) ? aLinkLocalAddress->ToString().AsCString() : "(null)");
+
+    mInfraIfIsRunning = aIsRunning;
+    if (aLinkLocalAddress == nullptr)
+    {
+        mInfraIfLinkLocalAddress.Clear();
+    }
+    else
+    {
+        mInfraIfLinkLocalAddress = *aLinkLocalAddress;
+    }
+
+    EvaluateState();
+
+exit:
+    return error;
+}
+
+void RoutingManager::HandleNotifierEvents(Events aEvents)
+{
+    VerifyOrExit(IsInitialized() && IsEnabled());
+
+    if (aEvents.Contains(kEventThreadRoleChanged))
+    {
+        EvaluateState();
+    }
+
+    if (mIsRunning && aEvents.Contains(kEventThreadNetdataChanged))
+    {
+        StartRoutingPolicyEvaluationDelay();
+    }
+
+exit:
+    return;
+}
+
+uint8_t RoutingManager::EvaluateOmrPrefix(Ip6::Prefix *aNewOmrPrefixes, uint8_t aMaxOmrPrefixNum)
+{
+    uint8_t                         newOmrPrefixNum = 0;
+    NetworkData::Iterator           iterator        = NetworkData::kIteratorInit;
+    NetworkData::OnMeshPrefixConfig onMeshPrefixConfig;
+    Ip6::Prefix *                   smallestOmrPrefix       = nullptr;
+    Ip6::Prefix *                   publishedLocalOmrPrefix = nullptr;
+
+    OT_ASSERT(mIsRunning);
+
+    while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, onMeshPrefixConfig) == kErrorNone)
+    {
+        uint8_t newPrefixIndex;
+
+        if (!IsValidOmrPrefix(onMeshPrefixConfig))
+        {
+            continue;
+        }
+
+        newPrefixIndex = 0;
+        while (newPrefixIndex < newOmrPrefixNum && onMeshPrefixConfig.GetPrefix() != aNewOmrPrefixes[newPrefixIndex])
+        {
+            ++newPrefixIndex;
+        }
+
+        if (newPrefixIndex != newOmrPrefixNum)
+        {
+            // Ignore duplicate prefixes.
+            continue;
+        }
+
+        if (newOmrPrefixNum >= aMaxOmrPrefixNum)
+        {
+            otLogWarnBr("EvaluateOmrPrefix: too many OMR prefixes, ignoring prefix %s",
+                        onMeshPrefixConfig.GetPrefix().ToString().AsCString());
+            continue;
+        }
+
+        aNewOmrPrefixes[newOmrPrefixNum] = onMeshPrefixConfig.GetPrefix();
+        if (smallestOmrPrefix == nullptr || (onMeshPrefixConfig.GetPrefix() < *smallestOmrPrefix))
+        {
+            smallestOmrPrefix = &aNewOmrPrefixes[newOmrPrefixNum];
+        }
+        if (aNewOmrPrefixes[newOmrPrefixNum] == mLocalOmrPrefix)
+        {
+            publishedLocalOmrPrefix = &aNewOmrPrefixes[newOmrPrefixNum];
+        }
+        ++newOmrPrefixNum;
+    }
+
+    // Decide if we need to add or remove my local OMR prefix.
+    if (newOmrPrefixNum == 0)
+    {
+        otLogInfoBr("EvaluateOmrPrefix: no valid OMR prefixes found in Thread network");
+        if (PublishLocalOmrPrefix() == kErrorNone)
+        {
+            aNewOmrPrefixes[newOmrPrefixNum++] = mLocalOmrPrefix;
+        }
+
+        // The `newOmrPrefixNum` is zero when we failed to publish the local OMR prefix.
+    }
+    else if (publishedLocalOmrPrefix != nullptr && smallestOmrPrefix != publishedLocalOmrPrefix)
+    {
+        otLogInfoBr("EvaluateOmrPrefix: there is already a smaller OMR prefix %s in the Thread network",
+                    smallestOmrPrefix->ToString().AsCString());
+        UnpublishLocalOmrPrefix();
+
+        // Remove the local OMR prefix from the list by overwriting it with the last one.
+        *publishedLocalOmrPrefix = aNewOmrPrefixes[--newOmrPrefixNum];
+    }
+
+    return newOmrPrefixNum;
+}
+
+Error RoutingManager::PublishLocalOmrPrefix(void)
+{
+    Error                           error = kErrorNone;
+    NetworkData::OnMeshPrefixConfig omrPrefixConfig;
+
+    OT_ASSERT(mIsRunning);
+
+    omrPrefixConfig.Clear();
+    omrPrefixConfig.mPrefix       = mLocalOmrPrefix;
+    omrPrefixConfig.mStable       = true;
+    omrPrefixConfig.mSlaac        = true;
+    omrPrefixConfig.mPreferred    = true;
+    omrPrefixConfig.mOnMesh       = true;
+    omrPrefixConfig.mDefaultRoute = false;
+    omrPrefixConfig.mPreference   = OT_ROUTE_PREFERENCE_MED;
+
+    error = Get<NetworkData::Local>().AddOnMeshPrefix(omrPrefixConfig);
+    if (error != kErrorNone)
+    {
+        otLogWarnBr("failed to publish local OMR prefix %s in Thread network: %s",
+                    mLocalOmrPrefix.ToString().AsCString(), ErrorToString(error));
+    }
+    else
+    {
+        Get<NetworkData::Notifier>().HandleServerDataUpdated();
+        otLogInfoBr("published local OMR prefix %s in Thread network", mLocalOmrPrefix.ToString().AsCString());
+    }
+
+    return error;
+}
+
+void RoutingManager::UnpublishLocalOmrPrefix(void)
+{
+    Error error = kErrorNone;
+
+    VerifyOrExit(mIsRunning);
+
+    SuccessOrExit(error = Get<NetworkData::Local>().RemoveOnMeshPrefix(mLocalOmrPrefix));
+
+    Get<NetworkData::Notifier>().HandleServerDataUpdated();
+    otLogInfoBr("unpublished local OMR prefix %s from Thread network", mLocalOmrPrefix.ToString().AsCString());
+
+exit:
+    if (error != kErrorNone)
+    {
+        otLogWarnBr("failed to unpublish local OMR prefix %s from Thread network: %s",
+                    mLocalOmrPrefix.ToString().AsCString(), ErrorToString(error));
+    }
+}
+
+Error RoutingManager::AddExternalRoute(const Ip6::Prefix &aPrefix, otRoutePreference aRoutePreference)
+{
+    Error                            error;
+    NetworkData::ExternalRouteConfig routeConfig;
+
+    OT_ASSERT(mIsRunning);
+
+    routeConfig.Clear();
+    routeConfig.SetPrefix(aPrefix);
+    routeConfig.mStable     = true;
+    routeConfig.mPreference = aRoutePreference;
+
+    error = Get<NetworkData::Local>().AddHasRoutePrefix(routeConfig);
+    if (error != kErrorNone)
+    {
+        otLogWarnBr("failed to add external route %s: %s", aPrefix.ToString().AsCString(), ErrorToString(error));
+    }
+    else
+    {
+        Get<NetworkData::Notifier>().HandleServerDataUpdated();
+        otLogInfoBr("added external route %s", aPrefix.ToString().AsCString());
+    }
+
+    return error;
+}
+
+void RoutingManager::RemoveExternalRoute(const Ip6::Prefix &aPrefix)
+{
+    Error error = kErrorNone;
+
+    VerifyOrExit(mIsRunning);
+
+    SuccessOrExit(error = Get<NetworkData::Local>().RemoveHasRoutePrefix(aPrefix));
+
+    Get<NetworkData::Notifier>().HandleServerDataUpdated();
+    otLogInfoBr("removed external route %s", aPrefix.ToString().AsCString());
+
+exit:
+    if (error != kErrorNone)
+    {
+        otLogWarnBr("failed to remove external route %s: %s", aPrefix.ToString().AsCString(), ErrorToString(error));
+    }
+}
+
+bool RoutingManager::ContainsPrefix(const Ip6::Prefix &aPrefix, const Ip6::Prefix *aPrefixList, uint8_t aPrefixNum)
+{
+    bool ret = false;
+
+    for (uint8_t i = 0; i < aPrefixNum; ++i)
+    {
+        if (aPrefixList[i] == aPrefix)
+        {
+            ret = true;
+            break;
+        }
+    }
+
+    return ret;
+}
+
+const Ip6::Prefix *RoutingManager::EvaluateOnLinkPrefix(void)
+{
+    const Ip6::Prefix *newOnLinkPrefix      = nullptr;
+    const Ip6::Prefix *smallestOnLinkPrefix = nullptr;
+
+    // We don't evaluate on-link prefix if we are doing Router Solicitation.
+    VerifyOrExit(!mRouterSolicitTimer.IsRunning());
+
+    for (uint8_t i = 0; i < mDiscoveredPrefixNum; ++i)
+    {
+        ExternalPrefix &prefix = mDiscoveredPrefixes[i];
+
+        if (!prefix.mIsOnLinkPrefix)
+        {
+            continue;
+        }
+
+        if (smallestOnLinkPrefix == nullptr || (prefix.mPrefix < *smallestOnLinkPrefix))
+        {
+            smallestOnLinkPrefix = &prefix.mPrefix;
+        }
+    }
+
+    // We start advertising our local on-link prefix if there is no existing one.
+    if (smallestOnLinkPrefix == nullptr)
+    {
+        if (mAdvertisedOnLinkPrefix != nullptr)
+        {
+            newOnLinkPrefix = mAdvertisedOnLinkPrefix;
+        }
+        else if (AddExternalRoute(mLocalOnLinkPrefix, OT_ROUTE_PREFERENCE_MED) == kErrorNone)
+        {
+            newOnLinkPrefix = &mLocalOnLinkPrefix;
+        }
+    }
+    // When an application-specific on-link prefix is received and it is bigger than the
+    // advertised prefix, we will not remove the advertised prefix. In this case, there
+    // will be two on-link prefixes on the infra link. But all BRs will still converge to
+    // the same smallest on-link prefix and the application-specific prefix is not used.
+    else if (mAdvertisedOnLinkPrefix != nullptr)
+    {
+        if (*mAdvertisedOnLinkPrefix < *smallestOnLinkPrefix)
+        {
+            newOnLinkPrefix = mAdvertisedOnLinkPrefix;
+        }
+        else
+        {
+            otLogInfoBr("EvaluateOnLinkPrefix: there is already smaller on-link prefix %s on interface %u",
+                        smallestOnLinkPrefix->ToString().AsCString(), mInfraIfIndex);
+
+            // TODO: we removes the advertised on-link prefix by setting valid lifetime for PIO.
+            // But SLAAC addresses configured by PIO prefix will not be removed immediately (
+            // https://tools.ietf.org/html/rfc4862#section-5.5.3). This leads to a situation that
+            // a WiFi device keeps using the old SLAAC address but a Thread device cannot reach to
+            // it. One solution is to delay removing external route until the SLAAC addresses are
+            // actually expired/deprecated.
+            RemoveExternalRoute(*mAdvertisedOnLinkPrefix);
+        }
+    }
+
+exit:
+    return newOnLinkPrefix;
+}
+
+// This method evaluate the routing policy depends on prefix and route
+// information on Thread Network and infra link. As a result, this
+// method May send RA messages on infra link and publish/unpublish
+// OMR prefix in the Thread network.
+void RoutingManager::EvaluateRoutingPolicy(void)
+{
+    OT_ASSERT(mIsRunning);
+
+    const Ip6::Prefix *newOnLinkPrefix = nullptr;
+    Ip6::Prefix        newOmrPrefixes[kMaxOmrPrefixNum];
+    uint8_t            newOmrPrefixNum = 0;
+
+    otLogInfoBr("evaluating routing policy");
+
+    // 0. Evaluate on-link & OMR prefixes.
+    newOnLinkPrefix = EvaluateOnLinkPrefix();
+    newOmrPrefixNum = EvaluateOmrPrefix(newOmrPrefixes, kMaxOmrPrefixNum);
+
+    // 1. Send Router Advertisement message if necessary.
+    SendRouterAdvertisement(newOmrPrefixes, newOmrPrefixNum, newOnLinkPrefix);
+    if (newOmrPrefixNum == 0)
+    {
+        // This is the very exceptional case and happens only when we failed to publish
+        // our local OMR prefix to the Thread network. We schedule the Router Advertisement
+        // timer to re-evaluate our routing policy in the future.
+        otLogWarnBr("no OMR prefix advertised! Start Router Advertisement timer for future evaluation");
+    }
+
+    // 2. Schedule Router Advertisement timer with random interval.
+    {
+        uint32_t nextSendTime;
+
+        nextSendTime = Random::NonCrypto::GetUint32InRange(kMinRtrAdvInterval, kMaxRtrAdvInterval);
+
+        if (mRouterAdvertisementCount <= kMaxInitRtrAdvertisements && nextSendTime > kMaxInitRtrAdvInterval)
+        {
+            nextSendTime = kMaxInitRtrAdvInterval;
+        }
+
+        otLogInfoBr("router advertisement scheduled in %u seconds", nextSendTime);
+        mRouterAdvertisementTimer.Start(Time::SecToMsec(nextSendTime));
+    }
+
+    // 3. Update advertised on-link & OMR prefixes information.
+    mAdvertisedOnLinkPrefix = newOnLinkPrefix;
+    static_assert(sizeof(mAdvertisedOmrPrefixes) == sizeof(newOmrPrefixes), "invalid new OMR prefix array size");
+    memcpy(mAdvertisedOmrPrefixes, newOmrPrefixes, sizeof(newOmrPrefixes[0]) * newOmrPrefixNum);
+    mAdvertisedOmrPrefixNum = newOmrPrefixNum;
+}
+
+void RoutingManager::StartRoutingPolicyEvaluationDelay(void)
+{
+    OT_ASSERT(mIsRunning);
+
+    uint32_t randomDelay;
+
+    static_assert(kMaxRoutingPolicyDelay > 0, "invalid maximum routing policy evaluation delay");
+    randomDelay = Random::NonCrypto::GetUint32InRange(0, Time::SecToMsec(kMaxRoutingPolicyDelay));
+
+    otLogInfoBr("start evaluating routing policy, scheduled in %u milliseconds", randomDelay);
+    mRoutingPolicyTimer.Start(randomDelay);
+}
+
+// starts sending Router Solicitations in random delay
+// between 0 and kMaxRtrSolicitationDelay.
+void RoutingManager::StartRouterSolicitationDelay(void)
+{
+    uint32_t randomDelay;
+
+    mRouterSolicitCount = 0;
+
+    static_assert(kMaxRtrSolicitationDelay > 0, "invalid maximum Router Solicitation delay");
+    randomDelay = Random::NonCrypto::GetUint32InRange(0, Time::SecToMsec(kMaxRtrSolicitationDelay));
+
+    otLogInfoBr("start Router Solicitation, scheduled in %u milliseconds", randomDelay);
+    mRouterSolicitTimer.Start(randomDelay);
+}
+
+Error RoutingManager::SendRouterSolicitation(void)
+{
+    Ip6::Address                    destAddress;
+    RouterAdv::RouterSolicitMessage routerSolicit;
+
+    OT_ASSERT(IsInitialized());
+
+    destAddress.SetToLinkLocalAllRoutersMulticast();
+    return otPlatInfraIfSendIcmp6Nd(mInfraIfIndex, &destAddress, reinterpret_cast<const uint8_t *>(&routerSolicit),
+                                    sizeof(routerSolicit));
+}
+
+// This method sends Router Advertisement messages to advertise on-link prefix and route for OMR prefix.
+// @param[in]  aNewOmrPrefixes   A pointer to an array of the new OMR prefixes to be advertised.
+//                               @p aNewOmrPrefixNum must be zero if this argument is nullptr.
+// @param[in]  aNewOmrPrefixNum  The number of the new OMR prefixes to be advertised.
+//                               Zero means we should stop advertising OMR prefixes.
+// @param[in]  aOnLinkPrefix     A pointer to the new on-link prefix to be advertised.
+//                               nullptr means we should stop advertising on-link prefix.
+void RoutingManager::SendRouterAdvertisement(const Ip6::Prefix *aNewOmrPrefixes,
+                                             uint8_t            aNewOmrPrefixNum,
+                                             const Ip6::Prefix *aNewOnLinkPrefix)
+{
+    uint8_t                     buffer[kMaxRouterAdvMessageLength];
+    uint16_t                    bufferLength = 0;
+    RouterAdv::RouterAdvMessage routerAdv;
+
+    // Set zero Router Lifetime to indicate that the Border Router is not the default
+    // router for infra link so that hosts on infra link will not create default route
+    // to the Border Router when received RA.
+    routerAdv.SetRouterLifetime(0);
+
+    OT_ASSERT(bufferLength + sizeof(routerAdv) <= sizeof(buffer));
+    memcpy(buffer, &routerAdv, sizeof(routerAdv));
+    bufferLength += sizeof(routerAdv);
+
+    if (aNewOnLinkPrefix != nullptr)
+    {
+        RouterAdv::PrefixInfoOption pio;
+
+        pio.SetOnLink(true);
+        pio.SetAutoAddrConfig(true);
+        pio.SetValidLifetime(kDefaultOnLinkPrefixLifetime);
+        pio.SetPreferredLifetime(kDefaultOnLinkPrefixLifetime);
+        pio.SetPrefix(*aNewOnLinkPrefix);
+
+        OT_ASSERT(bufferLength + pio.GetSize() <= sizeof(buffer));
+        memcpy(buffer + bufferLength, &pio, pio.GetSize());
+        bufferLength += pio.GetSize();
+
+        if (mAdvertisedOnLinkPrefix == nullptr)
+        {
+            otLogInfoBr("start advertising new on-link prefix %s on interface %u",
+                        aNewOnLinkPrefix->ToString().AsCString(), mInfraIfIndex);
+        }
+
+        otLogInfoBr("send on-link prefix %s in PIO (valid lifetime = %u seconds)",
+                    aNewOnLinkPrefix->ToString().AsCString(), kDefaultOnLinkPrefixLifetime);
+    }
+    else if (mAdvertisedOnLinkPrefix != nullptr)
+    {
+        RouterAdv::PrefixInfoOption pio;
+
+        pio.SetOnLink(true);
+        pio.SetAutoAddrConfig(true);
+
+        // Set zero valid lifetime to immediately invalidate the advertised on-link prefix.
+        pio.SetValidLifetime(0);
+        pio.SetPreferredLifetime(0);
+        pio.SetPrefix(*mAdvertisedOnLinkPrefix);
+
+        OT_ASSERT(bufferLength + pio.GetSize() <= sizeof(buffer));
+        memcpy(buffer + bufferLength, &pio, pio.GetSize());
+        bufferLength += pio.GetSize();
+
+        otLogInfoBr("stop advertising on-link prefix %s on interface %u",
+                    mAdvertisedOnLinkPrefix->ToString().AsCString(), mInfraIfIndex);
+    }
+
+    // Invalidate the advertised OMR prefixes if they are no longer in the new OMR prefix array.
+    for (uint8_t i = 0; i < mAdvertisedOmrPrefixNum; ++i)
+    {
+        const Ip6::Prefix &advertisedOmrPrefix = mAdvertisedOmrPrefixes[i];
+
+        if (!ContainsPrefix(advertisedOmrPrefix, aNewOmrPrefixes, aNewOmrPrefixNum))
+        {
+            RouterAdv::RouteInfoOption rio;
+
+            // Set zero route lifetime to immediately invalidate the advertised OMR prefix.
+            rio.SetRouteLifetime(0);
+            rio.SetPrefix(advertisedOmrPrefix);
+
+            OT_ASSERT(bufferLength + rio.GetSize() <= sizeof(buffer));
+            memcpy(buffer + bufferLength, &rio, rio.GetSize());
+            bufferLength += rio.GetSize();
+
+            otLogInfoBr("stop advertising OMR prefix %s on interface %u", advertisedOmrPrefix.ToString().AsCString(),
+                        mInfraIfIndex);
+        }
+    }
+
+    for (uint8_t i = 0; i < aNewOmrPrefixNum; ++i)
+    {
+        const Ip6::Prefix &        newOmrPrefix = aNewOmrPrefixes[i];
+        RouterAdv::RouteInfoOption rio;
+
+        rio.SetRouteLifetime(kDefaultOmrPrefixLifetime);
+        rio.SetPrefix(newOmrPrefix);
+
+        OT_ASSERT(bufferLength + rio.GetSize() <= sizeof(buffer));
+        memcpy(buffer + bufferLength, &rio, rio.GetSize());
+        bufferLength += rio.GetSize();
+
+        otLogInfoBr("send OMR prefix %s in RIO (valid lifetime = %u seconds)", newOmrPrefix.ToString().AsCString(),
+                    kDefaultOmrPrefixLifetime);
+    }
+
+    // Send the message only when there are options.
+    if (bufferLength > sizeof(routerAdv))
+    {
+        Error        error;
+        Ip6::Address destAddress;
+
+        ++mRouterAdvertisementCount;
+
+        destAddress.SetToLinkLocalAllNodesMulticast();
+        error = otPlatInfraIfSendIcmp6Nd(mInfraIfIndex, &destAddress, buffer, bufferLength);
+
+        if (error == kErrorNone)
+        {
+            otLogInfoBr("sent Router Advertisement on interface %u", mInfraIfIndex);
+        }
+        else
+        {
+            otLogWarnBr("failed to send Router Advertisement on interface %u: %s", mInfraIfIndex, ErrorToString(error));
+        }
+    }
+}
+
+bool RoutingManager::IsValidOmrPrefix(const NetworkData::OnMeshPrefixConfig &aOnMeshPrefixConfig)
+{
+    return IsValidOmrPrefix(aOnMeshPrefixConfig.GetPrefix()) && aOnMeshPrefixConfig.mSlaac && !aOnMeshPrefixConfig.mDp;
+}
+
+bool RoutingManager::IsValidOmrPrefix(const Ip6::Prefix &aOmrPrefix)
+{
+    // Accept ULA prefix with length of 64 bits and GUA prefix.
+    return (aOmrPrefix.mLength == kOmrPrefixLength && aOmrPrefix.mPrefix.mFields.m8[0] == 0xfd) ||
+           (aOmrPrefix.mLength >= 3 && (aOmrPrefix.GetBytes()[0] & 0xE0) == 0x20);
+}
+
+bool RoutingManager::IsValidOnLinkPrefix(const Ip6::Prefix &aOnLinkPrefix)
+{
+    // Accept ULA prefix with length of 64 bits and GUA prefix.
+    return (aOnLinkPrefix.mLength == kOnLinkPrefixLength && aOnLinkPrefix.mPrefix.mFields.m8[0] == 0xfd) ||
+           (aOnLinkPrefix.mLength >= 3 && (aOnLinkPrefix.GetBytes()[0] & 0xE0) == 0x20);
+}
+
+void RoutingManager::HandleRouterAdvertisementTimer(Timer &aTimer)
+{
+    aTimer.Get<RoutingManager>().HandleRouterAdvertisementTimer();
+}
+
+void RoutingManager::HandleRouterAdvertisementTimer(void)
+{
+    otLogInfoBr("router advertisement timer triggered");
+
+    EvaluateRoutingPolicy();
+}
+
+void RoutingManager::HandleRouterSolicitTimer(Timer &aTimer)
+{
+    aTimer.Get<RoutingManager>().HandleRouterSolicitTimer();
+}
+
+void RoutingManager::HandleRouterSolicitTimer(void)
+{
+    otLogInfoBr("router solicitation times out");
+
+    if (mRouterSolicitCount < kMaxRtrSolicitations)
+    {
+        uint32_t nextSolicitationDelay;
+        Error    error;
+
+        error = SendRouterSolicitation();
+        ++mRouterSolicitCount;
+
+        if (error == kErrorNone)
+        {
+            otLogDebgBr("successfully sent %uth Router Solicitation", mRouterSolicitCount);
+        }
+        else
+        {
+            otLogCritBr("failed to send %uth Router Solicitation: %s", mRouterSolicitCount, ErrorToString(error));
+        }
+
+        nextSolicitationDelay =
+            (mRouterSolicitCount == kMaxRtrSolicitations) ? kMaxRtrSolicitationDelay : kRtrSolicitationInterval;
+
+        otLogDebgBr("router solicitation timer scheduled in %u seconds", nextSolicitationDelay);
+        mRouterSolicitTimer.Start(Time::SecToMsec(nextSolicitationDelay));
+    }
+    else
+    {
+        // Re-evaluate our routing policy and send Router Advertisement if necessary.
+        EvaluateRoutingPolicy();
+    }
+}
+
+void RoutingManager::HandleDiscoveredPrefixInvalidTimer(Timer &aTimer)
+{
+    aTimer.Get<RoutingManager>().HandleDiscoveredPrefixInvalidTimer();
+}
+
+void RoutingManager::HandleDiscoveredPrefixInvalidTimer(void)
+{
+    InvalidateDiscoveredPrefixes();
+}
+
+void RoutingManager::HandleRoutingPolicyTimer(Timer &aTimer)
+{
+    aTimer.Get<RoutingManager>().EvaluateRoutingPolicy();
+}
+
+void RoutingManager::HandleRouterSolicit(const Ip6::Address &aSrcAddress,
+                                         const uint8_t *     aBuffer,
+                                         uint16_t            aBufferLength)
+{
+    uint32_t randomDelay;
+
+    OT_UNUSED_VARIABLE(aSrcAddress);
+    OT_UNUSED_VARIABLE(aBuffer);
+    OT_UNUSED_VARIABLE(aBufferLength);
+
+    VerifyOrExit(!mRouterSolicitTimer.IsRunning());
+
+    otLogInfoBr("received Router Solicitation from %s on interface %u", aSrcAddress.ToString().AsCString(),
+                mInfraIfIndex);
+
+    randomDelay = Random::NonCrypto::GetUint32InRange(0, kMaxRaDelayTime);
+
+    otLogInfoBr("Router Advertisement scheduled in %u milliseconds", randomDelay);
+    mRouterAdvertisementTimer.Start(randomDelay);
+
+exit:
+    return;
+}
+
+uint32_t RoutingManager::GetPrefixExpireDelay(uint32_t aValidLifetime)
+{
+    uint32_t delay;
+
+    if (aValidLifetime * static_cast<uint64_t>(1000) > Timer::kMaxDelay)
+    {
+        delay = Timer::kMaxDelay;
+    }
+    else
+    {
+        delay = aValidLifetime * 1000;
+    }
+
+    return delay;
+}
+
+void RoutingManager::HandleRouterAdvertisement(const Ip6::Address &aSrcAddress,
+                                               const uint8_t *     aBuffer,
+                                               uint16_t            aBufferLength)
+{
+    OT_ASSERT(mIsRunning);
+    OT_UNUSED_VARIABLE(aSrcAddress);
+
+    using RouterAdv::Option;
+    using RouterAdv::PrefixInfoOption;
+    using RouterAdv::RouteInfoOption;
+    using RouterAdv::RouterAdvMessage;
+
+    bool           needReevaluate = false;
+    const uint8_t *optionsBegin;
+    uint16_t       optionsLength;
+    const Option * option;
+
+    VerifyOrExit(aBufferLength >= sizeof(RouterAdvMessage));
+
+    otLogInfoBr("received Router Advertisement from %s on interface %u", aSrcAddress.ToString().AsCString(),
+                mInfraIfIndex);
+
+    optionsBegin  = aBuffer + sizeof(RouterAdvMessage);
+    optionsLength = aBufferLength - sizeof(RouterAdvMessage);
+
+    option = nullptr;
+    while ((option = Option::GetNextOption(option, optionsBegin, optionsLength)) != nullptr)
+    {
+        switch (option->GetType())
+        {
+        case Option::Type::kPrefixInfo:
+        {
+            const PrefixInfoOption *pio = static_cast<const PrefixInfoOption *>(option);
+
+            if (pio->IsValid())
+            {
+                needReevaluate |= UpdateDiscoveredPrefixes(*pio);
+            }
+        }
+        break;
+
+        case Option::Type::kRouteInfo:
+        {
+            const RouteInfoOption *rio = static_cast<const RouteInfoOption *>(option);
+
+            if (rio->IsValid())
+            {
+                needReevaluate |= UpdateDiscoveredPrefixes(*rio);
+            }
+        }
+        break;
+
+        default:
+            break;
+        }
+    }
+
+    if (needReevaluate)
+    {
+        StartRoutingPolicyEvaluationDelay();
+    }
+
+exit:
+    return;
+}
+
+bool RoutingManager::UpdateDiscoveredPrefixes(const RouterAdv::PrefixInfoOption &aPio)
+{
+    Ip6::Prefix prefix         = aPio.GetPrefix();
+    bool        needReevaluate = false;
+
+    if (!IsValidOnLinkPrefix(prefix))
+    {
+        otLogInfoBr("ignore invalid prefix in PIO: %s", prefix.ToString().AsCString());
+        ExitNow();
+    }
+
+    otLogInfoBr("discovered on-link prefix (%s, %u seconds) from interface %u", prefix.ToString().AsCString(),
+                aPio.GetValidLifetime(), mInfraIfIndex);
+
+    if (aPio.GetValidLifetime() == 0)
+    {
+        needReevaluate = InvalidateDiscoveredPrefixes(&prefix, /* aIsOnLinkPrefix */ true) > 0;
+    }
+    else
+    {
+        needReevaluate = AddDiscoveredPrefix(prefix, /* aIsOnLinkPrefix */ true, aPio.GetValidLifetime());
+    }
+
+exit:
+    return needReevaluate;
+}
+
+bool RoutingManager::UpdateDiscoveredPrefixes(const RouterAdv::RouteInfoOption &aRio)
+{
+    Ip6::Prefix prefix         = aRio.GetPrefix();
+    bool        needReevaluate = false;
+
+    if (!IsValidOmrPrefix(prefix))
+    {
+        otLogInfoBr("ignore invalid prefix in RIO: %s", prefix.ToString().AsCString());
+        ExitNow();
+    }
+
+    // Ignore the OMR prefix in current Thread Network.
+    VerifyOrExit(!NetworkDataContainsOmrPrefix(prefix));
+
+    otLogInfoBr("discovered OMR prefix (%s, %u seconds) from interface %u", prefix.ToString().AsCString(),
+                aRio.GetRouteLifetime(), mInfraIfIndex);
+
+    if (aRio.GetRouteLifetime() == 0)
+    {
+        needReevaluate = (InvalidateDiscoveredPrefixes(&prefix, /* aIsOnLinkPrefix */ false) > 0);
+    }
+    else
+    {
+        needReevaluate =
+            AddDiscoveredPrefix(prefix, /* aIsOnLinkPrefix */ false, aRio.GetRouteLifetime(), aRio.GetPreference());
+    }
+
+exit:
+    return needReevaluate;
+}
+
+bool RoutingManager::InvalidateDiscoveredPrefixes(const Ip6::Prefix *aPrefix, bool aIsOnLinkPrefix)
+{
+    uint8_t         removedNum          = 0;
+    ExternalPrefix *keptPrefix          = mDiscoveredPrefixes;
+    TimeMilli       now                 = TimerMilli::GetNow();
+    TimeMilli       earliestExpireTime  = now.GetDistantFuture();
+    uint8_t         keptOnLinkPrefixNum = 0;
+
+    for (uint8_t i = 0; i < mDiscoveredPrefixNum; ++i)
+    {
+        ExternalPrefix &prefix = mDiscoveredPrefixes[i];
+
+        if ((aPrefix != nullptr && prefix.mPrefix == *aPrefix && prefix.mIsOnLinkPrefix == aIsOnLinkPrefix) ||
+            (prefix.mExpireTime <= now))
+        {
+            RemoveExternalRoute(prefix.mPrefix);
+            ++removedNum;
+        }
+        else
+        {
+            earliestExpireTime = OT_MIN(earliestExpireTime, prefix.mExpireTime);
+            *keptPrefix        = prefix;
+            ++keptPrefix;
+            if (prefix.mIsOnLinkPrefix)
+            {
+                ++keptOnLinkPrefixNum;
+            }
+        }
+    }
+
+    mDiscoveredPrefixNum -= removedNum;
+
+    if (keptOnLinkPrefixNum == 0)
+    {
+        mDiscoveredPrefixInvalidTimer.Stop();
+
+        // There are no valid on-link prefixes on infra link now, start Router Solicitation
+        // To find out more on-link prefixes or timeout to advertise my local on-link prefix.
+        StartRouterSolicitationDelay();
+    }
+    else
+    {
+        mDiscoveredPrefixInvalidTimer.FireAt(earliestExpireTime);
+    }
+
+    return (removedNum != 0); // If anything was removed we need to reevaluate.
+}
+
+void RoutingManager::InvalidateAllDiscoveredPrefixes(void)
+{
+    TimeMilli past = TimerMilli::GetNow();
+
+    for (uint8_t i = 0; i < mDiscoveredPrefixNum; ++i)
+    {
+        mDiscoveredPrefixes[i].mExpireTime = past;
+    }
+
+    InvalidateDiscoveredPrefixes();
+
+    OT_ASSERT(mDiscoveredPrefixNum == 0);
+}
+
+// Adds a discovered prefix on infra link. If the same prefix already exists,
+// only the lifetime will be updated. Returns a boolean which indicates whether
+// a new prefix is added.
+bool RoutingManager::AddDiscoveredPrefix(const Ip6::Prefix &aPrefix,
+                                         bool               aIsOnLinkPrefix,
+                                         uint32_t           aLifetime,
+                                         otRoutePreference  aRoutePreference)
+{
+    OT_ASSERT(aIsOnLinkPrefix ? IsValidOmrPrefix(aPrefix) : IsValidOnLinkPrefix(aPrefix));
+    OT_ASSERT(aLifetime > 0);
+
+    bool added = false;
+
+    for (uint8_t i = 0; i < mDiscoveredPrefixNum; ++i)
+    {
+        ExternalPrefix &prefix = mDiscoveredPrefixes[i];
+
+        if (aPrefix == prefix.mPrefix && aIsOnLinkPrefix == prefix.mIsOnLinkPrefix)
+        {
+            prefix.mExpireTime = TimerMilli::GetNow() + GetPrefixExpireDelay(aLifetime);
+            mDiscoveredPrefixInvalidTimer.FireAtIfEarlier(prefix.mExpireTime);
+
+            otLogInfoBr("discovered prefix %s refreshed lifetime: %u seconds", aPrefix.ToString().AsCString(),
+                        aLifetime);
+            ExitNow();
+        }
+    }
+
+    if (mDiscoveredPrefixNum < kMaxDiscoveredPrefixNum)
+    {
+        ExternalPrefix &newPrefix = mDiscoveredPrefixes[mDiscoveredPrefixNum];
+
+        SuccessOrExit(AddExternalRoute(aPrefix, aRoutePreference));
+
+        if (aIsOnLinkPrefix && mRouterSolicitCount > 0)
+        {
+            // Stop Router Solicitation if we discovered a valid on-link prefix.
+            // Otherwise, we wait till the Router Solicitation process times out.
+            // So the maximum delay before the Border Router starts advertising
+            // its own on-link prefix is 10 seconds = RS_DELAY (1) + RS_INTERNAL (4)
+            // + RS_INTERVAL (4) + RS_DELAY (1).
+            //
+            // Always send at least one RS message so that all BRs will respond.
+            // Consider that there are multiple BRs on the infra link, we may not learn
+            // RIOs of other BRs if the router discovery process is stopped immediately
+            // before sending any RS messages because of receiving an unsolicited RA.
+            mRouterSolicitTimer.Stop();
+        }
+
+        newPrefix.mPrefix         = aPrefix;
+        newPrefix.mIsOnLinkPrefix = aIsOnLinkPrefix;
+        newPrefix.mExpireTime     = TimerMilli::GetNow() + GetPrefixExpireDelay(aLifetime);
+        mDiscoveredPrefixInvalidTimer.FireAtIfEarlier(newPrefix.mExpireTime);
+
+        ++mDiscoveredPrefixNum;
+        added = true;
+    }
+    else
+    {
+        otLogWarnBr("discovered too many prefixes, ignore new prefix %s", aPrefix.ToString().AsCString());
+    }
+
+exit:
+    return added;
+}
+
+bool RoutingManager::NetworkDataContainsOmrPrefix(const Ip6::Prefix &aPrefix) const
+{
+    NetworkData::Iterator           iterator = NetworkData::kIteratorInit;
+    NetworkData::OnMeshPrefixConfig onMeshPrefixConfig;
+    bool                            contain = false;
+
+    while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, onMeshPrefixConfig) == OT_ERROR_NONE)
+    {
+        if (IsValidOmrPrefix(onMeshPrefixConfig) && onMeshPrefixConfig.GetPrefix() == aPrefix)
+        {
+            contain = true;
+            break;
+        }
+    }
+
+    return contain;
+}
+
+} // namespace BorderRouter
+
+} // namespace ot
+
+#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
diff --git a/src/core/border_router/routing_manager.hpp b/src/core/border_router/routing_manager.hpp
new file mode 100644
index 0000000..c4a9e81
--- /dev/null
+++ b/src/core/border_router/routing_manager.hpp
@@ -0,0 +1,314 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file includes definitions for the RA-based routing management.
+ *
+ */
+
+#ifndef ROUTING_MANAGER_HPP_
+#define ROUTING_MANAGER_HPP_
+
+#include "openthread-core-config.h"
+
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+
+#if !OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
+#error "OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE is required for OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE."
+#endif
+
+#if !OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE
+#error "OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE is required for OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE."
+#endif
+
+#include <openthread/netdata.h>
+#include <openthread/platform/infra_if.h>
+
+#include "border_router/router_advertisement.hpp"
+#include "common/error.hpp"
+#include "common/locator.hpp"
+#include "common/notifier.hpp"
+#include "common/timer.hpp"
+#include "net/ip6.hpp"
+#include "thread/network_data.hpp"
+
+namespace ot {
+
+namespace BorderRouter {
+
+/**
+ * This class implements bi-directional routing between Thread and
+ * Infrastructure networks.
+ *
+ * The Border Routing manager works on both Thread interface and
+ * infrastructure interface. All ICMPv6 messages are sent/recv
+ * on the infrastructure interface.
+ *
+ */
+class RoutingManager : public InstanceLocator
+{
+    friend class ot::Notifier;
+
+public:
+    /**
+     * This constructor initializes the routing manager.
+     *
+     * @param[in]  aInstance  A OpenThread instance.
+     *
+     */
+    explicit RoutingManager(Instance &aInstance);
+
+    /**
+     * This method initializes the routing manager on given infrastructure interface.
+     *
+     * @param[in]  aInfraIfIndex  An infrastructure network interface index.
+     * @param[in]  aInfraIfIsRunning         A boolean that indicates whether the infrastructure
+     *                                       interface is running.
+     * @param[in]  aInfraIfLinkLocalAddress  A pointer to the IPv6 link-local address of the infrastructure
+     *                                       interface. NULL if the IPv6 link-local address is missing.
+     *
+     * @retval  kErrorNone          Successfully started the routing manager.
+     * @retval  kErrorInvalidArgs   The index of the infra interface is not valid.
+     *
+     */
+    Error Init(uint32_t aInfraIfIndex, bool aInfraIfIsRunning, const Ip6::Address *aInfraIfLinkLocalAddress);
+
+    /**
+     * This method enables/disables the Border Routing Manager.
+     *
+     * @note  The Border Routing Manager is enabled by default.
+     *
+     * @param[in]  aEnabled   A boolean to enable/disable the Border Routing Manager.
+     *
+     * @retval  kErrorInvalidState   The Border Routing Manager is not initialized yet.
+     * @retval  kErrorNone           Successfully enabled/disabled the Border Routing Manager.
+     *
+     */
+    Error SetEnabled(bool aEnabled);
+
+    /**
+     * This method receives an ICMPv6 message on the infrastructure interface.
+     *
+     * Malformed or undesired messages are dropped silently.
+     *
+     * @param[in]  aInfraIfIndex  The infrastructure interface index.
+     * @param[in]  aSrcAddress    The source address this message is sent from.
+     * @param[in]  aBuffer        THe ICMPv6 message buffer.
+     * @param[in]  aLength        The length of the ICMPv6 message buffer.
+     *
+     */
+    void RecvIcmp6Message(uint32_t            aInfraIfIndex,
+                          const Ip6::Address &aSrcAddress,
+                          const uint8_t *     aBuffer,
+                          uint16_t            aBufferLength);
+
+    /**
+     * This method handles infrastructure interface state changes.
+     *
+     * @param[in]  aInfraIfIndex      The index of the infrastructure interface.
+     * @param[in]  aIsRunning         A boolean that indicates whether the infrastructure
+     *                                interface is running.
+     * @param[in]  aLinkLocalAddress  A pointer to the IPv6 link local address of the infrastructure
+     *                                interface. NULL if the IPv6 link local address is lost.
+     *
+     * @retval  kErrorNone          Successfully updated the infra interface status.
+     * @retval  kErrorInvalidState  The Routing Manager is not initialized.
+     * @retval  kErrorInvalidArgs   The @p aInfraIfIndex doesn't match the infra interface the Routing Manager are
+     *                              initialized with, or the @p aLinkLocalAddress is not a valid IPv6 link-local
+     *                              address.
+     *
+     */
+    Error HandleInfraIfStateChanged(uint32_t aInfraIfIndex, bool aIsRunning, const Ip6::Address *aLinkLocalAddress);
+
+private:
+    enum : uint16_t
+    {
+        kMaxRouterAdvMessageLength = 256u, // The maximum RA message length we can handle.
+    };
+
+    enum : uint8_t
+    {
+        kMaxOmrPrefixNum =
+            OPENTHREAD_CONFIG_IP6_SLAAC_NUM_ADDRESSES, // The maximum number of the OMR prefixes to advertise.
+        kMaxDiscoveredPrefixNum = 8u,                  // The maximum number of prefixes to discover on the infra link.
+        kOmrPrefixLength        = OT_IP6_PREFIX_BITSIZE, // The length of an OMR prefix. In bits.
+        kOnLinkPrefixLength     = OT_IP6_PREFIX_BITSIZE, // The length of an On-link prefix. In bits.
+    };
+
+    enum : uint32_t
+    {
+        kDefaultOmrPrefixLifetime    = 1800u,                  // The default OMR prefix valid lifetime. In seconds.
+        kDefaultOnLinkPrefixLifetime = 1800u,                  // The default on-link prefix valid lifetime. In seconds.
+        kMaxRtrAdvInterval           = 600,                    // Maximum Router Advertisement Interval. In seconds.
+        kMinRtrAdvInterval           = kMaxRtrAdvInterval / 3, // Minimum Router Advertisement Interval. In seconds.
+        kMaxInitRtrAdvInterval       = 16,  // Maximum Initial Router Advertisement Interval. In seconds.
+        kMaxRaDelayTime              = 500, // The maximum delay of sending RA after receiving RS. In milliseconds.
+        kRtrSolicitationInterval     = 4,   // The interval between Router Solicitations. In seconds.
+        kMaxRtrSolicitationDelay     = 1,   // The maximum delay for initial solicitation. In seconds.
+        kMaxRoutingPolicyDelay       = 1,   // The maximum delay for routing policy evaluation. In seconds.
+    };
+
+    static_assert(kMinRtrAdvInterval <= 3 * kMaxRtrAdvInterval / 4, "invalid RA intervals");
+    static_assert(kDefaultOmrPrefixLifetime >= kMaxRtrAdvInterval, "invalid default OMR prefix lifetime");
+    static_assert(kDefaultOnLinkPrefixLifetime >= kMaxRtrAdvInterval, "invalid default on-link prefix lifetime");
+
+    enum : uint32_t
+    {
+        kMaxInitRtrAdvertisements = 3, // The maximum number of initial Router Advertisements.
+        kMaxRtrSolicitations = 3, // The Maximum number of Router Solicitations before sending Router Advertisements.
+    };
+
+    // This struct represents an external prefix which is
+    // discovered on the infrastructure interface.
+    struct ExternalPrefix : public Clearable<ExternalPrefix>
+    {
+        Ip6::Prefix mPrefix;
+        TimeMilli   mExpireTime;
+        bool        mIsOnLinkPrefix;
+    };
+
+    void  EvaluateState(void);
+    void  Start(void);
+    void  Stop(void);
+    void  HandleNotifierEvents(Events aEvents);
+    bool  IsInitialized(void) const { return mInfraIfIndex != 0; }
+    bool  IsEnabled(void) const { return mIsEnabled; }
+    Error LoadOrGenerateRandomOmrPrefix(void);
+    Error LoadOrGenerateRandomOnLinkPrefix(void);
+
+    const Ip6::Prefix *EvaluateOnLinkPrefix(void);
+
+    void    EvaluateRoutingPolicy(void);
+    void    StartRoutingPolicyEvaluationDelay(void);
+    uint8_t EvaluateOmrPrefix(Ip6::Prefix *aNewOmrPrefixes, uint8_t aMaxOmrPrefixNum);
+    Error   PublishLocalOmrPrefix(void);
+    void    UnpublishLocalOmrPrefix(void);
+    Error   AddExternalRoute(const Ip6::Prefix &aPrefix, otRoutePreference aRoutePreference);
+    void    RemoveExternalRoute(const Ip6::Prefix &aPrefix);
+    void    StartRouterSolicitationDelay(void);
+    Error   SendRouterSolicitation(void);
+    void    SendRouterAdvertisement(const Ip6::Prefix *aNewOmrPrefixes,
+                                    uint8_t            aNewOmrPrefixNum,
+                                    const Ip6::Prefix *aNewOnLinkPrefix);
+
+    static void HandleRouterAdvertisementTimer(Timer &aTimer);
+    void        HandleRouterAdvertisementTimer(void);
+
+    static void HandleRouterSolicitTimer(Timer &aTimer);
+    void        HandleRouterSolicitTimer(void);
+
+    static void HandleDiscoveredPrefixInvalidTimer(Timer &aTimer);
+    void        HandleDiscoveredPrefixInvalidTimer(void);
+
+    static void HandleRoutingPolicyTimer(Timer &aTimer);
+
+    void HandleRouterSolicit(const Ip6::Address &aSrcAddress, const uint8_t *aBuffer, uint16_t aBufferLength);
+    void HandleRouterAdvertisement(const Ip6::Address &aSrcAddress, const uint8_t *aBuffer, uint16_t aBufferLength);
+    bool UpdateDiscoveredPrefixes(const RouterAdv::PrefixInfoOption &aPio);
+    bool UpdateDiscoveredPrefixes(const RouterAdv::RouteInfoOption &aRio);
+    bool InvalidateDiscoveredPrefixes(const Ip6::Prefix *aPrefix = nullptr, bool aIsOnLinkPrefix = true);
+    void InvalidateAllDiscoveredPrefixes(void);
+    bool AddDiscoveredPrefix(const Ip6::Prefix &aPrefix,
+                             bool               aIsOnLinkPrefix,
+                             uint32_t           aLifetime,
+                             otRoutePreference  aRoutePreference = OT_ROUTE_PREFERENCE_MED);
+    bool NetworkDataContainsOmrPrefix(const Ip6::Prefix &aPrefix) const;
+
+    static bool     IsValidOmrPrefix(const NetworkData::OnMeshPrefixConfig &aOnMeshPrefixConfig);
+    static bool     IsValidOmrPrefix(const Ip6::Prefix &aOmrPrefix);
+    static bool     IsValidOnLinkPrefix(const Ip6::Prefix &aOnLinkPrefix);
+    static bool     ContainsPrefix(const Ip6::Prefix &aPrefix, const Ip6::Prefix *aPrefixList, uint8_t aPrefixNum);
+    static uint32_t GetPrefixExpireDelay(uint32_t aValidLifetime);
+
+    // Indicates whether the Routing Manager is running (started).
+    bool mIsRunning;
+
+    // Indicates whether the Routing manager is enabled.
+    // The Routing Manager will be stopped if we are
+    // disabled.
+    bool mIsEnabled;
+
+    // Indicates whether the infra interface is running.
+    // The Routing Manager will be stopped when the
+    // Infra interface is not running.
+    bool mInfraIfIsRunning;
+
+    // The index of the infra interface on which Router
+    // Advertisement messages will be sent.
+    uint32_t mInfraIfIndex;
+
+    // The IPv6 link-local address of the infra interface.
+    // It's UNSPECIFIED if no valid IPv6 link-local address
+    // is associated with the infra interface and the Routing
+    // Manager will be stopped.
+    Ip6::Address mInfraIfLinkLocalAddress;
+
+    // The OMR prefix loaded from local persistent storage or randomly generated
+    // if non is found in persistent storage.
+    Ip6::Prefix mLocalOmrPrefix;
+
+    // The advertised OMR prefixes. For a stable Thread network without
+    // manually configured OMR prefixes, there should be a single OMR prefix
+    // that is being advertised because each BRs will converge to the smallest
+    // OMR prefix sorted by method IsPrefixSmallerThan. If manually configured
+    // OMR prefixes exist, they will also be advertised on infra link.
+    Ip6::Prefix mAdvertisedOmrPrefixes[kMaxOmrPrefixNum];
+    uint8_t     mAdvertisedOmrPrefixNum;
+
+    // The on-link prefix loaded from local persistent storage or randomly generated
+    // if non is found in persistent storage.
+    Ip6::Prefix mLocalOnLinkPrefix;
+
+    // Could only be nullptr or a pointer to mLocalOnLinkPrefix.
+    const Ip6::Prefix *mAdvertisedOnLinkPrefix;
+
+    // The array of prefixes discovered on the infra link. Those prefixes consist of
+    // on-link prefix(es) and OMR prefixes advertised by BRs in another Thread Network
+    // which is connected to the same infra link.
+    ExternalPrefix mDiscoveredPrefixes[kMaxDiscoveredPrefixNum];
+    uint8_t        mDiscoveredPrefixNum;
+
+    TimerMilli mDiscoveredPrefixInvalidTimer;
+
+    TimerMilli mRouterAdvertisementTimer;
+    uint32_t   mRouterAdvertisementCount;
+
+    TimerMilli mRouterSolicitTimer;
+    uint8_t    mRouterSolicitCount;
+
+    TimerMilli mRoutingPolicyTimer;
+};
+
+} // namespace BorderRouter
+
+} // namespace ot
+
+#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+
+#endif // ROUTING_MANAGER_HPP_
diff --git a/src/core/coap/coap.cpp b/src/core/coap/coap.cpp
index 36f4c9d..9761940 100644
--- a/src/core/coap/coap.cpp
+++ b/src/core/coap/coap.cpp
@@ -56,6 +56,9 @@
     , mDefaultHandler(nullptr)
     , mDefaultHandlerContext(nullptr)
     , mSender(aSender)
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    , mLastResponse(nullptr)
+#endif
 {
 }
 
@@ -83,11 +86,24 @@
 
         if ((aAddress == nullptr) || (metadata.mSourceAddress == *aAddress))
         {
-            FinalizeCoapTransaction(*message, metadata, nullptr, nullptr, OT_ERROR_ABORT);
+            FinalizeCoapTransaction(*message, metadata, nullptr, nullptr, kErrorAbort);
         }
     }
 }
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+void CoapBase::AddBlockWiseResource(ResourceBlockWise &aResource)
+{
+    IgnoreError(mBlockWiseResources.Add(aResource));
+}
+
+void CoapBase::RemoveBlockWiseResource(ResourceBlockWise &aResource)
+{
+    IgnoreError(mBlockWiseResources.Remove(aResource));
+    aResource.SetNext(nullptr);
+}
+#endif
+
 void CoapBase::AddResource(Resource &aResource)
 {
     IgnoreError(mResources.Add(aResource));
@@ -122,9 +138,9 @@
     return message;
 }
 
-otError CoapBase::Send(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
+Error CoapBase::Send(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError error;
+    Error error;
 
 #if OPENTHREAD_CONFIG_OTNS_ENABLE
     Get<Utils::Otns>().EmitCoapSend(static_cast<Message &>(aMessage), aMessageInfo);
@@ -133,7 +149,7 @@
     error = mSender(*this, aMessage, aMessageInfo);
 
 #if OPENTHREAD_CONFIG_OTNS_ENABLE
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         Get<Utils::Otns>().EmitCoapSendFailure(error, static_cast<Message &>(aMessage), aMessageInfo);
     }
@@ -141,25 +157,76 @@
     return error;
 }
 
-otError CoapBase::SendMessage(Message &               aMessage,
-                              const Ip6::MessageInfo &aMessageInfo,
-                              const TxParameters &    aTxParameters,
-                              ResponseHandler         aHandler,
-                              void *                  aContext)
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+Error CoapBase::SendMessage(Message &                   aMessage,
+                            const Ip6::MessageInfo &    aMessageInfo,
+                            const TxParameters &        aTxParameters,
+                            ResponseHandler             aHandler,
+                            void *                      aContext,
+                            otCoapBlockwiseTransmitHook aTransmitHook,
+                            otCoapBlockwiseReceiveHook  aReceiveHook)
+#else
+Error CoapBase::SendMessage(Message &               aMessage,
+                            const Ip6::MessageInfo &aMessageInfo,
+                            const TxParameters &    aTxParameters,
+                            ResponseHandler         aHandler,
+                            void *                  aContext)
+#endif
 {
-    otError  error;
+    Error    error;
     Message *storedCopy = nullptr;
     uint16_t copyLength = 0;
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    uint8_t  buf[kMaxBlockLength] = {0};
+    uint16_t bufLen               = kMaxBlockLength;
+    bool     moreBlocks           = false;
+#endif
 
     switch (aMessage.GetType())
     {
     case kTypeAck:
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        // Check for block-wise transfer
+        if ((aTransmitHook != nullptr) && (aMessage.ReadBlockOptionValues(kOptionBlock2) == kErrorNone) &&
+            (aMessage.GetBlockWiseBlockNumber() == 0))
+        {
+            // Set payload for first block of the transfer
+            VerifyOrExit((bufLen = otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize())) <= kMaxBlockLength,
+                         error = kErrorNoBufs);
+            SuccessOrExit(error = aTransmitHook(aContext, buf, aMessage.GetBlockWiseBlockNumber() * bufLen, &bufLen,
+                                                &moreBlocks));
+            SuccessOrExit(error = aMessage.AppendBytes(buf, bufLen));
+
+            SuccessOrExit(error = CacheLastBlockResponse(&aMessage));
+        }
+#endif
+
         mResponsesQueue.EnqueueResponse(aMessage, aMessageInfo, aTxParameters);
         break;
     case kTypeReset:
         OT_ASSERT(aMessage.GetCode() == kCodeEmpty);
         break;
     default:
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        // Check for block-wise transfer
+        if ((aTransmitHook != nullptr) && (aMessage.ReadBlockOptionValues(kOptionBlock1) == kErrorNone) &&
+            (aMessage.GetBlockWiseBlockNumber() == 0))
+        {
+            // Set payload for first block of the transfer
+            VerifyOrExit((bufLen = otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize())) <= kMaxBlockLength,
+                         error = kErrorNoBufs);
+            SuccessOrExit(error = aTransmitHook(aContext, buf, aMessage.GetBlockWiseBlockNumber() * bufLen, &bufLen,
+                                                &moreBlocks));
+            SuccessOrExit(error = aMessage.AppendBytes(buf, bufLen));
+
+            // Block-Wise messages always have to be confirmable
+            if (aMessage.IsNonConfirmable())
+            {
+                aMessage.SetType(kTypeConfirmable);
+            }
+        }
+#endif
+
         aMessage.SetMessageId(mMessageId++);
         break;
     }
@@ -208,7 +275,7 @@
                 Message *origRequest = FindRelatedRequest(aMessage, aMessageInfo, handlerMetadata);
                 if (origRequest != nullptr)
                 {
-                    FinalizeCoapTransaction(*origRequest, handlerMetadata, nullptr, nullptr, OT_ERROR_NONE);
+                    FinalizeCoapTransaction(*origRequest, handlerMetadata, nullptr, nullptr, kErrorNone);
                 }
             }
         }
@@ -228,6 +295,10 @@
         metadata.mHopLimit        = aMessageInfo.GetHopLimit();
         metadata.mIsHostInterface = aMessageInfo.IsHostInterface();
 #endif
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        metadata.mBlockwiseReceiveHook  = aReceiveHook;
+        metadata.mBlockwiseTransmitHook = aTransmitHook;
+#endif
 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
         metadata.mObserve = observe;
 #endif
@@ -236,14 +307,14 @@
             (metadata.mConfirmable ? metadata.mRetransmissionTimeout : aTxParameters.CalculateMaxTransmitWait());
 
         storedCopy = CopyAndEnqueueMessage(aMessage, copyLength, metadata);
-        VerifyOrExit(storedCopy != nullptr, error = OT_ERROR_NO_BUFS);
+        VerifyOrExit(storedCopy != nullptr, error = kErrorNoBufs);
     }
 
     SuccessOrExit(error = Send(aMessage, aMessageInfo));
 
 exit:
 
-    if (error != OT_ERROR_NONE && storedCopy != nullptr)
+    if (error != kErrorNone && storedCopy != nullptr)
     {
         DequeueMessage(*storedCopy);
     }
@@ -251,42 +322,46 @@
     return error;
 }
 
-otError CoapBase::SendMessage(Message &               aMessage,
-                              const Ip6::MessageInfo &aMessageInfo,
-                              ResponseHandler         aHandler,
-                              void *                  aContext)
+Error CoapBase::SendMessage(Message &               aMessage,
+                            const Ip6::MessageInfo &aMessageInfo,
+                            ResponseHandler         aHandler,
+                            void *                  aContext)
 {
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    return SendMessage(aMessage, aMessageInfo, TxParameters::GetDefault(), aHandler, aContext, nullptr, nullptr);
+#else
     return SendMessage(aMessage, aMessageInfo, TxParameters::GetDefault(), aHandler, aContext);
+#endif
 }
 
-otError CoapBase::SendReset(Message &aRequest, const Ip6::MessageInfo &aMessageInfo)
+Error CoapBase::SendReset(Message &aRequest, const Ip6::MessageInfo &aMessageInfo)
 {
     return SendEmptyMessage(kTypeReset, aRequest, aMessageInfo);
 }
 
-otError CoapBase::SendAck(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo)
+Error CoapBase::SendAck(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo)
 {
     return SendEmptyMessage(kTypeAck, aRequest, aMessageInfo);
 }
 
-otError CoapBase::SendEmptyAck(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo, Code aCode)
+Error CoapBase::SendEmptyAck(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo, Code aCode)
 {
-    return (aRequest.IsConfirmable() ? SendHeaderResponse(aCode, aRequest, aMessageInfo) : OT_ERROR_INVALID_ARGS);
+    return (aRequest.IsConfirmable() ? SendHeaderResponse(aCode, aRequest, aMessageInfo) : kErrorInvalidArgs);
 }
 
-otError CoapBase::SendNotFound(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo)
+Error CoapBase::SendNotFound(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo)
 {
     return SendHeaderResponse(kCodeNotFound, aRequest, aMessageInfo);
 }
 
-otError CoapBase::SendEmptyMessage(Type aType, const Message &aRequest, const Ip6::MessageInfo &aMessageInfo)
+Error CoapBase::SendEmptyMessage(Type aType, const Message &aRequest, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError  error   = OT_ERROR_NONE;
+    Error    error   = kErrorNone;
     Message *message = nullptr;
 
-    VerifyOrExit(aRequest.IsConfirmable(), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aRequest.IsConfirmable(), error = kErrorInvalidArgs);
 
-    VerifyOrExit((message = NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMessage()) != nullptr, error = kErrorNoBufs);
 
     message->Init(aType, kCodeEmpty);
     message->SetMessageId(aRequest.GetMessageId());
@@ -299,13 +374,13 @@
     return error;
 }
 
-otError CoapBase::SendHeaderResponse(Message::Code aCode, const Message &aRequest, const Ip6::MessageInfo &aMessageInfo)
+Error CoapBase::SendHeaderResponse(Message::Code aCode, const Message &aRequest, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError  error   = OT_ERROR_NONE;
+    Error    error   = kErrorNone;
     Message *message = nullptr;
 
-    VerifyOrExit(aRequest.IsRequest(), error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit((message = NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(aRequest.IsRequest(), error = kErrorInvalidArgs);
+    VerifyOrExit((message = NewMessage()) != nullptr, error = kErrorNoBufs);
 
     switch (aRequest.GetType())
     {
@@ -319,7 +394,7 @@
         break;
 
     default:
-        ExitNow(error = OT_ERROR_INVALID_ARGS);
+        ExitNow(error = kErrorInvalidArgs);
         OT_UNREACHABLE_CODE(break);
     }
 
@@ -364,7 +439,7 @@
             if (!metadata.mConfirmable || (metadata.mRetransmissionsRemaining == 0))
             {
                 // No expected response or acknowledgment.
-                FinalizeCoapTransaction(*message, metadata, nullptr, nullptr, OT_ERROR_RESPONSE_TIMEOUT);
+                FinalizeCoapTransaction(*message, metadata, nullptr, nullptr, kErrorResponseTimeout);
                 continue;
             }
 
@@ -406,7 +481,7 @@
                                        const Metadata &        aMetadata,
                                        Message *               aResponse,
                                        const Ip6::MessageInfo *aMessageInfo,
-                                       otError                 aResult)
+                                       Error                   aResult)
 {
     DequeueMessage(aRequest);
 
@@ -416,9 +491,9 @@
     }
 }
 
-otError CoapBase::AbortTransaction(ResponseHandler aHandler, void *aContext)
+Error CoapBase::AbortTransaction(ResponseHandler aHandler, void *aContext)
 {
-    otError  error = OT_ERROR_NOT_FOUND;
+    Error    error = kErrorNotFound;
     Message *nextMessage;
     Metadata metadata;
 
@@ -429,8 +504,8 @@
 
         if (metadata.mResponseHandler == aHandler && metadata.mResponseContext == aContext)
         {
-            FinalizeCoapTransaction(*message, metadata, nullptr, nullptr, OT_ERROR_ABORT);
-            error = OT_ERROR_NONE;
+            FinalizeCoapTransaction(*message, metadata, nullptr, nullptr, kErrorAbort);
+            error = kErrorNone;
         }
     }
 
@@ -439,10 +514,10 @@
 
 Message *CoapBase::CopyAndEnqueueMessage(const Message &aMessage, uint16_t aCopyLength, const Metadata &aMetadata)
 {
-    otError  error       = OT_ERROR_NONE;
+    Error    error       = kErrorNone;
     Message *messageCopy = nullptr;
 
-    VerifyOrExit((messageCopy = aMessage.Clone(aCopyLength)) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((messageCopy = aMessage.Clone(aCopyLength)) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = aMetadata.AppendTo(*messageCopy));
 
@@ -470,22 +545,417 @@
     // the timer would just shoot earlier and then it'd be setup again.
 }
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+void CoapBase::FreeLastBlockResponse(void)
+{
+    if (mLastResponse != nullptr)
+    {
+        mLastResponse->Free();
+        mLastResponse = nullptr;
+    }
+}
+
+Error CoapBase::CacheLastBlockResponse(Message *aResponse)
+{
+    Error error = kErrorNone;
+    // Save last response for block-wise transfer
+    FreeLastBlockResponse();
+
+    if ((mLastResponse = aResponse->Clone()) == nullptr)
+    {
+        error = kErrorNoBufs;
+    }
+
+    return error;
+}
+
+Error CoapBase::PrepareNextBlockRequest(Message::BlockType aType,
+                                        bool               aMoreBlocks,
+                                        Message &          aRequestOld,
+                                        Message &          aRequest,
+                                        Message &          aMessage)
+{
+    Error            error       = kErrorNone;
+    bool             isOptionSet = false;
+    uint64_t         optionBuf   = 0;
+    uint16_t         blockOption = 0;
+    Option::Iterator iterator;
+
+    blockOption = (aType == Message::kBlockType1) ? kOptionBlock1 : kOptionBlock2;
+
+    aRequest.Init(kTypeConfirmable, static_cast<ot::Coap::Code>(aRequestOld.GetCode()));
+    SuccessOrExit(error = iterator.Init(aRequestOld));
+
+    // Copy options from last response to next message
+    for (; !iterator.IsDone() && iterator.GetOption()->GetLength() != 0; error = iterator.Advance())
+    {
+        uint16_t optionNumber = iterator.GetOption()->GetNumber();
+
+        SuccessOrExit(error);
+
+        // Check if option to copy next is higher than or equal to Block1 option
+        if (optionNumber >= blockOption && !isOptionSet)
+        {
+            // Write Block1 option to next message
+            SuccessOrExit(error = aRequest.AppendBlockOption(aType, aMessage.GetBlockWiseBlockNumber() + 1, aMoreBlocks,
+                                                             aMessage.GetBlockWiseBlockSize()));
+            aRequest.SetBlockWiseBlockNumber(aMessage.GetBlockWiseBlockNumber() + 1);
+            aRequest.SetBlockWiseBlockSize(aMessage.GetBlockWiseBlockSize());
+            aRequest.SetMoreBlocksFlag(aMoreBlocks);
+
+            isOptionSet = true;
+
+            // If option to copy next is Block1 or Block2 option, option is not copied
+            if (optionNumber == kOptionBlock1 || optionNumber == kOptionBlock2)
+            {
+                continue;
+            }
+        }
+
+        // Copy option
+        SuccessOrExit(error = iterator.ReadOptionValue(&optionBuf));
+        SuccessOrExit(error = aRequest.AppendOption(optionNumber, iterator.GetOption()->GetLength(), &optionBuf));
+    }
+
+    if (!isOptionSet)
+    {
+        // Write Block1 option to next message
+        SuccessOrExit(error = aRequest.AppendBlockOption(aType, aMessage.GetBlockWiseBlockNumber() + 1, aMoreBlocks,
+                                                         aMessage.GetBlockWiseBlockSize()));
+        aRequest.SetBlockWiseBlockNumber(aMessage.GetBlockWiseBlockNumber() + 1);
+        aRequest.SetBlockWiseBlockSize(aMessage.GetBlockWiseBlockSize());
+        aRequest.SetMoreBlocksFlag(aMoreBlocks);
+
+        isOptionSet = true;
+    }
+
+exit:
+    return error;
+}
+
+Error CoapBase::SendNextBlock1Request(Message &               aRequest,
+                                      Message &               aMessage,
+                                      const Ip6::MessageInfo &aMessageInfo,
+                                      const Metadata &        aCoapMetadata)
+{
+    Error    error                = kErrorNone;
+    Message *request              = nullptr;
+    bool     moreBlocks           = false;
+    uint8_t  buf[kMaxBlockLength] = {0};
+    uint16_t bufLen               = kMaxBlockLength;
+
+    SuccessOrExit(error = aRequest.ReadBlockOptionValues(kOptionBlock1));
+    SuccessOrExit(error = aMessage.ReadBlockOptionValues(kOptionBlock1));
+
+    // Conclude block-wise transfer if last block has been received
+    if (!aRequest.IsMoreBlocksFlagSet())
+    {
+        FinalizeCoapTransaction(aRequest, aCoapMetadata, &aMessage, &aMessageInfo, kErrorNone);
+        ExitNow();
+    }
+
+    // Get next block
+    VerifyOrExit((bufLen = otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize())) <= kMaxBlockLength,
+                 error = kErrorNoBufs);
+
+    SuccessOrExit(
+        error = aCoapMetadata.mBlockwiseTransmitHook(aCoapMetadata.mResponseContext, buf,
+                                                     otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) *
+                                                         (aMessage.GetBlockWiseBlockNumber() + 1),
+                                                     &bufLen, &moreBlocks));
+
+    // Check if block length is valid
+    VerifyOrExit(bufLen <= otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()), error = kErrorInvalidArgs);
+
+    // Init request for next block
+    VerifyOrExit((request = NewMessage()) != nullptr, error = kErrorNoBufs);
+    SuccessOrExit(error = PrepareNextBlockRequest(Message::kBlockType1, moreBlocks, aRequest, *request, aMessage));
+
+    SuccessOrExit(error = request->SetPayloadMarker());
+
+    SuccessOrExit(error = request->AppendBytes(buf, bufLen));
+
+    DequeueMessage(aRequest);
+
+    otLogInfoCoap("Send Block1 Nr. %d, Size: %d bytes, More Blocks Flag: %d", request->GetBlockWiseBlockNumber(),
+                  otCoapBlockSizeFromExponent(request->GetBlockWiseBlockSize()), request->IsMoreBlocksFlagSet());
+
+    SuccessOrExit(error = SendMessage(*request, aMessageInfo, TxParameters::GetDefault(),
+                                      aCoapMetadata.mResponseHandler, aCoapMetadata.mResponseContext,
+                                      aCoapMetadata.mBlockwiseTransmitHook, aCoapMetadata.mBlockwiseReceiveHook));
+
+exit:
+    FreeMessageOnError(request, error);
+
+    return error;
+}
+
+Error CoapBase::SendNextBlock2Request(Message &               aRequest,
+                                      Message &               aMessage,
+                                      const Ip6::MessageInfo &aMessageInfo,
+                                      const Metadata &        aCoapMetadata,
+                                      uint32_t                aTotalLength,
+                                      bool                    aBeginBlock1Transfer)
+{
+    Error    error                = kErrorNone;
+    Message *request              = nullptr;
+    uint8_t  buf[kMaxBlockLength] = {0};
+    uint16_t bufLen               = kMaxBlockLength;
+
+    SuccessOrExit(error = aMessage.ReadBlockOptionValues(kOptionBlock2));
+
+    // Check payload and block length
+    VerifyOrExit((aMessage.GetLength() - aMessage.GetOffset()) <=
+                         otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) &&
+                     (aMessage.GetLength() - aMessage.GetOffset()) <= kMaxBlockLength,
+                 error = kErrorNoBufs);
+
+    // Read and then forward payload to receive hook function
+    bufLen = aMessage.ReadBytes(aMessage.GetOffset(), buf, aMessage.GetLength() - aMessage.GetOffset());
+    SuccessOrExit(
+        error = aCoapMetadata.mBlockwiseReceiveHook(aCoapMetadata.mResponseContext, buf,
+                                                    otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) *
+                                                        aMessage.GetBlockWiseBlockNumber(),
+                                                    bufLen, aMessage.IsMoreBlocksFlagSet(), aTotalLength));
+
+    // CoAP Block-Wise Transfer continues
+    otLogInfoCoap("Received Block2 Nr. %d , Size: %d bytes, More Blocks Flag: %d", aMessage.GetBlockWiseBlockNumber(),
+                  otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()), aMessage.IsMoreBlocksFlagSet());
+
+    // Conclude block-wise transfer if last block has been received
+    if (!aMessage.IsMoreBlocksFlagSet())
+    {
+        FinalizeCoapTransaction(aRequest, aCoapMetadata, &aMessage, &aMessageInfo, kErrorNone);
+        ExitNow();
+    }
+
+    // Init request for next block
+    VerifyOrExit((request = NewMessage()) != nullptr, error = kErrorNoBufs);
+    SuccessOrExit(error = PrepareNextBlockRequest(Message::kBlockType2, aMessage.IsMoreBlocksFlagSet(), aRequest,
+                                                  *request, aMessage));
+
+    if (!aBeginBlock1Transfer)
+    {
+        DequeueMessage(aRequest);
+    }
+
+    otLogInfoCoap("Request Block2 Nr. %d, Size: %d bytes", request->GetBlockWiseBlockNumber(),
+                  otCoapBlockSizeFromExponent(request->GetBlockWiseBlockSize()));
+
+    SuccessOrExit(error =
+                      SendMessage(*request, aMessageInfo, TxParameters::GetDefault(), aCoapMetadata.mResponseHandler,
+                                  aCoapMetadata.mResponseContext, nullptr, aCoapMetadata.mBlockwiseReceiveHook));
+
+exit:
+    FreeMessageOnError(request, error);
+
+    return error;
+}
+
+Error CoapBase::ProcessBlock1Request(Message &                aMessage,
+                                     const Ip6::MessageInfo & aMessageInfo,
+                                     const ResourceBlockWise &aResource,
+                                     uint32_t                 aTotalLength)
+{
+    Error    error                = kErrorNone;
+    Message *response             = nullptr;
+    uint8_t  buf[kMaxBlockLength] = {0};
+    uint16_t bufLen               = kMaxBlockLength;
+
+    SuccessOrExit(error = aMessage.ReadBlockOptionValues(kOptionBlock1));
+
+    // Read and then forward payload to receive hook function
+    VerifyOrExit((aMessage.GetLength() - aMessage.GetOffset()) <= kMaxBlockLength, error = kErrorNoBufs);
+    bufLen = aMessage.ReadBytes(aMessage.GetOffset(), buf, aMessage.GetLength() - aMessage.GetOffset());
+    SuccessOrExit(error = aResource.HandleBlockReceive(buf,
+                                                       otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) *
+                                                           aMessage.GetBlockWiseBlockNumber(),
+                                                       bufLen, aMessage.IsMoreBlocksFlagSet(), aTotalLength));
+
+    if (aMessage.IsMoreBlocksFlagSet())
+    {
+        // Set up next response
+        VerifyOrExit((response = NewMessage()) != nullptr, error = kErrorFailed);
+        response->Init(kTypeAck, kCodeContinue);
+        response->SetMessageId(aMessage.GetMessageId());
+        IgnoreReturnValue(
+            response->SetToken(static_cast<const Message &>(aMessage).GetToken(), aMessage.GetTokenLength()));
+
+        response->SetBlockWiseBlockNumber(aMessage.GetBlockWiseBlockNumber());
+        response->SetMoreBlocksFlag(aMessage.IsMoreBlocksFlagSet());
+        response->SetBlockWiseBlockSize(aMessage.GetBlockWiseBlockSize());
+
+        SuccessOrExit(error = response->AppendBlockOption(Message::kBlockType1, response->GetBlockWiseBlockNumber(),
+                                                          response->IsMoreBlocksFlagSet(),
+                                                          response->GetBlockWiseBlockSize()));
+
+        SuccessOrExit(error = CacheLastBlockResponse(response));
+
+        otLogInfoCoap("Acknowledge Block1 Nr. %d, Size: %d bytes", response->GetBlockWiseBlockNumber(),
+                      otCoapBlockSizeFromExponent(response->GetBlockWiseBlockSize()));
+
+        SuccessOrExit(error = SendMessage(*response, aMessageInfo));
+
+        error = kErrorBusy;
+    }
+    else
+    {
+        // Conclude block-wise transfer if last block has been received
+        FreeLastBlockResponse();
+        error = kErrorNone;
+    }
+
+exit:
+    if (error != kErrorNone && error != kErrorBusy && response != nullptr)
+    {
+        response->Free();
+    }
+
+    return error;
+}
+
+Error CoapBase::ProcessBlock2Request(Message &                aMessage,
+                                     const Ip6::MessageInfo & aMessageInfo,
+                                     const ResourceBlockWise &aResource)
+{
+    Error            error                = kErrorNone;
+    Message *        response             = nullptr;
+    uint8_t          buf[kMaxBlockLength] = {0};
+    uint16_t         bufLen               = kMaxBlockLength;
+    bool             moreBlocks           = false;
+    uint64_t         optionBuf            = 0;
+    Option::Iterator iterator;
+
+    SuccessOrExit(error = aMessage.ReadBlockOptionValues(kOptionBlock2));
+
+    otLogInfoCoap("Request for Block2 Nr. %d, Size: %d bytes received", aMessage.GetBlockWiseBlockNumber(),
+                  otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()));
+
+    if (aMessage.GetBlockWiseBlockNumber() == 0)
+    {
+        aResource.HandleRequest(aMessage, aMessageInfo);
+        ExitNow();
+    }
+
+    // Set up next response
+    VerifyOrExit((response = NewMessage()) != nullptr, error = kErrorNoBufs);
+    response->Init(kTypeAck, kCodeContent);
+    response->SetMessageId(aMessage.GetMessageId());
+
+    VerifyOrExit((bufLen = otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize())) <= kMaxBlockLength,
+                 error = kErrorNoBufs);
+    SuccessOrExit(error = aResource.HandleBlockTransmit(buf,
+                                                        otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) *
+                                                            aMessage.GetBlockWiseBlockNumber(),
+                                                        &bufLen, &moreBlocks));
+
+    response->SetMoreBlocksFlag(moreBlocks);
+    if (moreBlocks)
+    {
+        switch (bufLen)
+        {
+        case 1024:
+            response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_1024);
+            break;
+        case 512:
+            response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_512);
+            break;
+        case 256:
+            response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_256);
+            break;
+        case 128:
+            response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_128);
+            break;
+        case 64:
+            response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_64);
+            break;
+        case 32:
+            response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_32);
+            break;
+        case 16:
+            response->SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_16);
+            break;
+        default:
+            error = kErrorInvalidArgs;
+            ExitNow();
+            break;
+        }
+    }
+    else
+    {
+        // Verify that buffer length is not larger than requested block size
+        VerifyOrExit(bufLen <= otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()),
+                     error = kErrorInvalidArgs);
+        response->SetBlockWiseBlockSize(aMessage.GetBlockWiseBlockSize());
+    }
+
+    response->SetBlockWiseBlockNumber(
+        (otCoapBlockSizeFromExponent(aMessage.GetBlockWiseBlockSize()) * aMessage.GetBlockWiseBlockNumber()) /
+        (otCoapBlockSizeFromExponent(response->GetBlockWiseBlockSize())));
+
+    // Copy options from last response
+    SuccessOrExit(error = iterator.Init(*mLastResponse));
+
+    while (!iterator.IsDone())
+    {
+        uint16_t optionNumber = iterator.GetOption()->GetNumber();
+
+        if (optionNumber == kOptionBlock2)
+        {
+            SuccessOrExit(error = response->AppendBlockOption(Message::kBlockType2, response->GetBlockWiseBlockNumber(),
+                                                              response->IsMoreBlocksFlagSet(),
+                                                              response->GetBlockWiseBlockSize()));
+        }
+        else if (optionNumber == kOptionBlock1)
+        {
+            SuccessOrExit(error = iterator.ReadOptionValue(&optionBuf));
+            SuccessOrExit(error = response->AppendOption(optionNumber, iterator.GetOption()->GetLength(), &optionBuf));
+        }
+
+        SuccessOrExit(error = iterator.Advance());
+    }
+
+    SuccessOrExit(error = response->SetPayloadMarker());
+    SuccessOrExit(error = response->AppendBytes(buf, bufLen));
+
+    if (response->IsMoreBlocksFlagSet())
+    {
+        SuccessOrExit(error = CacheLastBlockResponse(response));
+    }
+    else
+    {
+        // Conclude block-wise transfer if last block has been received
+        FreeLastBlockResponse();
+    }
+
+    otLogInfoCoap("Send Block2 Nr. %d, Size: %d bytes, More Blocks Flag %d", response->GetBlockWiseBlockNumber(),
+                  otCoapBlockSizeFromExponent(response->GetBlockWiseBlockSize()), response->IsMoreBlocksFlagSet());
+
+    SuccessOrExit(error = SendMessage(*response, aMessageInfo));
+
+exit:
+    FreeMessageOnError(response, error);
+
+    return error;
+}
+#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+
 void CoapBase::SendCopy(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError  error;
+    Error    error;
     Message *messageCopy = nullptr;
 
     // Create a message copy for lower layers.
     messageCopy = aMessage.Clone(aMessage.GetLength() - sizeof(Metadata));
-    VerifyOrExit(messageCopy != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(messageCopy != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = Send(*messageCopy, aMessageInfo));
 
 exit:
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogWarnCoap("Failed to send copy: %s", otThreadErrorToString(error));
+        otLogWarnCoap("Failed to send copy: %s", ErrorToString(error));
         FreeMessage(messageCopy);
     }
 }
@@ -536,7 +1006,7 @@
 {
     Message &message = static_cast<Message &>(aMessage);
 
-    if (message.ParseHeader() != OT_ERROR_NONE)
+    if (message.ParseHeader() != kErrorNone)
     {
         otLogDebgCoap("Failed to parse CoAP header");
 
@@ -563,10 +1033,14 @@
 {
     Metadata metadata;
     Message *request = nullptr;
-    otError  error   = OT_ERROR_NONE;
+    Error    error   = kErrorNone;
 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
     bool responseObserve = false;
 #endif
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    uint8_t  blockOptionType    = 0;
+    uint32_t totalTransfereSize = 0;
+#endif
 
     request = FindRelatedRequest(aMessage, aMessageInfo, metadata);
     VerifyOrExit(request != nullptr);
@@ -587,7 +1061,7 @@
     case kTypeReset:
         if (aMessage.IsEmpty())
         {
-            FinalizeCoapTransaction(*request, metadata, nullptr, nullptr, OT_ERROR_ABORT);
+            FinalizeCoapTransaction(*request, metadata, nullptr, nullptr, kErrorAbort);
         }
 
         // Silently ignore non-empty reset messages (RFC 7252, p. 4.2).
@@ -603,7 +1077,7 @@
                 // This is the ACK to our RFC7641 notification.  There will be no
                 // "separate" response so pass it back as if it were a piggy-backed
                 // response so we can stop re-sending and the application can move on.
-                FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, OT_ERROR_NONE);
+                FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, kErrorNone);
             }
             else
 #endif
@@ -634,7 +1108,7 @@
             if (metadata.mObserve && responseObserve && (metadata.mResponseHandler != nullptr))
             {
                 // This is a RFC7641 notification.  The request is *not* done!
-                metadata.mResponseHandler(metadata.mResponseContext, &aMessage, &aMessageInfo, OT_ERROR_NONE);
+                metadata.mResponseHandler(metadata.mResponseContext, &aMessage, &aMessageInfo, kErrorNone);
 
                 // Consider the message acknowledged at this point.
                 metadata.mAcknowledged = true;
@@ -642,9 +1116,89 @@
             }
             else
 #endif
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
             {
-                FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, OT_ERROR_NONE);
+                if (metadata.mBlockwiseTransmitHook != nullptr || metadata.mBlockwiseReceiveHook != nullptr)
+                {
+                    // Search for CoAP Block-Wise Option [RFC7959]
+                    Option::Iterator iterator;
+
+                    SuccessOrExit(error = iterator.Init(aMessage));
+                    while (!iterator.IsDone())
+                    {
+                        switch (iterator.GetOption()->GetNumber())
+                        {
+                        case kOptionBlock1:
+                            blockOptionType += 1;
+                            break;
+
+                        case kOptionBlock2:
+                            blockOptionType += 2;
+                            break;
+
+                        case kOptionSize2:
+                            // ToDo: wait for method to read uint option values
+                            totalTransfereSize = 0;
+                            break;
+
+                        default:
+                            break;
+                        }
+
+                        SuccessOrExit(error = iterator.Advance());
+                    }
+                }
+                switch (blockOptionType)
+                {
+                case 0:
+                    // Piggybacked response.
+                    FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, kErrorNone);
+                    break;
+                case 1: // Block1 option
+                    if (aMessage.GetCode() == kCodeContinue && metadata.mBlockwiseTransmitHook != nullptr)
+                    {
+                        error = SendNextBlock1Request(*request, aMessage, aMessageInfo, metadata);
+                    }
+
+                    if (aMessage.GetCode() != kCodeContinue || metadata.mBlockwiseTransmitHook == nullptr ||
+                        error != kErrorNone)
+                    {
+                        FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, error);
+                    }
+                    break;
+                case 2: // Block2 option
+                    if (aMessage.GetCode() < kCodeBadRequest && metadata.mBlockwiseReceiveHook != nullptr)
+                    {
+                        error = SendNextBlock2Request(*request, aMessage, aMessageInfo, metadata, totalTransfereSize,
+                                                      false);
+                    }
+
+                    if (aMessage.GetCode() >= kCodeBadRequest || metadata.mBlockwiseReceiveHook == nullptr ||
+                        error != kErrorNone)
+                    {
+                        FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, error);
+                    }
+                    break;
+                case 3: // Block1 & Block2 option
+                    if (aMessage.GetCode() < kCodeBadRequest && metadata.mBlockwiseReceiveHook != nullptr)
+                    {
+                        error =
+                            SendNextBlock2Request(*request, aMessage, aMessageInfo, metadata, totalTransfereSize, true);
+                    }
+
+                    FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, error);
+                    break;
+                default:
+                    error = kErrorAbort;
+                    FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, error);
+                    break;
+                }
             }
+#else  // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+            {
+                FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, kErrorNone);
+            }
+#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
         }
 
         // Silently ignore acknowledgments carrying requests (RFC 7252, p. 4.2)
@@ -654,7 +1208,8 @@
     case kTypeConfirmable:
         // Send empty ACK if it is a CON message.
         IgnoreError(SendAck(aMessage, aMessageInfo));
-        // Fall through
+
+        OT_FALL_THROUGH;
         // Handling of RFC7641 and multicast is below.
     case kTypeNonConfirmable:
         // Separate response or observation notification.  If the request was to a multicast
@@ -667,11 +1222,11 @@
                                                            ))
         {
             // If multicast non-confirmable request, allow multiple responses
-            metadata.mResponseHandler(metadata.mResponseContext, &aMessage, &aMessageInfo, OT_ERROR_NONE);
+            metadata.mResponseHandler(metadata.mResponseContext, &aMessage, &aMessageInfo, kErrorNone);
         }
         else
         {
-            FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, OT_ERROR_NONE);
+            FinalizeCoapTransaction(*request, metadata, &aMessage, &aMessageInfo, kErrorNone);
         }
 
         break;
@@ -679,7 +1234,7 @@
 
 exit:
 
-    if (error == OT_ERROR_NONE && request == nullptr)
+    if (error == kErrorNone && request == nullptr)
     {
         if (aMessage.IsConfirmable() || aMessage.IsNonConfirmable())
         {
@@ -694,7 +1249,13 @@
 {
     char     uriPath[Message::kMaxReceivedUriPath + 1];
     Message *cachedResponse = nullptr;
-    otError  error          = OT_ERROR_NOT_FOUND;
+    Error    error          = kErrorNotFound;
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    Option::Iterator iterator;
+    char *           curUriPath         = uriPath;
+    uint8_t          blockOptionType    = 0;
+    uint32_t         totalTransfereSize = 0;
+#endif
 
     if (mInterceptor != nullptr)
     {
@@ -703,28 +1264,128 @@
 
     switch (mResponsesQueue.GetMatchedResponseCopy(aMessage, aMessageInfo, &cachedResponse))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         cachedResponse->Finish();
         error = Send(*cachedResponse, aMessageInfo);
 
-        // fall through
+        OT_FALL_THROUGH;
 
-    case OT_ERROR_NO_BUFS:
+    case kErrorNoBufs:
         ExitNow();
 
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
     default:
         break;
     }
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    SuccessOrExit(error = iterator.Init(aMessage));
+
+    while (!iterator.IsDone())
+    {
+        switch (iterator.GetOption()->GetNumber())
+        {
+        case kOptionUriPath:
+            if (curUriPath != uriPath)
+            {
+                *curUriPath++ = '/';
+            }
+
+            VerifyOrExit(curUriPath + iterator.GetOption()->GetLength() < OT_ARRAY_END(uriPath), error = kErrorParse);
+
+            IgnoreError(iterator.ReadOptionValue(curUriPath));
+            curUriPath += iterator.GetOption()->GetLength();
+            break;
+
+        case kOptionBlock1:
+            blockOptionType += 1;
+            break;
+
+        case kOptionBlock2:
+            blockOptionType += 2;
+            break;
+
+        case kOptionSize1:
+            // ToDo: wait for method to read uint option values
+            totalTransfereSize = 0;
+            break;
+
+        default:
+            break;
+        }
+
+        SuccessOrExit(error = iterator.Advance());
+    }
+
+    curUriPath[0] = '\0';
+
+    for (const ResourceBlockWise *resource = mBlockWiseResources.GetHead(); resource; resource = resource->GetNext())
+    {
+        if (strcmp(resource->GetUriPath(), uriPath) != 0)
+        {
+            continue;
+        }
+
+        if ((resource->mReceiveHook != nullptr || resource->mTransmitHook != nullptr) && blockOptionType != 0)
+        {
+            switch (blockOptionType)
+            {
+            case 1:
+                if (resource->mReceiveHook != nullptr)
+                {
+                    switch (ProcessBlock1Request(aMessage, aMessageInfo, *resource, totalTransfereSize))
+                    {
+                    case kErrorNone:
+                        resource->HandleRequest(aMessage, aMessageInfo);
+                        // Fall through
+                    case kErrorBusy:
+                        error = kErrorNone;
+                        break;
+                    case kErrorNoBufs:
+                        IgnoreReturnValue(SendHeaderResponse(kCodeRequestTooLarge, aMessage, aMessageInfo));
+                        error = kErrorDrop;
+                        break;
+                    case kErrorNoFrameReceived:
+                        IgnoreReturnValue(SendHeaderResponse(kCodeRequestIncomplete, aMessage, aMessageInfo));
+                        error = kErrorDrop;
+                        break;
+                    default:
+                        IgnoreReturnValue(SendHeaderResponse(kCodeInternalError, aMessage, aMessageInfo));
+                        error = kErrorDrop;
+                        break;
+                    }
+                }
+                break;
+            case 2:
+                if (resource->mTransmitHook != nullptr)
+                {
+                    if ((error = ProcessBlock2Request(aMessage, aMessageInfo, *resource)) != kErrorNone)
+                    {
+                        IgnoreReturnValue(SendHeaderResponse(kCodeInternalError, aMessage, aMessageInfo));
+                        error = kErrorDrop;
+                    }
+                }
+                break;
+            }
+            ExitNow();
+        }
+        else
+        {
+            resource->HandleRequest(aMessage, aMessageInfo);
+            error = kErrorNone;
+            ExitNow();
+        }
+    }
+#else
     SuccessOrExit(error = aMessage.ReadUriPathOptions(uriPath));
+#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
 
     for (const Resource *resource = mResources.GetHead(); resource; resource = resource->GetNext())
     {
         if (strcmp(resource->mUriPath, uriPath) == 0)
         {
             resource->HandleRequest(aMessage, aMessageInfo);
-            error = OT_ERROR_NONE;
+            error = kErrorNone;
             ExitNow();
         }
     }
@@ -732,16 +1393,16 @@
     if (mDefaultHandler)
     {
         mDefaultHandler(mDefaultHandlerContext, &aMessage, &aMessageInfo);
-        error = OT_ERROR_NONE;
+        error = kErrorNone;
     }
 
 exit:
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogInfoCoap("Failed to process request: %s", otThreadErrorToString(error));
+        otLogInfoCoap("Failed to process request: %s", ErrorToString(error));
 
-        if (error == OT_ERROR_NOT_FOUND && !aMessageInfo.GetSockAddr().IsMulticast())
+        if (error == kErrorNotFound && !aMessageInfo.GetSockAddr().IsMulticast())
         {
             IgnoreError(SendNotFound(aMessage, aMessageInfo));
         }
@@ -768,18 +1429,18 @@
 {
 }
 
-otError ResponsesQueue::GetMatchedResponseCopy(const Message &         aRequest,
-                                               const Ip6::MessageInfo &aMessageInfo,
-                                               Message **              aResponse)
+Error ResponsesQueue::GetMatchedResponseCopy(const Message &         aRequest,
+                                             const Ip6::MessageInfo &aMessageInfo,
+                                             Message **              aResponse)
 {
-    otError        error = OT_ERROR_NONE;
+    Error          error = kErrorNone;
     const Message *cacheResponse;
 
     cacheResponse = FindMatchedResponse(aRequest, aMessageInfo);
-    VerifyOrExit(cacheResponse != nullptr, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(cacheResponse != nullptr, error = kErrorNotFound);
 
     *aResponse = cacheResponse->Clone(cacheResponse->GetLength() - sizeof(ResponseMetadata));
-    VerifyOrExit(*aResponse != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(*aResponse != nullptr, error = kErrorNoBufs);
 
 exit:
     return error;
@@ -824,7 +1485,7 @@
 
     VerifyOrExit((responseCopy = aMessage.Clone()) != nullptr);
 
-    VerifyOrExit(metadata.AppendTo(*responseCopy) == OT_ERROR_NONE, responseCopy->Free());
+    VerifyOrExit(metadata.AppendTo(*responseCopy) == kErrorNone, responseCopy->Free());
 
     mQueue.Enqueue(*responseCopy);
 
@@ -950,8 +1611,8 @@
         // Calulate exchange lifetime step by step and verify no overflow.
         uint32_t tmp = Multiply(mAckTimeout, (1U << (mMaxRetransmit + 1)) - 1);
 
-        tmp /= mAckRandomFactorDenominator;
         tmp = Multiply(tmp, mAckRandomFactorNumerator);
+        tmp /= mAckRandomFactorDenominator;
 
         rval = (tmp != 0 && (tmp + mAckTimeout + 2 * kDefaultMaxLatency) > tmp);
     }
@@ -995,21 +1656,20 @@
 {
 }
 
-otError Coap::Start(uint16_t aPort, otNetifIdentifier aNetifIdentifier)
+Error Coap::Start(uint16_t aPort, otNetifIdentifier aNetifIdentifier)
 {
-    otError error        = OT_ERROR_NONE;
-    bool    socketOpened = false;
+    Error error        = kErrorNone;
+    bool  socketOpened = false;
 
     VerifyOrExit(!mSocket.IsBound());
 
     SuccessOrExit(error = mSocket.Open(&Coap::HandleUdpReceive, this));
     socketOpened = true;
 
-    SuccessOrExit(error = mSocket.BindToNetif(aNetifIdentifier));
-    SuccessOrExit(error = mSocket.Bind(aPort));
+    SuccessOrExit(error = mSocket.Bind(aPort, aNetifIdentifier));
 
 exit:
-    if (error != OT_ERROR_NONE && socketOpened)
+    if (error != kErrorNone && socketOpened)
     {
         IgnoreError(mSocket.Close());
     }
@@ -1017,9 +1677,9 @@
     return error;
 }
 
-otError Coap::Stop(void)
+Error Coap::Stop(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     VerifyOrExit(mSocket.IsBound());
 
@@ -1036,14 +1696,14 @@
                                            *static_cast<const Ip6::MessageInfo *>(aMessageInfo));
 }
 
-otError Coap::Send(CoapBase &aCoapBase, ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
+Error Coap::Send(CoapBase &aCoapBase, ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
     return static_cast<Coap &>(aCoapBase).Send(aMessage, aMessageInfo);
 }
 
-otError Coap::Send(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
+Error Coap::Send(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    return mSocket.IsBound() ? mSocket.SendTo(aMessage, aMessageInfo) : OT_ERROR_INVALID_STATE;
+    return mSocket.IsBound() ? mSocket.SendTo(aMessage, aMessageInfo) : kErrorInvalidState;
 }
 
 } // namespace Coap
diff --git a/src/core/coap/coap.hpp b/src/core/coap/coap.hpp
index 8a1d983..2092f3c 100644
--- a/src/core/coap/coap.hpp
+++ b/src/core/coap/coap.hpp
@@ -169,13 +169,108 @@
      */
     const char *GetUriPath(void) const { return mUriPath; }
 
-private:
+protected:
     void HandleRequest(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) const
     {
         mHandler(mContext, &aMessage, &aMessageInfo);
     }
 };
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+/**
+ * This class implements CoAP block-wise resource handling.
+ *
+ */
+class ResourceBlockWise : public otCoapBlockwiseResource
+{
+    friend class CoapBase;
+
+public:
+    /**
+     * This constructor initializes the resource.
+     *
+     * @param[in]  aUriPath         A pointer to a NULL-terminated string for the Uri-Path.
+     * @param[in]  aHandler         A function pointer that is called when receiving a CoAP message for @p aUriPath.
+     * @param[in]  aContext         A pointer to arbitrary context information.
+     * @param[in]  aReceiveHook     A function pointer that is called when receiving a CoAP block message for @p
+     *                              aUriPath.
+     * @param[in]  aTransmitHook    A function pointer that is called when transmitting a CoAP block message from @p
+     *                              aUriPath.
+     */
+    ResourceBlockWise(const char *                aUriPath,
+                      otCoapRequestHandler        aHandler,
+                      void *                      aContext,
+                      otCoapBlockwiseReceiveHook  aReceiveHook,
+                      otCoapBlockwiseTransmitHook aTransmitHook)
+    {
+        mUriPath      = aUriPath;
+        mHandler      = aHandler;
+        mContext      = aContext;
+        mReceiveHook  = aReceiveHook;
+        mTransmitHook = aTransmitHook;
+        mNext         = nullptr;
+    }
+
+    Error HandleBlockReceive(const uint8_t *aBlock,
+                             uint32_t       aPosition,
+                             uint16_t       aBlockLength,
+                             bool           aMore,
+                             uint32_t       aTotalLength) const
+    {
+        return mReceiveHook(otCoapBlockwiseResource::mContext, aBlock, aPosition, aBlockLength, aMore, aTotalLength);
+    }
+
+    Error HandleBlockTransmit(uint8_t *aBlock, uint32_t aPosition, uint16_t *aBlockLength, bool *aMore) const
+    {
+        return mTransmitHook(otCoapBlockwiseResource::mContext, aBlock, aPosition, aBlockLength, aMore);
+    }
+
+    /**
+     * This method gets the next entry in the linked list.
+     *
+     * @returns A pointer to the next entry in the linked list or nullptr if at the end of the list.
+     *
+     */
+    const ResourceBlockWise *GetNext(void) const
+    {
+        return static_cast<const ResourceBlockWise *>(static_cast<const ResourceBlockWise *>(this)->mNext);
+    }
+
+    /**
+     * This method gets the next entry in the linked list.
+     *
+     * @returns A pointer to the next entry in the linked list or nullptr if at the end of the list.
+     *
+     */
+    ResourceBlockWise *GetNext(void)
+    {
+        return static_cast<ResourceBlockWise *>(static_cast<ResourceBlockWise *>(this)->mNext);
+    }
+
+    /**
+     * This method sets the next pointer on the entry.
+     *
+     * @param[in] aNext  A pointer to the next entry.
+     *
+     */
+    void SetNext(ResourceBlockWise *aNext) { static_cast<ResourceBlockWise *>(this)->mNext = aNext; }
+
+    /**
+     * This method returns a pointer to the URI path.
+     *
+     * @returns A pointer to the URI path.
+     *
+     */
+    const char *GetUriPath(void) const { return mUriPath; }
+
+protected:
+    void HandleRequest(Message &aMessage, const Ip6::MessageInfo &aMessageInfo) const
+    {
+        mHandler(mContext, &aMessage, &aMessageInfo);
+    }
+};
+#endif
+
 /**
  * This class caches CoAP responses to implement message deduplication.
  *
@@ -219,12 +314,12 @@
      * @param[in]  aMessageInfo  The message info containing source endpoint address and port.
      * @param[out] aResponse     A pointer to return a copy of a cached CoAP response matching given arguments.
      *
-     * @retval OT_ERROR_NONE       Matching response found and successfully created a copy.
-     * @retval OT_ERROR_NO_BUFS    Matching response found but there is not sufficient buffer to create a copy.
-     * @retval OT_ERROR_NOT_FOUND  Matching response not found.
+     * @retval kErrorNone      Matching response found and successfully created a copy.
+     * @retval kErrorNoBufs    Matching response found but there is not sufficient buffer to create a copy.
+     * @retval kErrorNotFound  Matching response not found.
      *
      */
-    otError GetMatchedResponseCopy(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo, Message **aResponse);
+    Error GetMatchedResponseCopy(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo, Message **aResponse);
 
     /**
      * This method gets a reference to the cached CoAP responses queue.
@@ -242,8 +337,8 @@
 
     struct ResponseMetadata
     {
-        otError AppendTo(Message &aMessage) const { return aMessage.Append(*this); }
-        void    ReadFrom(const Message &aMessage);
+        Error AppendTo(Message &aMessage) const { return aMessage.Append(*this); }
+        void  ReadFrom(const Message &aMessage);
 
         TimeMilli        mDequeueTime;
         Ip6::MessageInfo mMessageInfo;
@@ -269,6 +364,12 @@
     friend class ResponsesQueue;
 
 public:
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    enum {
+        kMaxBlockLength = OPENTHREAD_CONFIG_COAP_MAX_BLOCK_LENGTH,
+    };
+#endif
+
     /**
      * This function pointer is called before CoAP server processing a CoAP message.
      *
@@ -276,13 +377,12 @@
      @ @param[in]   aMessageInfo    A reference to the message info associated with @p aMessage.
      * @param[in]   aContext        A pointer to arbitrary context information.
      *
-     * @retval  OT_ERROR_NONE       Server should continue processing this message, other
-     *                              return values indicates the server should stop processing
-     *                              this message.
-     * @retval  OT_ERROR_NOT_TMF    The message is not a TMF message.
+     * @retval  kErrorNone      Server should continue processing this message, other return values indicates the
+     *                          server should stop processing this message.
+     * @retval  kErrorNotTmf    The message is not a TMF message.
      *
      */
-    typedef otError (*Interceptor)(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, void *aContext);
+    typedef Error (*Interceptor)(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, void *aContext);
 
     /**
      * This method clears requests and responses used by this CoAP agent.
@@ -298,6 +398,25 @@
      */
     void ClearRequests(const Ip6::Address &aAddress);
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+
+    /**
+     * This method adds a block-wise resource to the CoAP server.
+     *
+     * @param[in]  aResource  A reference to the resource.
+     *
+     */
+    void AddBlockWiseResource(ResourceBlockWise &aResource);
+
+    /**
+     * This method removes a block-wise resource from the CoAP server.
+     *
+     * @param[in]  aResource  A reference to the resource.
+     *
+     */
+    void RemoveBlockWiseResource(ResourceBlockWise &aResource);
+#endif
+
     /**
      * This method adds a resource to the CoAP server.
      *
@@ -343,6 +462,35 @@
         return NewMessage(Message::Settings(Message::kWithLinkSecurity, Message::kPriorityNet));
     }
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    /**
+     * This method sends a CoAP message block-wise with custom transmission parameters.
+     *
+     * If a response for a request is expected, respective function and context information should be provided.
+     * If no response is expected, these arguments should be NULL pointers.
+     * If Message Id was not set in the header (equal to 0), this function will assign unique Message Id to the message.
+     *
+     * @param[in]  aMessage      A reference to the message to send.
+     * @param[in]  aMessageInfo  A reference to the message info associated with @p aMessage.
+     * @param[in]  aTxParameters A reference to transmission parameters for this message.
+     * @param[in]  aHandler      A function pointer that shall be called on response reception or time-out.
+     * @param[in]  aContext      A pointer to arbitrary context information.
+     * @param[in]  aTransmitHook A pointer to a hook function for outgoing block-wise transfer.
+     * @param[in]  aReceiveHook  A pointer to a hook function for incoming block-wise transfer.
+     *
+     * @retval kErrorNone     Successfully sent CoAP message.
+     * @retval kErrorNoBufs   Failed to allocate retransmission data.
+     *
+     */
+    Error SendMessage(Message &                   aMessage,
+                      const Ip6::MessageInfo &    aMessageInfo,
+                      const TxParameters &        aTxParameters,
+                      otCoapResponseHandler       aHandler      = nullptr,
+                      void *                      aContext      = nullptr,
+                      otCoapBlockwiseTransmitHook aTransmitHook = nullptr,
+                      otCoapBlockwiseReceiveHook  aReceiveHook  = nullptr);
+#else  // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+
     /**
      * This method sends a CoAP message with custom transmission parameters.
      *
@@ -356,15 +504,16 @@
      * @param[in]  aHandler      A function pointer that shall be called on response reception or time-out.
      * @param[in]  aContext      A pointer to arbitrary context information.
      *
-     * @retval OT_ERROR_NONE     Successfully sent CoAP message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to send the CoAP message.
+     * @retval kErrorNone    Successfully sent CoAP message.
+     * @retval kErrorNoBufs  Insufficient buffers available to send the CoAP message.
      *
      */
-    otError SendMessage(Message &               aMessage,
-                        const Ip6::MessageInfo &aMessageInfo,
-                        const TxParameters &    aTxParameters,
-                        ResponseHandler         aHandler = nullptr,
-                        void *                  aContext = nullptr);
+    Error SendMessage(Message &               aMessage,
+                      const Ip6::MessageInfo &aMessageInfo,
+                      const TxParameters &    aTxParameters,
+                      ResponseHandler         aHandler = nullptr,
+                      void *                  aContext = nullptr);
+#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
 
     /**
      * This method sends a CoAP message with default transmission parameters.
@@ -378,14 +527,14 @@
      * @param[in]  aHandler      A function pointer that shall be called on response reception or time-out.
      * @param[in]  aContext      A pointer to arbitrary context information.
      *
-     * @retval OT_ERROR_NONE     Successfully sent CoAP message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to send the CoAP response.
+     * @retval kErrorNone    Successfully sent CoAP message.
+     * @retval kErrorNoBufs  Insufficient buffers available to send the CoAP response.
      *
      */
-    otError SendMessage(Message &               aMessage,
-                        const Ip6::MessageInfo &aMessageInfo,
-                        ResponseHandler         aHandler = nullptr,
-                        void *                  aContext = nullptr);
+    Error SendMessage(Message &               aMessage,
+                      const Ip6::MessageInfo &aMessageInfo,
+                      ResponseHandler         aHandler = nullptr,
+                      void *                  aContext = nullptr);
 
     /**
      * This method sends a CoAP reset message.
@@ -393,12 +542,12 @@
      * @param[in]  aRequest        A reference to the CoAP Message that was used in CoAP request.
      * @param[in]  aMessageInfo    The message info corresponding to the CoAP request.
      *
-     * @retval OT_ERROR_NONE          Successfully enqueued the CoAP response message.
-     * @retval OT_ERROR_NO_BUFS       Insufficient buffers available to send the CoAP response.
-     * @retval OT_ERROR_INVALID_ARGS  The @p aRequest is not of confirmable type.
+     * @retval kErrorNone          Successfully enqueued the CoAP response message.
+     * @retval kErrorNoBufs        Insufficient buffers available to send the CoAP response.
+     * @retval kErrorInvalidArgs   The @p aRequest is not of confirmable type.
      *
      */
-    otError SendReset(Message &aRequest, const Ip6::MessageInfo &aMessageInfo);
+    Error SendReset(Message &aRequest, const Ip6::MessageInfo &aMessageInfo);
 
     /**
      * This method sends header-only CoAP response message.
@@ -407,12 +556,12 @@
      * @param[in]  aRequest        A reference to the CoAP Message that was used in CoAP request.
      * @param[in]  aMessageInfo    The message info corresponding to the CoAP request.
      *
-     * @retval OT_ERROR_NONE          Successfully enqueued the CoAP response message.
-     * @retval OT_ERROR_NO_BUFS       Insufficient buffers available to send the CoAP response.
-     * @retval OT_ERROR_INVALID_ARGS  The @p aRequest header is not of confirmable type.
+     * @retval kErrorNone          Successfully enqueued the CoAP response message.
+     * @retval kErrorNoBufs        Insufficient buffers available to send the CoAP response.
+     * @retval kErrorInvalidArgs   The @p aRequest header is not of confirmable type.
      *
      */
-    otError SendHeaderResponse(Message::Code aCode, const Message &aRequest, const Ip6::MessageInfo &aMessageInfo);
+    Error SendHeaderResponse(Message::Code aCode, const Message &aRequest, const Ip6::MessageInfo &aMessageInfo);
 
     /**
      * This method sends a CoAP ACK empty message which is used in Separate Response for confirmable requests.
@@ -420,12 +569,12 @@
      * @param[in]  aRequest        A reference to the CoAP Message that was used in CoAP request.
      * @param[in]  aMessageInfo    The message info corresponding to the CoAP request.
      *
-     * @retval OT_ERROR_NONE          Successfully enqueued the CoAP response message.
-     * @retval OT_ERROR_NO_BUFS       Insufficient buffers available to send the CoAP response.
-     * @retval OT_ERROR_INVALID_ARGS  The @p aRequest header is not of confirmable type.
+     * @retval kErrorNone          Successfully enqueued the CoAP response message.
+     * @retval kErrorNoBufs        Insufficient buffers available to send the CoAP response.
+     * @retval kErrorInvalidArgs   The @p aRequest header is not of confirmable type.
      *
      */
-    otError SendAck(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo);
+    Error SendAck(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo);
 
     /**
      * This method sends a CoAP ACK message on which a dummy CoAP response is piggybacked.
@@ -434,38 +583,56 @@
      * @param[in]  aMessageInfo    The message info corresponding to the CoAP request.
      * @param[in]  aCode           The CoAP code of the dummy CoAP response.
      *
-     * @retval OT_ERROR_NONE          Successfully enqueued the CoAP response message.
-     * @retval OT_ERROR_NO_BUFS       Insufficient buffers available to send the CoAP response.
-     * @retval OT_ERROR_INVALID_ARGS  The @p aRequest header is not of confirmable type.
+     * @retval kErrorNone          Successfully enqueued the CoAP response message.
+     * @retval kErrorNoBufs        Insufficient buffers available to send the CoAP response.
+     * @retval kErrorInvalidArgs   The @p aRequest header is not of confirmable type.
      *
      */
-    otError SendEmptyAck(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo, Code aCode = kCodeChanged);
+    Error SendEmptyAck(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo, Code aCode = kCodeChanged);
 
     /**
      * This method sends a header-only CoAP message to indicate no resource matched for the request.
      *
      * @param[in]  aRequest        A reference to the CoAP Message that was used in CoAP request.
-     * @param[in]  aMessageInfo          The message info corresponding to the CoAP request.
+     * @param[in]  aMessageInfo    The message info corresponding to the CoAP request.
      *
-     * @retval OT_ERROR_NONE         Successfully enqueued the CoAP response message.
-     * @retval OT_ERROR_NO_BUFS      Insufficient buffers available to send the CoAP response.
+     * @retval kErrorNone          Successfully enqueued the CoAP response message.
+     * @retval kErrorNoBufs        Insufficient buffers available to send the CoAP response.
      *
      */
-    otError SendNotFound(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo);
+    Error SendNotFound(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo);
+
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    /**
+     * This method sends a header-only CoAP message to indicate not all blocks have been sent or
+     * were sent out of order.
+     *
+     * @param[in]  aRequest        A reference to the CoAP Message that was used in CoAP request.
+     * @param[in]  aMessageInfo    The message info corresponding to the CoAP request.
+     *
+     * @retval kErrorNone          Successfully enqueued the CoAP response message.
+     * @retval kErrorNoBufs        Insufficient buffers available to send the CoAP response.
+     *
+     */
+    Error SendRequestEntityIncomplete(const Message &aRequest, const Ip6::MessageInfo &aMessageInfo)
+    {
+        return SendHeaderResponse(kCodeRequestIncomplete, aRequest, aMessageInfo);
+    }
+#endif
 
     /**
      * This method aborts CoAP transactions associated with given handler and context.
      *
-     * The associated response handler will be called with OT_ERROR_ABORT.
+     * The associated response handler will be called with kErrorAbort.
      *
      * @param[in]  aHandler  A function pointer that should be called when the transaction ends.
      * @param[in]  aContext  A pointer to arbitrary context information.
      *
-     * @retval OT_ERROR_NONE       Successfully aborted CoAP transactions.
-     * @retval OT_ERROR_NOT_FOUND  CoAP transaction associated with given handler was not found.
+     * @retval kErrorNone      Successfully aborted CoAP transactions.
+     * @retval kErrorNotFound  CoAP transaction associated with given handler was not found.
      *
      */
-    otError AbortTransaction(ResponseHandler aHandler, void *aContext);
+    Error AbortTransaction(ResponseHandler aHandler, void *aContext);
 
     /**
      * This method sets interceptor to be called before processing a CoAP packet.
@@ -500,11 +667,11 @@
      * @param[in]  aMessage      A reference to the message to send.
      * @param[in]  aMessageInfo  A reference to the message info associated with @p aMessage.
      *
-     * @retval OT_ERROR_NONE     Successfully sent CoAP message.
-     * @retval OT_ERROR_NO_BUFS  Failed to allocate retransmission data.
+     * @retval kErrorNone    Successfully sent CoAP message.
+     * @retval kErrorNoBufs  Failed to allocate retransmission data.
      *
      */
-    typedef otError (*Sender)(CoapBase &aCoapBase, ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    typedef Error (*Sender)(CoapBase &aCoapBase, ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
 
     /**
      * This constructor initializes the object.
@@ -528,9 +695,9 @@
 private:
     struct Metadata
     {
-        otError AppendTo(Message &aMessage) const { return aMessage.Append(*this); }
-        void    ReadFrom(const Message &aMessage);
-        void    UpdateIn(Message &aMessage) const;
+        Error AppendTo(Message &aMessage) const { return aMessage.Append(*this); }
+        void  ReadFrom(const Message &aMessage);
+        void  UpdateIn(Message &aMessage) const;
 
         Ip6::Address    mSourceAddress;            // IPv6 address of the message source.
         Ip6::Address    mDestinationAddress;       // IPv6 address of the message destination.
@@ -552,6 +719,12 @@
 #if OPENTHREAD_CONFIG_COAP_OBSERVE_API_ENABLE
         bool mObserve : 1; // Information that this request involves Observations.
 #endif
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        otCoapBlockwiseReceiveHook mBlockwiseReceiveHook;   // A function pointer that is called on Block2
+                                                            // response reception.
+        otCoapBlockwiseTransmitHook mBlockwiseTransmitHook; // A function pointer that is called on Block1
+                                                            // response reception.
+#endif
     };
 
     static void HandleRetransmissionTimer(Timer &aTimer);
@@ -565,15 +738,44 @@
                                      const Metadata &        aMetadata,
                                      Message *               aResponse,
                                      const Ip6::MessageInfo *aMessageInfo,
-                                     otError                 aResult);
+                                     Error                   aResult);
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    void  FreeLastBlockResponse(void);
+    Error CacheLastBlockResponse(Message *aResponse);
+
+    Error PrepareNextBlockRequest(Message::BlockType aType,
+                                  bool               aMoreBlocks,
+                                  Message &          aRequestOld,
+                                  Message &          aRequest,
+                                  Message &          aMessage);
+    Error ProcessBlock1Request(Message &                aMessage,
+                               const Ip6::MessageInfo & aMessageInfo,
+                               const ResourceBlockWise &aResource,
+                               uint32_t                 aTotalLength);
+    Error ProcessBlock2Request(Message &                aMessage,
+                               const Ip6::MessageInfo & aMessageInfo,
+                               const ResourceBlockWise &aResource);
+#endif
     void ProcessReceivedRequest(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
     void ProcessReceivedResponse(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
 
-    void    SendCopy(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
-    otError SendEmptyMessage(Type aType, const Message &aRequest, const Ip6::MessageInfo &aMessageInfo);
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    Error SendNextBlock1Request(Message &               aRequest,
+                                Message &               aMessage,
+                                const Ip6::MessageInfo &aMessageInfo,
+                                const Metadata &        aCoapMetadata);
+    Error SendNextBlock2Request(Message &               aRequest,
+                                Message &               aMessage,
+                                const Ip6::MessageInfo &aMessageInfo,
+                                const Metadata &        aCoapMetadata,
+                                uint32_t                aTotalLength,
+                                bool                    aBeginBlock1Transfer);
+#endif
+    void  SendCopy(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    Error SendEmptyMessage(Type aType, const Message &aRequest, const Ip6::MessageInfo &aMessageInfo);
 
-    otError Send(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    Error Send(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
 
     MessageQueue      mPendingRequests;
     uint16_t          mMessageId;
@@ -589,6 +791,11 @@
     void *         mDefaultHandlerContext;
 
     const Sender mSender;
+
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    LinkedList<ResourceBlockWise> mBlockWiseResources;
+    Message *                     mLastResponse;
+#endif
 };
 
 /**
@@ -612,28 +819,28 @@
      * @param[in]  aPort             The local UDP port to bind to.
      * @param[in]  aNetifIdentifier  The network interface identifier to bind.
      *
-     * @retval OT_ERROR_NONE    Successfully started the CoAP service.
-     * @retval OT_ERROR_FAILED  Failed to start CoAP agent.
+     * @retval kErrorNone    Successfully started the CoAP service.
+     * @retval kErrorFailed  Failed to start CoAP agent.
      *
      */
-    otError Start(uint16_t aPort, otNetifIdentifier aNetifIdentifier = OT_NETIF_UNSPECIFIED);
+    Error Start(uint16_t aPort, otNetifIdentifier aNetifIdentifier = OT_NETIF_UNSPECIFIED);
 
     /**
      * This method stops the CoAP service.
      *
-     * @retval OT_ERROR_NONE    Successfully stopped the CoAP service.
-     * @retval OT_ERROR_FAILED  Failed to stop CoAP agent.
+     * @retval kErrorNone    Successfully stopped the CoAP service.
+     * @retval kErrorFailed  Failed to stop CoAP agent.
      *
      */
-    otError Stop(void);
+    Error Stop(void);
 
 protected:
     Ip6::Udp::Socket mSocket;
 
 private:
-    static otError Send(CoapBase &aCoapBase, ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
-    static void    HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
-    otError        Send(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    static Error Send(CoapBase &aCoapBase, ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    static void  HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
+    Error        Send(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
 };
 
 } // namespace Coap
diff --git a/src/core/coap/coap_message.cpp b/src/core/coap/coap_message.cpp
index 5ca789e..41a001d 100644
--- a/src/core/coap/coap_message.cpp
+++ b/src/core/coap/coap_message.cpp
@@ -51,6 +51,11 @@
     GetHelpData().mHeaderLength = kMinHeaderLength;
 
     IgnoreError(SetLength(GetHelpData().mHeaderLength));
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    SetBlockWiseBlockNumber(0);
+    SetMoreBlocksFlag(false);
+    SetBlockWiseBlockSize(OT_COAP_OPTION_BLOCK_SZX_16);
+#endif
 }
 
 void Message::Init(Type aType, Code aCode)
@@ -60,9 +65,9 @@
     SetCode(aCode);
 }
 
-otError Message::Init(Type aType, Code aCode, const char *aUriPath)
+Error Message::Init(Type aType, Code aCode, const char *aUriPath)
 {
-    otError error;
+    Error error;
 
     Init(aType, aCode);
     SuccessOrExit(error = GenerateRandomToken(kDefaultTokenLength));
@@ -82,17 +87,17 @@
     Init(kTypeNonConfirmable, kCodePost);
 }
 
-otError Message::InitAsConfirmablePost(const char *aUriPath)
+Error Message::InitAsConfirmablePost(const char *aUriPath)
 {
     return Init(kTypeConfirmable, kCodePost, aUriPath);
 }
 
-otError Message::InitAsNonConfirmablePost(const char *aUriPath)
+Error Message::InitAsNonConfirmablePost(const char *aUriPath)
 {
     return Init(kTypeNonConfirmable, kCodePost, aUriPath);
 }
 
-otError Message::InitAsPost(const Ip6::Address &aDestination, const char *aUriPath)
+Error Message::InitAsPost(const Ip6::Address &aDestination, const char *aUriPath)
 {
     return Init(aDestination.IsMulticast() ? kTypeNonConfirmable : kTypeConfirmable, kCodePost, aUriPath);
 }
@@ -155,15 +160,15 @@
     return rval;
 }
 
-otError Message::AppendOption(uint16_t aNumber, uint16_t aLength, const void *aValue)
+Error Message::AppendOption(uint16_t aNumber, uint16_t aLength, const void *aValue)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     uint16_t delta;
     uint8_t  header[kMaxOptionHeaderSize];
     uint16_t headerLength;
     uint8_t *cur;
 
-    VerifyOrExit(aNumber >= GetHelpData().mOptionLast, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aNumber >= GetHelpData().mOptionLast, error = kErrorInvalidArgs);
     delta = aNumber - GetHelpData().mOptionLast;
 
     cur = &header[1];
@@ -173,8 +178,7 @@
 
     headerLength = static_cast<uint16_t>(cur - header);
 
-    VerifyOrExit(static_cast<uint32_t>(GetLength()) + headerLength + aLength < kMaxHeaderLength,
-                 error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(static_cast<uint32_t>(GetLength()) + headerLength + aLength < kMaxHeaderLength, error = kErrorNoBufs);
 
     SuccessOrExit(error = AppendBytes(header, headerLength));
     SuccessOrExit(error = AppendBytes(aValue, aLength));
@@ -187,7 +191,7 @@
     return error;
 }
 
-otError Message::AppendUintOption(uint16_t aNumber, uint32_t aValue)
+Error Message::AppendUintOption(uint16_t aNumber, uint32_t aValue)
 {
     uint8_t        buffer[sizeof(uint32_t)];
     const uint8_t *value  = &buffer[0];
@@ -195,7 +199,7 @@
 
     Encoding::BigEndian::WriteUint32(aValue, buffer);
 
-    while (value[0] == 0 && length > 0)
+    while ((length > 0) && (value[0] == 0))
     {
         value++;
         length--;
@@ -204,14 +208,14 @@
     return AppendOption(aNumber, length, value);
 }
 
-otError Message::AppendStringOption(uint16_t aNumber, const char *aValue)
+Error Message::AppendStringOption(uint16_t aNumber, const char *aValue)
 {
     return AppendOption(aNumber, static_cast<uint16_t>(strlen(aValue)), aValue);
 }
 
-otError Message::AppendUriPathOptions(const char *aUriPath)
+Error Message::AppendUriPathOptions(const char *aUriPath)
 {
-    otError     error = OT_ERROR_NONE;
+    Error       error = kErrorNone;
     const char *cur   = aUriPath;
     const char *end;
 
@@ -227,10 +231,10 @@
     return error;
 }
 
-otError Message::ReadUriPathOptions(char (&aUriPath)[kMaxReceivedUriPath + 1]) const
+Error Message::ReadUriPathOptions(char (&aUriPath)[kMaxReceivedUriPath + 1]) const
 {
     char *           curUriPath = aUriPath;
-    otError          error      = OT_ERROR_NONE;
+    Error            error      = kErrorNone;
     Option::Iterator iterator;
 
     SuccessOrExit(error = iterator.Init(*this, kOptionUriPath));
@@ -244,7 +248,7 @@
             *curUriPath++ = '/';
         }
 
-        VerifyOrExit(curUriPath + optionLength < OT_ARRAY_END(aUriPath), error = OT_ERROR_PARSE);
+        VerifyOrExit(curUriPath + optionLength < OT_ARRAY_END(aUriPath), error = kErrorParse);
 
         IgnoreError(iterator.ReadOptionValue(curUriPath));
         curUriPath += optionLength;
@@ -258,14 +262,14 @@
     return error;
 }
 
-otError Message::AppendBlockOption(Message::BlockType aType, uint32_t aNum, bool aMore, otCoapBlockSize aSize)
+Error Message::AppendBlockOption(Message::BlockType aType, uint32_t aNum, bool aMore, otCoapBlockSzx aSize)
 {
-    otError  error   = OT_ERROR_NONE;
+    Error    error   = kErrorNone;
     uint32_t encoded = aSize;
 
-    VerifyOrExit(aType == kBlockType1 || aType == kBlockType2, error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit(aSize <= OT_COAP_BLOCK_SIZE_1024, error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit(aNum < kBlockNumMax, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aType == kBlockType1 || aType == kBlockType2, error = kErrorInvalidArgs);
+    VerifyOrExit(aSize <= OT_COAP_OPTION_BLOCK_SZX_1024, error = kErrorInvalidArgs);
+    VerifyOrExit(aNum < kBlockNumMax, error = kErrorInvalidArgs);
 
     encoded |= static_cast<uint32_t>(aMore << kBlockMOffset);
     encoded |= aNum << kBlockNumOffset;
@@ -276,12 +280,55 @@
     return error;
 }
 
-otError Message::SetPayloadMarker(void)
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+Error Message::ReadBlockOptionValues(uint16_t aBlockType)
 {
-    otError error  = OT_ERROR_NONE;
+    Error            error                     = kErrorNone;
+    uint8_t          buf[kMaxOptionHeaderSize] = {0};
+    Option::Iterator iterator;
+
+    VerifyOrExit((aBlockType == kOptionBlock1) || (aBlockType == kOptionBlock2), error = kErrorInvalidArgs);
+
+    SuccessOrExit(error = iterator.Init(*this, aBlockType));
+    SuccessOrExit(error = iterator.ReadOptionValue(buf));
+
+    SetBlockWiseBlockNumber(0);
+    SetMoreBlocksFlag(false);
+
+    switch (iterator.GetOption()->GetLength())
+    {
+    case 0:
+    case 1:
+        SetBlockWiseBlockNumber(static_cast<uint32_t>((buf[0] & 0xf0) >> 4));
+        SetMoreBlocksFlag(static_cast<bool>((buf[0] & 0x08) >> 3 == 1));
+        SetBlockWiseBlockSize(static_cast<otCoapBlockSzx>(buf[0] & 0x07));
+        break;
+    case 2:
+        SetBlockWiseBlockNumber(static_cast<uint32_t>((buf[0] << 4) + ((buf[1] & 0xf0) >> 4)));
+        SetMoreBlocksFlag(static_cast<bool>((buf[1] & 0x08) >> 3 == 1));
+        SetBlockWiseBlockSize(static_cast<otCoapBlockSzx>(buf[1] & 0x07));
+        break;
+    case 3:
+        SetBlockWiseBlockNumber(static_cast<uint32_t>((buf[0] << 12) + (buf[1] << 4) + ((buf[2] & 0xf0) >> 4)));
+        SetMoreBlocksFlag(static_cast<bool>((buf[2] & 0x08) >> 3 == 1));
+        SetBlockWiseBlockSize(static_cast<otCoapBlockSzx>(buf[2] & 0x07));
+        break;
+    default:
+        error = kErrorInvalidArgs;
+        break;
+    }
+
+exit:
+    return error;
+}
+#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+
+Error Message::SetPayloadMarker(void)
+{
+    Error   error  = kErrorNone;
     uint8_t marker = kPayloadMarker;
 
-    VerifyOrExit(GetLength() < kMaxHeaderLength, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(GetLength() < kMaxHeaderLength, error = kErrorNoBufs);
     SuccessOrExit(error = Append(marker));
     GetHelpData().mHeaderLength = GetLength();
 
@@ -292,9 +339,9 @@
     return error;
 }
 
-otError Message::ParseHeader(void)
+Error Message::ParseHeader(void)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     Option::Iterator iterator;
 
     OT_ASSERT(mBuffer.mHead.mMetadata.mReserved >=
@@ -306,7 +353,7 @@
     GetHelpData().mHeaderOffset = GetOffset();
     IgnoreError(Read(GetHelpData().mHeaderOffset, GetHelpData().mHeader));
 
-    VerifyOrExit(GetTokenLength() <= kMaxTokenLength, error = OT_ERROR_PARSE);
+    VerifyOrExit(GetTokenLength() <= kMaxTokenLength, error = kErrorParse);
 
     SuccessOrExit(error = iterator.Init(*this));
 
@@ -322,7 +369,7 @@
     return error;
 }
 
-otError Message::SetToken(const uint8_t *aToken, uint8_t aTokenLength)
+Error Message::SetToken(const uint8_t *aToken, uint8_t aTokenLength)
 {
     OT_ASSERT(aTokenLength <= kMaxTokenLength);
 
@@ -333,7 +380,7 @@
     return SetLength(GetHelpData().mHeaderLength);
 }
 
-otError Message::GenerateRandomToken(uint8_t aTokenLength)
+Error Message::GenerateRandomToken(uint8_t aTokenLength)
 {
     uint8_t token[kMaxTokenLength];
 
@@ -344,7 +391,7 @@
     return SetToken(token, aTokenLength);
 }
 
-otError Message::SetTokenFromMessage(const Message &aMessage)
+Error Message::SetTokenFromMessage(const Message &aMessage)
 {
     return SetToken(aMessage.GetToken(), aMessage.GetTokenLength());
 }
@@ -356,7 +403,7 @@
     return ((tokenLength == aMessage.GetTokenLength()) && (memcmp(GetToken(), aMessage.GetToken(), tokenLength) == 0));
 }
 
-otError Message::SetDefaultResponseHeader(const Message &aRequest)
+Error Message::SetDefaultResponseHeader(const Message &aRequest)
 {
     Init(kTypeAck, kCodeChanged);
 
@@ -477,9 +524,9 @@
 }
 #endif // OPENTHREAD_CONFIG_COAP_API_ENABLE
 
-otError Option::Iterator::Init(const Message &aMessage)
+Error Option::Iterator::Init(const Message &aMessage)
 {
-    otError  error  = OT_ERROR_PARSE;
+    Error    error  = kErrorParse;
     uint32_t offset = static_cast<uint32_t>(aMessage.GetHelpData().mHeaderOffset) + aMessage.GetOptionStart();
 
     // Note that the case where `offset == aMessage.GetLength())` is
@@ -499,9 +546,9 @@
     return error;
 }
 
-otError Option::Iterator::Advance(void)
+Error Option::Iterator::Advance(void)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     uint8_t  headerByte;
     uint16_t optionDelta;
     uint16_t optionLength;
@@ -510,22 +557,22 @@
 
     error = Read(sizeof(uint8_t), &headerByte);
 
-    if ((error != OT_ERROR_NONE) || (headerByte == Message::kPayloadMarker))
+    if ((error != kErrorNone) || (headerByte == Message::kPayloadMarker))
     {
         // Payload Marker indicates end of options and start of payload.
         // Absence of a Payload Marker indicates a zero-length payload.
 
         MarkAsDone();
 
-        if (error == OT_ERROR_NONE)
+        if (error == kErrorNone)
         {
             // The presence of a marker followed by a zero-length payload
             // MUST be processed as a message format error.
 
-            VerifyOrExit(mNextOptionOffset < GetMessage().GetLength(), error = OT_ERROR_PARSE);
+            VerifyOrExit(mNextOptionOffset < GetMessage().GetLength(), error = kErrorParse);
         }
 
-        ExitNow(error = OT_ERROR_NONE);
+        ExitNow(error = kErrorNone);
     }
 
     optionDelta = (headerByte & Message::kOptionDeltaMask) >> Message::kOptionDeltaOffset;
@@ -534,14 +581,14 @@
     optionLength = (headerByte & Message::kOptionLengthMask) >> Message::kOptionLengthOffset;
     SuccessOrExit(error = ReadExtendedOptionField(optionLength));
 
-    VerifyOrExit(optionLength <= GetMessage().GetLength() - mNextOptionOffset, error = OT_ERROR_PARSE);
+    VerifyOrExit(optionLength <= GetMessage().GetLength() - mNextOptionOffset, error = kErrorParse);
     mNextOptionOffset += optionLength;
 
     mOption.mNumber += optionDelta;
     mOption.mLength = optionLength;
 
 exit:
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         MarkAsParseErrored();
     }
@@ -549,25 +596,25 @@
     return error;
 }
 
-otError Option::Iterator::ReadOptionValue(void *aValue) const
+Error Option::Iterator::ReadOptionValue(void *aValue) const
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(!IsDone(), error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(!IsDone(), error = kErrorNotFound);
     GetMessage().ReadBytes(mNextOptionOffset - mOption.mLength, aValue, mOption.mLength);
 
 exit:
     return error;
 }
 
-otError Option::Iterator::ReadOptionValue(uint64_t &aUintValue) const
+Error Option::Iterator::ReadOptionValue(uint64_t &aUintValue) const
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     uint8_t buffer[sizeof(uint64_t)];
 
-    VerifyOrExit(!IsDone(), error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(!IsDone(), error = kErrorNotFound);
 
-    VerifyOrExit(mOption.mLength <= sizeof(uint64_t), error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(mOption.mLength <= sizeof(uint64_t), error = kErrorNoBufs);
     IgnoreError(ReadOptionValue(buffer));
 
     aUintValue = 0;
@@ -582,13 +629,13 @@
     return error;
 }
 
-otError Option::Iterator::Read(uint16_t aLength, void *aBuffer)
+Error Option::Iterator::Read(uint16_t aLength, void *aBuffer)
 {
     // Reads `aLength` bytes from the message into `aBuffer` at
     // `mNextOptionOffset` and updates the `mNextOptionOffset` on a
     // successful read (i.e., when entire `aLength` bytes can be read).
 
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     SuccessOrExit(error = GetMessage().Read(mNextOptionOffset, aBuffer, aLength));
     mNextOptionOffset += aLength;
@@ -597,9 +644,9 @@
     return error;
 }
 
-otError Option::Iterator::ReadExtendedOptionField(uint16_t &aValue)
+Error Option::Iterator::ReadExtendedOptionField(uint16_t &aValue)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     VerifyOrExit(aValue >= Message::kOption1ByteExtension);
 
@@ -620,18 +667,18 @@
     }
     else
     {
-        error = OT_ERROR_PARSE;
+        error = kErrorParse;
     }
 
 exit:
     return error;
 }
 
-otError Option::Iterator::InitOrAdvance(const Message *aMessage, uint16_t aNumber)
+Error Option::Iterator::InitOrAdvance(const Message *aMessage, uint16_t aNumber)
 {
-    otError error = (aMessage != nullptr) ? Init(*aMessage) : Advance();
+    Error error = (aMessage != nullptr) ? Init(*aMessage) : Advance();
 
-    while ((error == OT_ERROR_NONE) && !IsDone() && (GetOption()->GetNumber() != aNumber))
+    while ((error == kErrorNone) && !IsDone() && (GetOption()->GetNumber() != aNumber))
     {
         error = Advance();
     }
diff --git a/src/core/coap/coap_message.hpp b/src/core/coap/coap_message.hpp
index b8580ba..2fd59da 100644
--- a/src/core/coap/coap_message.hpp
+++ b/src/core/coap/coap_message.hpp
@@ -149,6 +149,7 @@
     kOptionLocationQuery = OT_COAP_OPTION_LOCATION_QUERY, ///< Location-Query
     kOptionBlock2        = OT_COAP_OPTION_BLOCK2,         ///< Block2 (RFC7959)
     kOptionBlock1        = OT_COAP_OPTION_BLOCK1,         ///< Block1 (RFC7959)
+    kOptionSize2         = OT_COAP_OPTION_SIZE2,          ///< Size2 (RFC7959)
     kOptionProxyUri      = OT_COAP_OPTION_PROXY_URI,      ///< Proxy-Uri
     kOptionProxyScheme   = OT_COAP_OPTION_PROXY_SCHEME,   ///< Proxy-Scheme
     kOptionSize1         = OT_COAP_OPTION_SIZE1,          ///< Size1
@@ -222,33 +223,33 @@
      * @param[in]  aCode              The Code value.
      * @param[in]  aUriPath           A pointer to a null-terminated string.
      *
-     * @retval OT_ERROR_NONE          Successfully appended the option.
-     * @retval OT_ERROR_NO_BUFS       The option length exceeds the buffer size.
+     * @retval kErrorNone         Successfully appended the option.
+     * @retval kErrorNoBufs       The option length exceeds the buffer size.
      *
      */
-    otError Init(Type aType, Code aCode, const char *aUriPath);
+    Error Init(Type aType, Code aCode, const char *aUriPath);
 
     /**
      * This method initializes the CoAP header as `kTypeConfirmable` and `kCodePost` with a given URI Path.
      *
      * @param[in]  aUriPath           A pointer to a null-terminated string.
      *
-     * @retval OT_ERROR_NONE          Successfully appended the option.
-     * @retval OT_ERROR_NO_BUFS       The option length exceeds the buffer size.
+     * @retval kErrorNone         Successfully appended the option.
+     * @retval kErrorNoBufs       The option length exceeds the buffer size.
      *
      */
-    otError InitAsConfirmablePost(const char *aUriPath);
+    Error InitAsConfirmablePost(const char *aUriPath);
 
     /**
      * This method initializes the CoAP header as `kTypeNonConfirmable` and `kCodePost` with a given URI Path.
      *
      * @param[in]  aUriPath           A pointer to a null-terminated string.
      *
-     * @retval OT_ERROR_NONE          Successfully appended the option.
-     * @retval OT_ERROR_NO_BUFS       The option length exceeds the buffer size.
+     * @retval kErrorNone         Successfully appended the option.
+     * @retval kErrorNoBufs       The option length exceeds the buffer size.
      *
      */
-    otError InitAsNonConfirmablePost(const char *aUriPath);
+    Error InitAsNonConfirmablePost(const char *aUriPath);
 
     /**
      * This method initializes the CoAP header as `kCodePost` with a given URI Path with its type determined from a
@@ -258,11 +259,11 @@
      *                                `kTypeNonConfirmable` if multicast address, `kTypeConfirmable` otherwise.
      * @param[in]  aUriPath           A pointer to a null-terminated string.
      *
-     * @retval OT_ERROR_NONE          Successfully appended the option.
-     * @retval OT_ERROR_NO_BUFS       The option length exceeds the buffer size.
+     * @retval kErrorNone         Successfully appended the option.
+     * @retval kErrorNoBufs       The option length exceeds the buffer size.
      *
      */
-    otError InitAsPost(const Ip6::Address &aDestination, const char *aUriPath);
+    Error InitAsPost(const Ip6::Address &aDestination, const char *aUriPath);
 
     /**
      * This method writes header to the message. This must be called before sending the message.
@@ -380,33 +381,33 @@
      * @param[in]  aToken        A pointer to the Token value.
      * @param[in]  aTokenLength  The Length of @p aToken.
      *
-     * @retval OT_ERROR_NONE     Successfully set the token value.
-     * @retval OT_ERROR_NO_BUFS  Insufficient message buffers available to set the token value.
+     * @retval kErrorNone    Successfully set the token value.
+     * @retval kErrorNoBufs  Insufficient message buffers available to set the token value.
      *
      */
-    otError SetToken(const uint8_t *aToken, uint8_t aTokenLength);
+    Error SetToken(const uint8_t *aToken, uint8_t aTokenLength);
 
     /**
      * This method sets the Token value and length by copying it from another given message.
      *
      * @param[in] aMessage       The message to copy the Token from.
      *
-     * @retval OT_ERROR_NONE     Successfully set the token value.
-     * @retval OT_ERROR_NO_BUFS  Insufficient message buffers available to set the token value.
+     * @retval kErrorNone    Successfully set the token value.
+     * @retval kErrorNoBufs  Insufficient message buffers available to set the token value.
      *
      */
-    otError SetTokenFromMessage(const Message &aMessage);
+    Error SetTokenFromMessage(const Message &aMessage);
 
     /**
      * This method sets the Token length and randomizes its value.
      *
      * @param[in]  aTokenLength  The Length of a Token to set.
      *
-     * @retval OT_ERROR_NONE     Successfully set the token value.
-     * @retval OT_ERROR_NO_BUFS  Insufficient message buffers available to set the token value.
+     * @retval kErrorNone    Successfully set the token value.
+     * @retval kErrorNoBufs  Insufficient message buffers available to set the token value.
      *
      */
-    otError GenerateRandomToken(uint8_t aTokenLength);
+    Error GenerateRandomToken(uint8_t aTokenLength);
 
     /**
      * This method checks if Tokens in two CoAP headers are equal.
@@ -426,12 +427,12 @@
      * @param[in] aLength   The CoAP Option length.
      * @param[in] aValue    A pointer to the CoAP Option value (@p aLength bytes are used as Option value).
      *
-     * @retval OT_ERROR_NONE          Successfully appended the option.
-     * @retval OT_ERROR_INVALID_ARGS  The option type is not equal or greater than the last option type.
-     * @retval OT_ERROR_NO_BUFS       The option length exceeds the buffer size.
+     * @retval kErrorNone         Successfully appended the option.
+     * @retval kErrorInvalidArgs  The option type is not equal or greater than the last option type.
+     * @retval kErrorNoBufs       The option length exceeds the buffer size.
      *
      */
-    otError AppendOption(uint16_t aNumber, uint16_t aLength, const void *aValue);
+    Error AppendOption(uint16_t aNumber, uint16_t aLength, const void *aValue);
 
     /**
      * This method appends an unsigned integer CoAP option as specified in RFC-7252 section-3.2
@@ -439,12 +440,12 @@
      * @param[in]  aNumber  The CoAP Option number.
      * @param[in]  aValue   The CoAP Option unsigned integer value.
      *
-     * @retval OT_ERROR_NONE          Successfully appended the option.
-     * @retval OT_ERROR_INVALID_ARGS  The option type is not equal or greater than the last option type.
-     * @retval OT_ERROR_NO_BUFS       The option length exceeds the buffer size.
+     * @retval kErrorNone         Successfully appended the option.
+     * @retval kErrorInvalidArgs  The option type is not equal or greater than the last option type.
+     * @retval kErrorNoBufs       The option length exceeds the buffer size.
      *
      */
-    otError AppendUintOption(uint16_t aNumber, uint32_t aValue);
+    Error AppendUintOption(uint16_t aNumber, uint32_t aValue);
 
     /**
      * This method appends a string CoAP option.
@@ -452,35 +453,35 @@
      * @param[in]  aNumber  The CoAP Option number.
      * @param[in]  aValue   The CoAP Option string value.
      *
-     * @retval OT_ERROR_NONE          Successfully appended the option.
-     * @retval OT_ERROR_INVALID_ARGS  The option type is not equal or greater than the last option type.
-     * @retval OT_ERROR_NO_BUFS       The option length exceeds the buffer size.
+     * @retval kErrorNone         Successfully appended the option.
+     * @retval kErrorInvalidArgs  The option type is not equal or greater than the last option type.
+     * @retval kErrorNoBufs       The option length exceeds the buffer size.
      *
      */
-    otError AppendStringOption(uint16_t aNumber, const char *aValue);
+    Error AppendStringOption(uint16_t aNumber, const char *aValue);
 
     /**
      * This method appends an Observe option.
      *
      * @param[in]  aObserve  Observe field value.
      *
-     * @retval OT_ERROR_NONE          Successfully appended the option.
-     * @retval OT_ERROR_INVALID_ARGS  The option type is not equal or greater than the last option type.
-     * @retval OT_ERROR_NO_BUFS       The option length exceeds the buffer size.
+     * @retval kErrorNone         Successfully appended the option.
+     * @retval kErrorInvalidArgs  The option type is not equal or greater than the last option type.
+     * @retval kErrorNoBufs       The option length exceeds the buffer size.
      */
-    otError AppendObserveOption(uint32_t aObserve) { return AppendUintOption(kOptionObserve, aObserve & kObserveMask); }
+    Error AppendObserveOption(uint32_t aObserve) { return AppendUintOption(kOptionObserve, aObserve & kObserveMask); }
 
     /**
      * This method appends a Uri-Path option.
      *
      * @param[in]  aUriPath           A pointer to a null-terminated string.
      *
-     * @retval OT_ERROR_NONE          Successfully appended the option.
-     * @retval OT_ERROR_INVALID_ARGS  The option type is not equal or greater than the last option type.
-     * @retval OT_ERROR_NO_BUFS       The option length exceeds the buffer size.
+     * @retval kErrorNone         Successfully appended the option.
+     * @retval kErrorInvalidArgs  The option type is not equal or greater than the last option type.
+     * @retval kErrorNoBufs       The option length exceeds the buffer size.
      *
      */
-    otError AppendUriPathOptions(const char *aUriPath);
+    Error AppendUriPathOptions(const char *aUriPath);
 
     /**
      * This method reads the Uri-Path options and constructs the URI path in the buffer referenced by @p `aUriPath`.
@@ -488,11 +489,11 @@
      * @param[in] aUriPath  A reference to the buffer for storing URI path.
      *                      NOTE: The buffer size must be `kMaxReceivedUriPath + 1`.
      *
-     * @retval  OT_ERROR_NONE   Successfully read the Uri-Path options.
-     * @retval  OT_ERROR_PARSE  CoAP Option header not well-formed.
+     * @retval  kErrorNone   Successfully read the Uri-Path options.
+     * @retval  kErrorParse  CoAP Option header not well-formed.
      *
      */
-    otError ReadUriPathOptions(char (&aUriPath)[kMaxReceivedUriPath + 1]) const;
+    Error ReadUriPathOptions(char (&aUriPath)[kMaxReceivedUriPath + 1]) const;
 
     /**
      * This method appends a Block option
@@ -502,36 +503,36 @@
      * @param[in]  aMore              Boolean to indicate more blocks are to be sent.
      * @param[in]  aSize              Maximum block size.
      *
-     * @retval OT_ERROR_NONE          Successfully appended the option.
-     * @retval OT_ERROR_INVALID_ARGS  The option type is not equal or greater than the last option type.
-     * @retval OT_ERROR_NO_BUFS       The option length exceeds the buffer size.
+     * @retval kErrorNone         Successfully appended the option.
+     * @retval kErrorInvalidArgs  The option type is not equal or greater than the last option type.
+     * @retval kErrorNoBufs       The option length exceeds the buffer size.
      *
      */
-    otError AppendBlockOption(BlockType aType, uint32_t aNum, bool aMore, otCoapBlockSize aSize);
+    Error AppendBlockOption(BlockType aType, uint32_t aNum, bool aMore, otCoapBlockSzx aSize);
 
     /**
      * This method appends a Proxy-Uri option.
      *
      * @param[in]  aProxyUri          A pointer to a null-terminated string.
      *
-     * @retval OT_ERROR_NONE          Successfully appended the option.
-     * @retval OT_ERROR_INVALID_ARGS  The option type is not equal or greater than the last option type.
-     * @retval OT_ERROR_NO_BUFS       The option length exceeds the buffer size.
+     * @retval kErrorNone         Successfully appended the option.
+     * @retval kErrorInvalidArgs  The option type is not equal or greater than the last option type.
+     * @retval kErrorNoBufs       The option length exceeds the buffer size.
      *
      */
-    otError AppendProxyUriOption(const char *aProxyUri) { return AppendStringOption(kOptionProxyUri, aProxyUri); }
+    Error AppendProxyUriOption(const char *aProxyUri) { return AppendStringOption(kOptionProxyUri, aProxyUri); }
 
     /**
      * This method appends a Content-Format option.
      *
      * @param[in]  aContentFormat  The Content Format value.
      *
-     * @retval OT_ERROR_NONE          Successfully appended the option.
-     * @retval OT_ERROR_INVALID_ARGS  The option type is not equal or greater than the last option type.
-     * @retval OT_ERROR_NO_BUFS       The option length exceeds the buffer size.
+     * @retval kErrorNone         Successfully appended the option.
+     * @retval kErrorInvalidArgs  The option type is not equal or greater than the last option type.
+     * @retval kErrorNoBufs       The option length exceeds the buffer size.
      *
      */
-    otError AppendContentFormatOption(otCoapOptionContentFormat aContentFormat)
+    Error AppendContentFormatOption(otCoapOptionContentFormat aContentFormat)
     {
         return AppendUintOption(kOptionContentFormat, static_cast<uint32_t>(aContentFormat));
     }
@@ -541,33 +542,89 @@
      *
      * @param[in]  aMaxAge  The Max-Age value.
      *
-     * @retval OT_ERROR_NONE          Successfully appended the option.
-     * @retval OT_ERROR_INVALID_ARGS  The option type is not equal or greater than the last option type.
-     * @retval OT_ERROR_NO_BUFS       The option length exceeds the buffer size.
+     * @retval kErrorNone         Successfully appended the option.
+     * @retval kErrorInvalidArgs  The option type is not equal or greater than the last option type.
+     * @retval kErrorNoBufs       The option length exceeds the buffer size.
      */
-    otError AppendMaxAgeOption(uint32_t aMaxAge) { return AppendUintOption(kOptionMaxAge, aMaxAge); }
+    Error AppendMaxAgeOption(uint32_t aMaxAge) { return AppendUintOption(kOptionMaxAge, aMaxAge); }
 
     /**
      * This method appends a single Uri-Query option.
      *
      * @param[in]  aUriQuery  A pointer to null-terminated string, which should contain a single key=value pair.
      *
-     * @retval OT_ERROR_NONE          Successfully appended the option.
-     * @retval OT_ERROR_INVALID_ARGS  The option type is not equal or greater than the last option type.
-     * @retval OT_ERROR_NO_BUFS       The option length exceeds the buffer size.
+     * @retval kErrorNone         Successfully appended the option.
+     * @retval kErrorInvalidArgs  The option type is not equal or greater than the last option type.
+     * @retval kErrorNoBufs       The option length exceeds the buffer size.
      */
-    otError AppendUriQueryOption(const char *aUriQuery) { return AppendStringOption(kOptionUriQuery, aUriQuery); }
+    Error AppendUriQueryOption(const char *aUriQuery) { return AppendStringOption(kOptionUriQuery, aUriQuery); }
+
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    /**
+     * This function reads the information contained in a Block1 or Block2 option and set it in
+     * the HelpData of the message.
+     *
+     * @param[in]   aBlockType  Block1 or Block2 option value.
+     *
+     * @retval  kErrorNone          The option has been found and is valid.
+     * @retval  kErrorNotFound      The option has not been found.
+     * @retval  kErrorInvalidArgs   The option is invalid.
+     */
+    Error ReadBlockOptionValues(uint16_t aBlockType);
+
+    /**
+     * This method returns the current header length of a message.
+     *
+     * @returns The length of the message header.
+     *
+     */
+    uint16_t GetHeaderLength(void) const { return GetHelpData().mHeaderLength; }
+
+    /**
+     * This method returns the block number of a CoAP block-wise transfer message.
+     *
+     * @returns The block number.
+     *
+     */
+    uint32_t GetBlockWiseBlockNumber(void) const { return GetHelpData().mBlockWiseData.mBlockNumber; }
+
+    /**
+     * This method checks if the More Blocks flag is set.
+     *
+     * @retval TRUE   More Blocks flag is set.
+     * @retval FALSE  More Blocks flag is not set.
+     *
+     */
+    bool IsMoreBlocksFlagSet(void) const { return GetHelpData().mBlockWiseData.mMoreBlocks; }
+
+    /**
+     * This method returns the block size of a CoAP block-wise transfer message.
+     *
+     * @returns The block size.
+     *
+     */
+    otCoapBlockSzx GetBlockWiseBlockSize(void) const { return GetHelpData().mBlockWiseData.mBlockSize; }
+#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+
+    /**
+     * This function reads and reassembles the URI path string and fills it into @p aUriPath.
+     *
+     * @retval  kErrorNone      URI path string has been reassembled.
+     * @retval  kErrorNoBufs    URI path string is too long.
+     *
+     */
+    Error GetUriPath(char *aUriPath) const;
 
     /**
      * This method adds Payload Marker indicating beginning of the payload to the CoAP header.
      *
      * It also set offset to the start of payload.
      *
-     * @retval OT_ERROR_NONE     Payload Marker successfully added.
-     * @retval OT_ERROR_NO_BUFS  Message Payload Marker exceeds the buffer size.
+     * @retval kErrorNone    Payload Marker successfully added.
+     * @retval kErrorNoBufs  Message Payload Marker exceeds the buffer size.
      *
      */
-    otError SetPayloadMarker(void);
+    Error SetPayloadMarker(void);
 
     /**
      * This method returns the offset of the first CoAP option.
@@ -580,22 +637,49 @@
     /**
      * This method parses CoAP header and moves offset end of CoAP header.
      *
-     * @retval  OT_ERROR_NONE   Successfully parsed CoAP header from the message.
-     * @retval  OT_ERROR_PARSE  Failed to parse the CoAP header.
+     * @retval  kErrorNone   Successfully parsed CoAP header from the message.
+     * @retval  kErrorParse  Failed to parse the CoAP header.
      *
      */
-    otError ParseHeader(void);
+    Error ParseHeader(void);
 
     /**
      * This method sets a default response header based on request header.
      *
      * @param[in]  aRequest  The request message.
      *
-     * @retval OT_ERROR_NONE     Successfully set the default response header.
-     * @retval OT_ERROR_NO_BUFS  Insufficient message buffers available to set the default response header.
+     * @retval kErrorNone    Successfully set the default response header.
+     * @retval kErrorNoBufs  Insufficient message buffers available to set the default response header.
      *
      */
-    otError SetDefaultResponseHeader(const Message &aRequest);
+    Error SetDefaultResponseHeader(const Message &aRequest);
+
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+
+    /**
+     * This method sets the block number value in the message HelpData.
+     *
+     * @param[in]   aBlockNumber    Block number value to set.
+     *
+     */
+    void SetBlockWiseBlockNumber(uint32_t aBlockNumber) { GetHelpData().mBlockWiseData.mBlockNumber = aBlockNumber; }
+
+    /**
+     * This method sets the More Blocks falg in the message HelpData.
+     *
+     * @param[in]   aMoreBlocks    TRUE or FALSE.
+     *
+     */
+    void SetMoreBlocksFlag(bool aMoreBlocks) { GetHelpData().mBlockWiseData.mMoreBlocks = aMoreBlocks; }
+
+    /**
+     * This method sets the block size value in the message HelpData.
+     *
+     * @param[in]   aBlockSize    Block size value to set.
+     *
+     */
+    void SetBlockWiseBlockSize(otCoapBlockSzx aBlockSize) { GetHelpData().mBlockWiseData.mBlockSize = aBlockSize; }
+#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
 
     /**
      * This method checks if a header is an empty message header.
@@ -845,6 +929,15 @@
         kBlockNumMax = 0xffff,
     };
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    struct BlockWiseData
+    {
+        uint32_t       mBlockNumber;
+        bool           mMoreBlocks;
+        otCoapBlockSzx mBlockSize;
+    };
+#endif
+
     /**
      * This structure represents a CoAP header excluding CoAP options.
      *
@@ -868,6 +961,9 @@
         uint16_t mOptionLast;
         uint16_t mHeaderOffset; ///< The byte offset for the CoAP Header
         uint16_t mHeaderLength;
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+        BlockWiseData mBlockWiseData;
+#endif
     };
 
     const HelpData &GetHelpData(void) const
@@ -962,11 +1058,11 @@
          *
          * @param[in] aMessage  The CoAP message.
          *
-         * @retval OT_ERROR_NONE   Successfully initialized. Iterator is either at the first option or done.
-         * @retval OT_ERROR_PARSE  CoAP Option header in @p aMessage is not well-formed.
+         * @retval kErrorNone   Successfully initialized. Iterator is either at the first option or done.
+         * @retval kErrorParse  CoAP Option header in @p aMessage is not well-formed.
          *
          */
-        otError Init(const Message &aMessage);
+        Error Init(const Message &aMessage);
 
         /**
          * This method initializes the iterator to iterate over CoAP Options in a CoAP message matching a given Option
@@ -981,11 +1077,11 @@
          * @param[in] aMessage  The CoAP message.
          * @param[in] aNumber   The CoAP Option Number.
          *
-         * @retval  OT_ERROR_NONE   Successfully initialized. Iterator is either at the first matching option or done.
-         * @retval  OT_ERROR_PARSE  CoAP Option header in @p aMessage is not well-formed.
+         * @retval  kErrorNone   Successfully initialized. Iterator is either at the first matching option or done.
+         * @retval  kErrorParse  CoAP Option header in @p aMessage is not well-formed.
          *
          */
-        otError Init(const Message &aMessage, uint16_t aNumber) { return InitOrAdvance(&aMessage, aNumber); }
+        Error Init(const Message &aMessage, uint16_t aNumber) { return InitOrAdvance(&aMessage, aNumber); }
 
         /**
          * This method indicates whether or not the iterator is done (i.e., has reached the end of CoAP Option Header).
@@ -1012,11 +1108,11 @@
          *
          * The iterator is updated to point to the next option or marked as done when there are no more options.
          *
-         * @retval  OT_ERROR_NONE   Successfully advanced the iterator.
-         * @retval  OT_ERROR_PARSE  CoAP Option header is not well-formed.
+         * @retval  kErrorNone   Successfully advanced the iterator.
+         * @retval  kErrorParse  CoAP Option header is not well-formed.
          *
          */
-        otError Advance(void);
+        Error Advance(void);
 
         /**
          * This method advances the iterator to the next CoAP Option in the header matching a given Option Number value.
@@ -1026,11 +1122,11 @@
          *
          * @param[in] aNumber   The CoAP Option Number.
          *
-         * @retval  OT_ERROR_NONE   Successfully advanced the iterator.
-         * @retval  OT_ERROR_PARSE  CoAP Option header is not well-formed.
+         * @retval  kErrorNone   Successfully advanced the iterator.
+         * @retval  kErrorParse  CoAP Option header is not well-formed.
          *
          */
-        otError Advance(uint16_t aNumber) { return InitOrAdvance(nullptr, aNumber); }
+        Error Advance(uint16_t aNumber) { return InitOrAdvance(nullptr, aNumber); }
 
         /**
          * This method gets the CoAP message associated with the iterator.
@@ -1055,23 +1151,23 @@
          * @param[out]  aValue   The pointer to a buffer to copy the Option Value. The buffer is assumed to be
          *                       sufficiently large (i.e. at least `GetOption()->GetLength()` bytes).
          *
-         * @retval OT_ERROR_NONE        Successfully read and copied the Option Value into given buffer.
-         * @retval OT_ERROR_NOT_FOUND   Iterator is done (not pointing to any option).
+         * @retval kErrorNone       Successfully read and copied the Option Value into given buffer.
+         * @retval kErrorNotFound   Iterator is done (not pointing to any option).
          *
          */
-        otError ReadOptionValue(void *aValue) const;
+        Error ReadOptionValue(void *aValue) const;
 
         /**
          * This method read the current Option Value which is assumed to be an unsigned integer.
          *
          * @param[out]  aUintValue      A reference to `uint64_t` to output the read Option Value.
          *
-         * @retval OT_ERROR_NONE        Successfully read the Option value.
-         * @retval OT_ERROR_NO_BUFS     Value is too long to fit in an `uint64_t`.
-         * @retval OT_ERROR_NOT_FOUND   Iterator is done (not pointing to any option).
+         * @retval kErrorNone       Successfully read the Option value.
+         * @retval kErrorNoBufs     Value is too long to fit in an `uint64_t`.
+         * @retval kErrorNotFound   Iterator is done (not pointing to any option).
          *
          */
-        otError ReadOptionValue(uint64_t &aUintValue) const;
+        Error ReadOptionValue(uint64_t &aUintValue) const;
 
         /**
          * This method gets the offset of beginning of the CoAP message payload (after the CoAP header).
@@ -1093,9 +1189,9 @@
         void MarkAsDone(void) { mOption.mLength = kIteratorDoneLength; }
         void MarkAsParseErrored(void) { MarkAsDone(), mNextOptionOffset = kNextOptionOffsetParseError; }
 
-        otError Read(uint16_t aLength, void *aBuffer);
-        otError ReadExtendedOptionField(uint16_t &aValue);
-        otError InitOrAdvance(const Message *aMessage, uint16_t aNumber);
+        Error Read(uint16_t aLength, void *aBuffer);
+        Error ReadExtendedOptionField(uint16_t &aValue);
+        Error InitOrAdvance(const Message *aMessage, uint16_t aNumber);
     };
 
     /**
diff --git a/src/core/coap/coap_secure.cpp b/src/core/coap/coap_secure.cpp
index 06769f8..1ab1c4f 100644
--- a/src/core/coap/coap_secure.cpp
+++ b/src/core/coap/coap_secure.cpp
@@ -54,9 +54,9 @@
 {
 }
 
-otError CoapSecure::Start(uint16_t aPort)
+Error CoapSecure::Start(uint16_t aPort)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     mConnectedCallback = nullptr;
     mConnectedContext  = nullptr;
@@ -68,9 +68,9 @@
     return error;
 }
 
-otError CoapSecure::Start(MeshCoP::Dtls::TransportCallback aCallback, void *aContext)
+Error CoapSecure::Start(MeshCoP::Dtls::TransportCallback aCallback, void *aContext)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     mConnectedCallback = nullptr;
     mConnectedContext  = nullptr;
@@ -95,7 +95,7 @@
     ClearRequestsAndResponses();
 }
 
-otError CoapSecure::Connect(const Ip6::SockAddr &aSockAddr, ConnectedCallback aCallback, void *aContext)
+Error CoapSecure::Connect(const Ip6::SockAddr &aSockAddr, ConnectedCallback aCallback, void *aContext)
 {
     mConnectedCallback = aCallback;
     mConnectedContext  = aContext;
@@ -105,7 +105,7 @@
 
 void CoapSecure::SetPsk(const MeshCoP::JoinerPskd &aPskd)
 {
-    otError error;
+    Error error;
 
     OT_UNUSED_VARIABLE(error);
 
@@ -115,14 +115,43 @@
 
     error = mDtls.SetPsk(reinterpret_cast<const uint8_t *>(aPskd.GetAsCString()), aPskd.GetLength());
 
-    OT_ASSERT(error == OT_ERROR_NONE);
+    OT_ASSERT(error == kErrorNone);
 }
 
-otError CoapSecure::SendMessage(Message &aMessage, ResponseHandler aHandler, void *aContext)
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+Error CoapSecure::SendMessage(Message &                   aMessage,
+                              ResponseHandler             aHandler,
+                              void *                      aContext,
+                              otCoapBlockwiseTransmitHook aTransmitHook,
+                              otCoapBlockwiseReceiveHook  aReceiveHook)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(IsConnected(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(IsConnected(), error = kErrorInvalidState);
+
+    error = CoapBase::SendMessage(aMessage, mDtls.GetMessageInfo(), TxParameters::GetDefault(), aHandler, aContext,
+                                  aTransmitHook, aReceiveHook);
+
+exit:
+    return error;
+}
+
+Error CoapSecure::SendMessage(Message &                   aMessage,
+                              const Ip6::MessageInfo &    aMessageInfo,
+                              ResponseHandler             aHandler,
+                              void *                      aContext,
+                              otCoapBlockwiseTransmitHook aTransmitHook,
+                              otCoapBlockwiseReceiveHook  aReceiveHook)
+{
+    return CoapBase::SendMessage(aMessage, aMessageInfo, TxParameters::GetDefault(), aHandler, aContext, aTransmitHook,
+                                 aReceiveHook);
+}
+#else  // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+Error CoapSecure::SendMessage(Message &aMessage, ResponseHandler aHandler, void *aContext)
+{
+    Error error = kErrorNone;
+
+    VerifyOrExit(IsConnected(), error = kErrorInvalidState);
 
     error = CoapBase::SendMessage(aMessage, mDtls.GetMessageInfo(), aHandler, aContext);
 
@@ -130,22 +159,23 @@
     return error;
 }
 
-otError CoapSecure::SendMessage(Message &               aMessage,
-                                const Ip6::MessageInfo &aMessageInfo,
-                                ResponseHandler         aHandler,
-                                void *                  aContext)
+Error CoapSecure::SendMessage(Message &               aMessage,
+                              const Ip6::MessageInfo &aMessageInfo,
+                              ResponseHandler         aHandler,
+                              void *                  aContext)
 {
     return CoapBase::SendMessage(aMessage, aMessageInfo, aHandler, aContext);
 }
+#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
 
-otError CoapSecure::Send(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
+Error CoapSecure::Send(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
 
     mTransmitQueue.Enqueue(aMessage);
     mTransmitTask.Post();
 
-    return OT_ERROR_NONE;
+    return kErrorNone;
 }
 
 void CoapSecure::HandleDtlsConnected(void *aContext, bool aConnected)
@@ -186,7 +216,7 @@
 
 void CoapSecure::HandleTransmit(void)
 {
-    otError      error   = OT_ERROR_NONE;
+    Error        error   = kErrorNone;
     ot::Message *message = mTransmitQueue.GetHead();
 
     VerifyOrExit(message != nullptr);
@@ -200,14 +230,14 @@
     SuccessOrExit(error = mDtls.Send(*message, message->GetLength()));
 
 exit:
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogNoteMeshCoP("CoapSecure Transmit: %s", otThreadErrorToString(error));
+        otLogNoteMeshCoP("CoapSecure Transmit: %s", ErrorToString(error));
         message->Free();
     }
     else
     {
-        otLogDebgMeshCoP("CoapSecure Transmit: %s", otThreadErrorToString(error));
+        otLogDebgMeshCoP("CoapSecure Transmit: %s", ErrorToString(error));
     }
 }
 
diff --git a/src/core/coap/coap_secure.hpp b/src/core/coap/coap_secure.hpp
index 889ed74..272a1d6 100644
--- a/src/core/coap/coap_secure.hpp
+++ b/src/core/coap/coap_secure.hpp
@@ -72,11 +72,11 @@
      *
      * @param[in]  aPort      The local UDP port to bind to.
      *
-     * @retval OT_ERROR_NONE        Successfully started the CoAP agent.
-     * @retval OT_ERROR_ALREADY     Already started.
+     * @retval kErrorNone        Successfully started the CoAP agent.
+     * @retval kErrorAlready     Already started.
      *
      */
-    otError Start(uint16_t aPort);
+    Error Start(uint16_t aPort);
 
     /**
      * This method starts the secure CoAP agent, but do not use socket to transmit/receive messages.
@@ -84,11 +84,11 @@
      * @param[in]  aCallback  A pointer to a function for sending messages.
      * @param[in]  aContext   A pointer to arbitrary context information.
      *
-     * @retval OT_ERROR_NONE        Successfully started the CoAP agent.
-     * @retval OT_ERROR_ALREADY     Already started.
+     * @retval kErrorNone        Successfully started the CoAP agent.
+     * @retval kErrorAlready     Already started.
      *
      */
-    otError Start(MeshCoP::Dtls::TransportCallback aCallback, void *aContext);
+    Error Start(MeshCoP::Dtls::TransportCallback aCallback, void *aContext);
 
     /**
      * This method sets connected callback of this secure CoAP agent.
@@ -116,10 +116,10 @@
      * @param[in]  aCallback               A pointer to a function that will be called once DTLS connection is
      * established.
      *
-     * @retval OT_ERROR_NONE  Successfully started DTLS connection.
+     * @retval kErrorNone  Successfully started DTLS connection.
      *
      */
-    otError Connect(const Ip6::SockAddr &aSockAddr, ConnectedCallback aCallback, void *aContext);
+    Error Connect(const Ip6::SockAddr &aSockAddr, ConnectedCallback aCallback, void *aContext);
 
     /**
      * This method indicates whether or not the DTLS session is active.
@@ -159,11 +159,11 @@
      * @param[in]  aPsk        A pointer to the PSK.
      * @param[in]  aPskLength  The PSK length.
      *
-     * @retval OT_ERROR_NONE          Successfully set the PSK.
-     * @retval OT_ERROR_INVALID_ARGS  The PSK is invalid.
+     * @retval kErrorNone         Successfully set the PSK.
+     * @retval kErrorInvalidArgs  The PSK is invalid.
      *
      */
-    otError SetPsk(const uint8_t *aPsk, uint8_t aPskLength) { return mDtls.SetPsk(aPsk, aPskLength); }
+    Error SetPsk(const uint8_t *aPsk, uint8_t aPskLength) { return mDtls.SetPsk(aPsk, aPskLength); }
 
     /**
      * This method sets the PSK.
@@ -238,11 +238,11 @@
      * @param[out]  aCertLength      The length of the base64 encoded peer certificate.
      * @param[in]   aCertBufferSize  The buffer size of aPeerCert.
      *
-     * @retval OT_ERROR_NONE     Successfully get the peer certificate.
-     * @retval OT_ERROR_NO_BUFS  Can't allocate memory for certificate.
+     * @retval kErrorNone    Successfully get the peer certificate.
+     * @retval kErrorNoBufs  Can't allocate memory for certificate.
      *
      */
-    otError GetPeerCertificateBase64(unsigned char *aPeerCert, size_t *aCertLength, size_t aCertBufferSize)
+    Error GetPeerCertificateBase64(unsigned char *aPeerCert, size_t *aCertLength, size_t aCertBufferSize)
     {
         return mDtls.GetPeerCertificateBase64(aPeerCert, aCertLength, aCertBufferSize);
     }
@@ -272,6 +272,57 @@
 
 #endif // OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE
 
+#if OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+    /**
+     * This method sends a CoAP message over secure DTLS connection.
+     *
+     * If a response for a request is expected, respective function and context information should be provided.
+     * If no response is expected, these arguments should be NULL pointers.
+     * If Message Id was not set in the header (equal to 0), this function will assign unique Message Id to the message.
+     *
+     * @param[in]  aMessage      A reference to the message to send.
+     * @param[in]  aHandler      A function pointer that shall be called on response reception or time-out.
+     * @param[in]  aContext      A pointer to arbitrary context information.
+     * @param[in]  aTransmitHook A pointer to a hook function for outgoing block-wise transfer.
+     * @param[in]  aReceiveHook  A pointer to a hook function for incoming block-wise transfer.
+     *
+     * @retval kErrorNone          Successfully sent CoAP message.
+     * @retval kErrorNoBufs        Failed to allocate retransmission data.
+     * @retval kErrorInvalidState  DTLS connection was not initialized.
+     *
+     */
+    Error SendMessage(Message &                   aMessage,
+                      ResponseHandler             aHandler      = nullptr,
+                      void *                      aContext      = nullptr,
+                      otCoapBlockwiseTransmitHook aTransmitHook = nullptr,
+                      otCoapBlockwiseReceiveHook  aReceiveHook  = nullptr);
+
+    /**
+     * This method sends a CoAP message over secure DTLS connection.
+     *
+     * If a response for a request is expected, respective function and context information should be provided.
+     * If no response is expected, these arguments should be NULL pointers.
+     * If Message Id was not set in the header (equal to 0), this function will assign unique Message Id to the message.
+     *
+     * @param[in]  aMessage      A reference to the message to send.
+     * @param[in]  aMessageInfo  A reference to the message info associated with @p aMessage.
+     * @param[in]  aHandler      A function pointer that shall be called on response reception or time-out.
+     * @param[in]  aContext      A pointer to arbitrary context information.
+     * @param[in]  aTransmitHook A pointer to a hook function for outgoing block-wise transfer.
+     * @param[in]  aReceiveHook  A pointer to a hook function for incoming block-wise transfer.
+     *
+     * @retval kErrorNone          Successfully sent CoAP message.
+     * @retval kErrorNoBufs        Failed to allocate retransmission data.
+     * @retval kErrorInvalidState  DTLS connection was not initialized.
+     *
+     */
+    Error SendMessage(Message &                   aMessage,
+                      const Ip6::MessageInfo &    aMessageInfo,
+                      ResponseHandler             aHandler      = nullptr,
+                      void *                      aContext      = nullptr,
+                      otCoapBlockwiseTransmitHook aTransmitHook = nullptr,
+                      otCoapBlockwiseReceiveHook  aReceiveHook  = nullptr);
+#else  // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
     /**
      * This method sends a CoAP message over secure DTLS connection.
      *
@@ -283,12 +334,12 @@
      * @param[in]  aHandler      A function pointer that shall be called on response reception or time-out.
      * @param[in]  aContext      A pointer to arbitrary context information.
      *
-     * @retval OT_ERROR_NONE           Successfully sent CoAP message.
-     * @retval OT_ERROR_NO_BUFS        Failed to allocate retransmission data.
-     * @retval OT_ERROR_INVALID_STATE  DTLS connection was not initialized.
+     * @retval kErrorNone          Successfully sent CoAP message.
+     * @retval kErrorNoBufs        Failed to allocate retransmission data.
+     * @retval kErrorInvalidState  DTLS connection was not initialized.
      *
      */
-    otError SendMessage(Message &aMessage, ResponseHandler aHandler = nullptr, void *aContext = nullptr);
+    Error SendMessage(Message &aMessage, ResponseHandler aHandler = nullptr, void *aContext = nullptr);
 
     /**
      * This method sends a CoAP message over secure DTLS connection.
@@ -302,15 +353,16 @@
      * @param[in]  aHandler      A function pointer that shall be called on response reception or time-out.
      * @param[in]  aContext      A pointer to arbitrary context information.
      *
-     * @retval OT_ERROR_NONE           Successfully sent CoAP message.
-     * @retval OT_ERROR_NO_BUFS        Failed to allocate retransmission data.
-     * @retval OT_ERROR_INVALID_STATE  DTLS connection was not initialized.
+     * @retval kErrorNone          Successfully sent CoAP message.
+     * @retval kErrorNoBufs        Failed to allocate retransmission data.
+     * @retval kErrorInvalidState  DTLS connection was not initialized.
      *
      */
-    otError SendMessage(Message &               aMessage,
-                        const Ip6::MessageInfo &aMessageInfo,
-                        ResponseHandler         aHandler = nullptr,
-                        void *                  aContext = nullptr);
+    Error SendMessage(Message &               aMessage,
+                      const Ip6::MessageInfo &aMessageInfo,
+                      ResponseHandler         aHandler = nullptr,
+                      void *                  aContext = nullptr);
+#endif // OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
 
     /**
      * This method is used to pass UDP messages to the secure CoAP server.
@@ -333,11 +385,11 @@
     const Ip6::MessageInfo &GetMessageInfo(void) const { return mDtls.GetMessageInfo(); }
 
 private:
-    static otError Send(CoapBase &aCoapBase, ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
+    static Error Send(CoapBase &aCoapBase, ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
     {
         return static_cast<CoapSecure &>(aCoapBase).Send(aMessage, aMessageInfo);
     }
-    otError Send(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    Error Send(ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
 
     static void HandleDtlsConnected(void *aContext, bool aConnected);
     void        HandleDtlsConnected(bool aConnected);
diff --git a/src/core/common/equatable.hpp b/src/core/common/equatable.hpp
index 83ab05b..06fdda9 100644
--- a/src/core/common/equatable.hpp
+++ b/src/core/common/equatable.hpp
@@ -41,6 +41,32 @@
 namespace ot {
 
 /**
+ * This template class defines an overload of operator `!=`.
+ *
+ * The `!=` implementation uses an existing `==` overload provided by the `Type` class.
+ *
+ * Users of this class should follow CRTP-style inheritance, i.e., the `Type` class itself should publicly inherit
+ * from `Unequatable<Type>`.
+ *
+ */
+template <typename Type> class Unequatable
+{
+public:
+    /**
+     * This method overloads operator `!=` to evaluate whether or not two instances of `Type` are equal.
+     *
+     * This is implemented in terms of an existing `==` overload provided by `Type` class itself.
+     *
+     * @param[in]  aOther  The other `Type` instance to compare with.
+     *
+     * @retval TRUE   If the two `Type` instances are not equal.
+     * @retval FALSE  If the two `Type` instances are equal.
+     *
+     */
+    bool operator!=(const Type &aOther) const { return !(*static_cast<const Type *>(this) == aOther); }
+};
+
+/**
  * This template class defines overloads of operators `==` and `!=`.
  *
  * The `==` implementation simply compares all the bytes of two `Type` instances to be equal (using `memcmp()`).
@@ -49,7 +75,7 @@
  * from `Equatable<Type>`.
  *
  */
-template <class Type> class Equatable
+template <typename Type> class Equatable : public Unequatable<Type>
 {
 public:
     /**
@@ -62,17 +88,6 @@
      *
      */
     bool operator==(const Type &aOther) const { return memcmp(this, &aOther, sizeof(Type)) == 0; }
-
-    /**
-     * This method overloads operator `!=` to evaluate whether or not two instances of `Type` are equal.
-     *
-     * @param[in]  aOther  The other `Type` instance to compare with.
-     *
-     * @retval TRUE   If the two `Type` instances are not equal.
-     * @retval FALSE  If the two `Type` instances are equal.
-     *
-     */
-    bool operator!=(const Type &aOther) const { return !(*this == aOther); }
 };
 
 } // namespace ot
diff --git a/src/core/common/error.cpp b/src/core/common/error.cpp
new file mode 100644
index 0000000..3d0e4a5
--- /dev/null
+++ b/src/core/common/error.cpp
@@ -0,0 +1,85 @@
+/*
+ *  Copyright (c) 2016-2021, The OpenThread Authors.
+ *  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
+ *   This file implements the error code functions used by OpenThread core modules.
+ */
+
+#include "error.hpp"
+
+#include "common/code_utils.hpp"
+
+namespace ot {
+
+const char *ErrorToString(Error aError)
+{
+    static const char *const kErrorStrings[kNumErrors] = {
+        "OK",                         // (0)  kErrorNone
+        "Failed",                     // (1)  kErrorFailed
+        "Drop",                       // (2)  kErrorDrop
+        "NoBufs",                     // (3)  kErrorNoBufs
+        "NoRoute",                    // (4)  kErrorNoRoute
+        "Busy",                       // (5)  kErrorBusy
+        "Parse",                      // (6)  kErrorParse
+        "InvalidArgs",                // (7)  kErrorInvalidArgs
+        "Security",                   // (8)  kErrorSecurity
+        "AddressQuery",               // (9)  kErrorAddressQuery
+        "NoAddress",                  // (10) kErrorNoAddress
+        "Abort",                      // (11) kErrorAbort
+        "NotImplemented",             // (12) kErrorNotImplemented
+        "InvalidState",               // (13) kErrorInvalidState
+        "NoAck",                      // (14) kErrorNoAck
+        "ChannelAccessFailure",       // (15) kErrorChannelAccessFailure
+        "Detached",                   // (16) kErrorDetached
+        "FcsErr",                     // (17) kErrorFcs
+        "NoFrameReceived",            // (18) kErrorNoFrameReceived
+        "UnknownNeighbor",            // (19) kErrorUnknownNeighbor
+        "InvalidSourceAddress",       // (20) kErrorInvalidSourceAddress
+        "AddressFiltered",            // (21) kErrorAddressFiltered
+        "DestinationAddressFiltered", // (22) kErrorDestinationAddressFiltered
+        "NotFound",                   // (23) kErrorNotFound
+        "Already",                    // (24) kErrorAlready
+        "ReservedError25",            // (25) Error 25 is reserved
+        "Ipv6AddressCreationFailure", // (26) kErrorIp6AddressCreationFailure
+        "NotCapable",                 // (27) kErrorNotCapable
+        "ResponseTimeout",            // (28) kErrorResponseTimeout
+        "Duplicated",                 // (29) kErrorDuplicated
+        "ReassemblyTimeout",          // (30) kErrorReassemblyTimeout
+        "NotTmf",                     // (31) kErrorNotTmf
+        "NonLowpanDataFrame",         // (32) kErrorNotLowpanDataFrame
+        "ReservedError33",            // (33) Error 33 is reserved
+        "LinkMarginLow",              // (34) kErrorLinkMarginLow
+        "InvalidCommand",             // (35) kErrorInvalidCommand
+        "Pending",                    // (36) kErrorPending
+    };
+
+    return aError < OT_ARRAY_LENGTH(kErrorStrings) ? kErrorStrings[aError] : "UnknownErrorType";
+}
+
+} // namespace ot
diff --git a/src/core/common/error.hpp b/src/core/common/error.hpp
new file mode 100644
index 0000000..838bbb1
--- /dev/null
+++ b/src/core/common/error.hpp
@@ -0,0 +1,107 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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
+ *   This file defines the errors used by OpenThread core.
+ */
+
+#ifndef ERROR_HPP_
+#define ERROR_HPP_
+
+#include "openthread-core-config.h"
+
+#include <openthread/error.h>
+
+#include <stdint.h>
+
+namespace ot {
+
+/**
+ * This type represents error codes used by OpenThread core modules.
+ *
+ */
+typedef otError Error;
+
+/*
+ * The `OT_ERROR_*` enumeration values are re-defined using `kError` style format.
+ * See `openthread/error.h` for more details about each error.
+ *
+ */
+constexpr Error kErrorNone                       = OT_ERROR_NONE;
+constexpr Error kErrorFailed                     = OT_ERROR_FAILED;
+constexpr Error kErrorDrop                       = OT_ERROR_DROP;
+constexpr Error kErrorNoBufs                     = OT_ERROR_NO_BUFS;
+constexpr Error kErrorNoRoute                    = OT_ERROR_NO_ROUTE;
+constexpr Error kErrorBusy                       = OT_ERROR_BUSY;
+constexpr Error kErrorParse                      = OT_ERROR_PARSE;
+constexpr Error kErrorInvalidArgs                = OT_ERROR_INVALID_ARGS;
+constexpr Error kErrorSecurity                   = OT_ERROR_SECURITY;
+constexpr Error kErrorAddressQuery               = OT_ERROR_ADDRESS_QUERY;
+constexpr Error kErrorNoAddress                  = OT_ERROR_NO_ADDRESS;
+constexpr Error kErrorAbort                      = OT_ERROR_ABORT;
+constexpr Error kErrorNotImplemented             = OT_ERROR_NOT_IMPLEMENTED;
+constexpr Error kErrorInvalidState               = OT_ERROR_INVALID_STATE;
+constexpr Error kErrorNoAck                      = OT_ERROR_NO_ACK;
+constexpr Error kErrorChannelAccessFailure       = OT_ERROR_CHANNEL_ACCESS_FAILURE;
+constexpr Error kErrorDetached                   = OT_ERROR_DETACHED;
+constexpr Error kErrorFcs                        = OT_ERROR_FCS;
+constexpr Error kErrorNoFrameReceived            = OT_ERROR_NO_FRAME_RECEIVED;
+constexpr Error kErrorUnknownNeighbor            = OT_ERROR_UNKNOWN_NEIGHBOR;
+constexpr Error kErrorInvalidSourceAddress       = OT_ERROR_INVALID_SOURCE_ADDRESS;
+constexpr Error kErrorAddressFiltered            = OT_ERROR_ADDRESS_FILTERED;
+constexpr Error kErrorDestinationAddressFiltered = OT_ERROR_DESTINATION_ADDRESS_FILTERED;
+constexpr Error kErrorNotFound                   = OT_ERROR_NOT_FOUND;
+constexpr Error kErrorAlready                    = OT_ERROR_ALREADY;
+constexpr Error kErrorIp6AddressCreationFailure  = OT_ERROR_IP6_ADDRESS_CREATION_FAILURE;
+constexpr Error kErrorNotCapable                 = OT_ERROR_NOT_CAPABLE;
+constexpr Error kErrorResponseTimeout            = OT_ERROR_RESPONSE_TIMEOUT;
+constexpr Error kErrorDuplicated                 = OT_ERROR_DUPLICATED;
+constexpr Error kErrorReassemblyTimeout          = OT_ERROR_REASSEMBLY_TIMEOUT;
+constexpr Error kErrorNotTmf                     = OT_ERROR_NOT_TMF;
+constexpr Error kErrorNotLowpanDataFrame         = OT_ERROR_NOT_LOWPAN_DATA_FRAME;
+constexpr Error kErrorLinkMarginLow              = OT_ERROR_LINK_MARGIN_LOW;
+constexpr Error kErrorInvalidCommand             = OT_ERROR_INVALID_COMMAND;
+constexpr Error kErrorPending                    = OT_ERROR_PENDING;
+constexpr Error kErrorGeneric                    = OT_ERROR_GENERIC;
+
+constexpr uint8_t kNumErrors = OT_NUM_ERRORS;
+
+/**
+ * This function converts an `Error` into a string.
+ *
+ * @param[in]  aError     An error.
+ *
+ * @returns  A string representation of @p aError.
+ *
+ */
+const char *ErrorToString(Error aError);
+
+} // namespace ot
+
+#endif // ERROR_HPP_
diff --git a/src/core/common/instance.cpp b/src/core/common/instance.cpp
index 440dcdb..1e37ef8 100644
--- a/src/core/common/instance.cpp
+++ b/src/core/common/instance.cpp
@@ -37,6 +37,7 @@
 
 #include "common/logging.hpp"
 #include "common/new.hpp"
+#include "utils/heap.hpp"
 
 namespace ot {
 
@@ -48,15 +49,10 @@
 #endif
 
 #if OPENTHREAD_MTD || OPENTHREAD_FTD
-
-#if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
-
-otHeapFreeFn   ot::Instance::mFree   = nullptr;
-otHeapCAllocFn ot::Instance::mCAlloc = nullptr;
-
-#endif // OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
-
-#endif // OPENTHREAD_MTD || OPENTHREAD_FTD
+#if !OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
+Utils::Heap Instance::sHeap;
+#endif
+#endif
 
 Instance::Instance(void)
     : mTimerMilliScheduler(*this)
@@ -78,6 +74,9 @@
 #if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE
     , mApplicationCoapSecure(*this, /* aLayerTwoSecurity */ true)
 #endif
+#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
+    , mPingSender(*this)
+#endif
 #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
     , mChannelMonitor(*this)
 #endif
@@ -93,6 +92,9 @@
 #if OPENTHREAD_CONFIG_OTNS_ENABLE
     , mOtns(*this)
 #endif
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+    , mRoutingManager(*this)
+#endif
 #endif // OPENTHREAD_MTD || OPENTHREAD_FTD
 #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
     , mLinkRaw(*this)
@@ -215,11 +217,11 @@
     otPlatReset(this);
 }
 
-otError Instance::ErasePersistentInfo(void)
+Error Instance::ErasePersistentInfo(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(Get<Mle::MleRouter>().IsDisabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(Get<Mle::MleRouter>().IsDisabled(), error = kErrorInvalidState);
     Get<Settings>().Wipe();
 
 exit:
diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp
index 2cb4324..d2997a5 100644
--- a/src/core/common/instance.hpp
+++ b/src/core/common/instance.hpp
@@ -39,13 +39,13 @@
 #include <stdbool.h>
 #include <stdint.h>
 
-#include <openthread/error.h>
 #include <openthread/heap.h>
 #include <openthread/platform/logging.h>
-#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
+#if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
 #include <openthread/platform/memory.h>
 #endif
 
+#include "common/error.hpp"
 #include "common/non_copyable.hpp"
 #include "common/random_manager.hpp"
 #include "common/tasklet.hpp"
@@ -60,27 +60,28 @@
 #endif
 #if OPENTHREAD_FTD || OPENTHREAD_MTD
 #include "common/code_utils.hpp"
-#include "crypto/mbedtls.hpp"
-#include "thread/tmf.hpp"
-#if !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
-#include "utils/heap.hpp"
-#endif
 #include "common/notifier.hpp"
 #include "common/settings.hpp"
+#include "crypto/mbedtls.hpp"
 #include "meshcop/border_agent.hpp"
+#if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD
+#include "meshcop/dataset_updater.hpp"
+#endif
 #include "net/ip6.hpp"
 #include "thread/announce_sender.hpp"
 #include "thread/link_quality.hpp"
 #include "thread/thread_netif.hpp"
+#include "thread/tmf.hpp"
+#include "utils/heap.hpp"
+#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
+#include "utils/ping_sender.hpp"
+#endif
 #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD
 #include "utils/channel_manager.hpp"
 #endif
 #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
 #include "utils/channel_monitor.hpp"
 #endif
-#if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD
-#include "utils/dataset_updater.hpp"
-#endif
 
 #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
 #include "backbone_router/bbr_leader.hpp"
@@ -95,6 +96,10 @@
 
 #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
 
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+#include "border_router/routing_manager.hpp"
+#endif
+
 #endif // OPENTHREAD_FTD || OPENTHREAD_MTD
 #if OPENTHREAD_ENABLE_VENDOR_EXTENSION
 #include "common/extension.hpp"
@@ -236,35 +241,18 @@
      *
      * Erase is successful/allowed only if the device is in `disabled` state/role.
      *
-     * @retval OT_ERROR_NONE           All persistent info/state was erased successfully.
-     * @retval OT_ERROR_INVALID_STATE  Device is not in `disabled` state/role.
+     * @retval kErrorNone          All persistent info/state was erased successfully.
+     * @retval kErrorInvalidState  Device is not in `disabled` state/role.
      *
      */
-    otError ErasePersistentInfo(void);
+    Error ErasePersistentInfo(void);
 
 #if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
-    void HeapFree(void *aPointer)
-    {
-        OT_ASSERT(mFree != nullptr);
-
-        mFree(aPointer);
-    }
-
-    void *HeapCAlloc(size_t aCount, size_t aSize)
-    {
-        OT_ASSERT(mCAlloc != nullptr);
-
-        return mCAlloc(aCount, aSize);
-    }
-
-    static void HeapSetCAllocFree(otHeapCAllocFn aCAlloc, otHeapFreeFn aFree)
-    {
-        mFree   = aFree;
-        mCAlloc = aCAlloc;
-    }
-#elif !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
-    void  HeapFree(void *aPointer) { mHeap.Free(aPointer); }
-    void *HeapCAlloc(size_t aCount, size_t aSize) { return mHeap.CAlloc(aCount, aSize); }
+    static void  HeapFree(void *aPointer) { otPlatFree(aPointer); }
+    static void *HeapCAlloc(size_t aCount, size_t aSize) { return otPlatCAlloc(aCount, aSize); }
+#else
+    static void  HeapFree(void *aPointer) { sHeap.Free(aPointer); }
+    static void *HeapCAlloc(size_t aCount, size_t aSize) { return sHeap.CAlloc(aCount, aSize); }
 
     /**
      * This method returns a reference to the Heap object.
@@ -272,10 +260,7 @@
      * @returns A reference to the Heap object.
      *
      */
-    Utils::Heap &GetHeap(void) { return mHeap; }
-#else
-    void  HeapFree(void *aPointer) { otPlatFree(aPointer); }
-    void *HeapCAlloc(size_t aCount, size_t aSize) { return otPlatCAlloc(aCount, aSize); }
+    Utils::Heap &GetHeap(void) { return sHeap; }
 #endif // OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
 
 #if OPENTHREAD_CONFIG_COAP_API_ENABLE
@@ -334,11 +319,8 @@
 #if OPENTHREAD_MTD || OPENTHREAD_FTD
     // RandomManager is initialized before other objects. Note that it
     // requires MbedTls which itself may use Heap.
-#if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
-    static otHeapFreeFn   mFree;
-    static otHeapCAllocFn mCAlloc;
-#elif !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
-    Utils::Heap  mHeap;
+#if !OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
+    static Utils::Heap sHeap;
 #endif
     Crypto::MbedTls mMbedTls;
 #endif // OPENTHREAD_MTD || OPENTHREAD_FTD
@@ -371,6 +353,10 @@
     Coap::CoapSecure mApplicationCoapSecure;
 #endif
 
+#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
+    Utils::PingSender mPingSender;
+#endif
+
 #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
     Utils::ChannelMonitor mChannelMonitor;
 #endif
@@ -380,7 +366,7 @@
 #endif
 
 #if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD
-    Utils::DatasetUpdater mDatasetUpdater;
+    MeshCoP::DatasetUpdater mDatasetUpdater;
 #endif
 
 #if OPENTHREAD_CONFIG_ANNOUNCE_SENDER_ENABLE
@@ -391,6 +377,10 @@
     Utils::Otns mOtns;
 #endif
 
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+    BorderRouter::RoutingManager mRoutingManager;
+#endif
+
 #endif // OPENTHREAD_MTD || OPENTHREAD_FTD
 #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
     Mac::LinkRaw mLinkRaw;
@@ -561,7 +551,7 @@
     return mThreadNetif.mMeshForwarder.mIndirectSender.mDataPollHandler;
 }
 
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
 template <> inline CslTxScheduler &Instance::Get(void)
 {
     return mThreadNetif.mMeshForwarder.mIndirectSender.mCslTxScheduler;
@@ -623,6 +613,11 @@
 }
 #endif
 
+template <> inline NetworkData::Service::Manager &Instance::Get(void)
+{
+    return mThreadNetif.mNetworkDataServiceManager;
+}
+
 template <> inline Ip6::Udp &Instance::Get(void)
 {
     return mIp6.mUdp;
@@ -688,6 +683,20 @@
 }
 #endif
 
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+template <> inline Srp::Client &Instance::Get(void)
+{
+    return mThreadNetif.mSrpClient;
+}
+#endif
+
+#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
+template <> inline Dns::ServiceDiscovery::Server &Instance::Get(void)
+{
+    return mThreadNetif.mDnssdServer;
+}
+#endif
+
 #if OPENTHREAD_FTD || OPENTHREAD_CONFIG_TMF_NETWORK_DIAG_MTD_ENABLE
 template <> inline NetworkDiagnostic::NetworkDiagnostic &Instance::Get(void)
 {
@@ -740,6 +749,13 @@
     return mThreadNetif.mSupervisionListener;
 }
 
+#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
+template <> inline Utils::PingSender &Instance::Get(void)
+{
+    return mPingSender;
+}
+#endif
+
 #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
 template <> inline Utils::ChannelMonitor &Instance::Get(void)
 {
@@ -755,7 +771,7 @@
 #endif
 
 #if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD
-template <> inline Utils::DatasetUpdater &Instance::Get(void)
+template <> inline MeshCoP::DatasetUpdater &Instance::Get(void)
 {
     return mDatasetUpdater;
 }
@@ -847,6 +863,20 @@
 }
 #endif
 
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+template <> inline BorderRouter::RoutingManager &Instance::Get(void)
+{
+    return mRoutingManager;
+}
+#endif
+
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+template <> inline Srp::Server &Instance::Get(void)
+{
+    return mThreadNetif.mSrpServer;
+}
+#endif
+
 #endif // OPENTHREAD_MTD || OPENTHREAD_FTD
 
 #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
diff --git a/src/core/common/iterator_utils.hpp b/src/core/common/iterator_utils.hpp
new file mode 100644
index 0000000..dbee0aa
--- /dev/null
+++ b/src/core/common/iterator_utils.hpp
@@ -0,0 +1,169 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file includes definitions for a generic item-pointer iterator class.
+ */
+
+#ifndef ITERATOR_UTILS_HPP_
+#define ITERATOR_UTILS_HPP_
+
+namespace ot {
+
+/**
+ * @addtogroup core-iterator-utils
+ *
+ * @brief
+ *   This module includes definitions for OpenThread generic item-pointer iterator class.
+ *
+ * @{
+ *
+ */
+
+/**
+ * This template class is used as a base class for those item-pointer iterators.
+ *
+ * These iterators have common methods and operators like `Advance()` and `++` and hold a pointer to the
+ * object.
+ *
+ * Users of this class should follow CRTP-style inheritance, i.e., `IteratorType` class itself should publicly inherit
+ * from `ItemPtrIterator<ItemType, IteratorType>`.
+ *
+ * @tparam  ItemType      The type of the object that the iterator points to.
+ * @tparam  IteratorType  The Iterator class that inherits this class. The class MUST have a method `Advance()` which
+ *                        moves the pointer to the next. `Advance()` SHALL NOT be called when `IsDone()` is `true` and
+ *                        would set the pointer to `nullptr` when there's no more elements.
+ *
+ */
+template <class ItemType, class IteratorType> class ItemPtrIterator
+{
+public:
+    /**
+     * This method indicates whether there are no more items to be accessed (iterator has reached the end).
+     *
+     * @retval TRUE   There are no more items to be accessed (iterator has reached the end).
+     * @retval FALSE  The current item is valid.
+     *
+     */
+    bool IsDone(void) const { return mItem == nullptr; }
+
+    /**
+     * This method overloads `++` operator (pre-increment) to advance the iterator.
+     *
+     * The iterator is moved to point to the next item using IteratorType's `Advance` method.
+     * If there are no more items, the iterator becomes empty (i.e., `operator*` returns `nullptr` and `IsDone()`
+     * returns `true`).
+     *
+     */
+    void operator++(void) { static_cast<IteratorType *>(this)->Advance(); }
+
+    /**
+     * This method overloads `++` operator (post-increment) to advance the iterator.
+     *
+     * The iterator is moved to point to the next item using IteratorType's `Advance` method.
+     * If there are no more items, the iterator becomes empty (i.e., `operator*` returns `nullptr` and `IsDone()`
+     * returns `true`).
+     *
+     */
+    void operator++(int) { static_cast<IteratorType *>(this)->Advance(); }
+
+    /**
+     * This method overloads the `*` dereference operator and gets a reference to then item to which the iterator is
+     * currently pointing.
+     *
+     * This method MUST be used when the iterator is not empty/finished (i.e., `IsDone()` returns `false`).
+     *
+     * @returns A reference to the item currently pointed by the iterator.
+     *
+     */
+    ItemType &operator*(void) { return *mItem; }
+
+    /**
+     * This method overloads the `->` dereference operator and gets a pointer to the item to which the iterator is
+     * currently pointing.
+     *
+     * @returns A pointer to the item associated with the iterator, or `nullptr` if iterator is empty/done.
+     *
+     */
+    ItemType *operator->(void) { return mItem; }
+
+    /**
+     * This method overloads operator `==` to evaluate whether or not two `Iterator` instances point to the same
+     * item.
+     *
+     * @param[in]  aOther  The other `Iterator` to compare with.
+     *
+     * @retval TRUE   If the two `Iterator` objects point to the same item or both are done.
+     * @retval FALSE  If the two `Iterator` objects do not point to the same item.
+     *
+     */
+    bool operator==(const IteratorType &aOther) const { return mItem == aOther.mItem; }
+
+    /**
+     * This method overloads operator `!=` to evaluate whether or not two `Iterator` instances point to the same
+     * child entry.
+     *
+     * @param[in]  aOther  The other `Iterator` to compare with.
+     *
+     * @retval TRUE   If the two `Iterator` objects do not point to the same item.
+     * @retval FALSE  If the two `Iterator` objects point to the same item or both are done.
+     *
+     */
+    bool operator!=(const IteratorType &aOther) const { return mItem != aOther.mItem; }
+
+protected:
+    /**
+     * Default constructor
+     *
+     */
+    ItemPtrIterator(void)
+        : mItem(nullptr)
+    {
+    }
+
+    /**
+     * Contructor with an Item pointer.
+     *
+     */
+    explicit ItemPtrIterator(ItemType *item)
+        : mItem(item)
+    {
+    }
+
+    ItemType *mItem;
+};
+
+/**
+ * @}
+ *
+ */
+
+} // namespace ot
+
+#endif // ITERATOR_UTILS_HPP_
diff --git a/src/core/common/linked_list.hpp b/src/core/common/linked_list.hpp
index 08ef9a5..4e48597 100644
--- a/src/core/common/linked_list.hpp
+++ b/src/core/common/linked_list.hpp
@@ -37,7 +37,8 @@
 #include "openthread-core-config.h"
 
 #include <stdio.h>
-#include <openthread/error.h>
+
+#include "common/error.hpp"
 
 namespace ot {
 
@@ -239,7 +240,7 @@
     {
         const Type *prev;
 
-        return Find(aEntry, prev) == OT_ERROR_NONE;
+        return Find(aEntry, prev) == kErrorNone;
     }
 
     /**
@@ -267,17 +268,17 @@
      *
      * @param[in] aEntry   A reference to an entry to add.
      *
-     * @retval OT_ERROR_NONE     The entry was successfully added at the head of the list.
-     * @retval OT_ERROR_ALREADY  The entry is already in the list.
+     * @retval kErrorNone     The entry was successfully added at the head of the list.
+     * @retval kErrorAlready  The entry is already in the list.
      *
      */
-    otError Add(Type &aEntry)
+    Error Add(Type &aEntry)
     {
-        otError error = OT_ERROR_NONE;
+        Error error = kErrorNone;
 
         if (Contains(aEntry))
         {
-            error = OT_ERROR_ALREADY;
+            error = kErrorAlready;
         }
         else
         {
@@ -295,16 +296,16 @@
      *
      * @param[in] aEntry   A reference to an entry to remove.
      *
-     * @retval OT_ERROR_NONE       The entry was successfully removed from the list.
-     * @retval OT_ERROR_NOT_FOUND  Could not find the entry in the list.
+     * @retval kErrorNone      The entry was successfully removed from the list.
+     * @retval kErrorNotFound  Could not find the entry in the list.
      *
      */
-    otError Remove(const Type &aEntry)
+    Error Remove(const Type &aEntry)
     {
-        Type *  prev;
-        otError error = Find(aEntry, prev);
+        Type *prev;
+        Error error = Find(aEntry, prev);
 
-        if (error == OT_ERROR_NONE)
+        if (error == kErrorNone)
         {
             PopAfter(prev);
         }
@@ -351,13 +352,13 @@
      *                         @p aPrevEntry is set to nullptr if @p aEntry is the head of the list. Otherwise it is
      *                         updated to point to the previous entry before @p aEntry in the list.
      *
-     * @retval OT_ERROR_NONE       The entry was found in the list and @p aPrevEntry was updated successfully.
-     * @retval OT_ERROR_NOT_FOUND  The entry was not found in the list.
+     * @retval kErrorNone      The entry was found in the list and @p aPrevEntry was updated successfully.
+     * @retval kErrorNotFound  The entry was not found in the list.
      *
      */
-    otError Find(const Type &aEntry, const Type *&aPrevEntry) const
+    Error Find(const Type &aEntry, const Type *&aPrevEntry) const
     {
-        otError error = OT_ERROR_NOT_FOUND;
+        Error error = kErrorNotFound;
 
         aPrevEntry = nullptr;
 
@@ -365,7 +366,7 @@
         {
             if (entry == &aEntry)
             {
-                error = OT_ERROR_NONE;
+                error = kErrorNone;
                 break;
             }
         }
@@ -381,11 +382,11 @@
      *                         @p aPrevEntry is set to nullptr if @p aEntry is the head of the list. Otherwise it is
      *                         updated to point to the previous entry before @p aEntry in the list.
      *
-     * @retval OT_ERROR_NONE       The entry was found in the list and @p aPrevEntry was updated successfully.
-     * @retval OT_ERROR_NOT_FOUND  The entry was not found in the list.
+     * @retval kErrorNone      The entry was found in the list and @p aPrevEntry was updated successfully.
+     * @retval kErrorNotFound  The entry was not found in the list.
      *
      */
-    otError Find(const Type &aEntry, Type *&aPrevEntry)
+    Error Find(const Type &aEntry, Type *&aPrevEntry)
     {
         return const_cast<const LinkedList *>(this)->Find(aEntry, const_cast<const Type *&>(aPrevEntry));
     }
diff --git a/src/core/common/locator-getters.hpp b/src/core/common/locator-getters.hpp
index 43ed54c..dc87b40 100644
--- a/src/core/common/locator-getters.hpp
+++ b/src/core/common/locator-getters.hpp
@@ -63,15 +63,6 @@
     return GetInstance().Get<Type>();
 }
 
-#if !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
-
-template <typename OwnerType> OwnerType &OwnerLocator::GetOwner(void)
-{
-    return Instance::Get().Get<OwnerType>();
-}
-
-#endif
-
 } // namespace ot
 
 #endif // LOCATOR_GETTERS_HPP_
diff --git a/src/core/common/locator.hpp b/src/core/common/locator.hpp
index e2bc32b..30350c3 100644
--- a/src/core/common/locator.hpp
+++ b/src/core/common/locator.hpp
@@ -162,54 +162,6 @@
 };
 
 /**
- * This class implements a locator for owner of an object.
- *
- * This is used as the base class for objects that provide a callback (e.g., `Timer` or `Tasklet`).
- *
- */
-class OwnerLocator
-{
-public:
-    /**
-     * This template method returns a reference to the owner object.
-     *
-     * The caller needs to provide the `OwnerType` as part of the template type.
-     *
-     * @returns A reference to the owner of this object.
-     *
-     */
-    template <typename OwnerType> OwnerType &GetOwner(void)
-#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
-    {
-        return *static_cast<OwnerType *>(mOwner);
-    }
-#else
-    // Implemented in `locator-getters.hpp`
-    ;
-#endif
-
-protected:
-    /**
-     * This constructor initializes the object.
-     *
-     * @param[in]  aOwner   A pointer to the owner object (as `void *`).
-     *
-     */
-    explicit OwnerLocator(void *aOwner)
-#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
-        : mOwner(aOwner)
-#endif
-    {
-        OT_UNUSED_VARIABLE(aOwner);
-    }
-
-#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
-private:
-    void *mOwner;
-#endif
-};
-
-/**
  * @}
  *
  */
diff --git a/src/core/common/logging.cpp b/src/core/common/logging.cpp
index ec0fe04..42be86d 100644
--- a/src/core/common/logging.cpp
+++ b/src/core/common/logging.cpp
@@ -101,7 +101,7 @@
 #endif // OPENTHREAD_CONFIG_LOG_PREPEND_LEVEL
 
     IgnoreError(logString.Append("%s", aRegionPrefix));
-    VerifyOrExit(logString.AppendVarArgs(aFormat, aArgs) != OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(logString.AppendVarArgs(aFormat, aArgs) != ot::kErrorInvalidArgs);
     otPlatLog(aLogLevel, aLogRegion, "%s" OPENTHREAD_CONFIG_LOG_SUFFIX, logString.AsCString());
 
 exit:
@@ -318,51 +318,6 @@
 }
 #endif // OPENTHREAD_CONFIG_LOG_PKT_DUMP
 
-static const char *const sThreadErrorStrings[OT_NUM_ERRORS] = {
-    "OK",                         // OT_ERROR_NONE = 0
-    "Failed",                     // OT_ERROR_FAILED = 1
-    "Drop",                       // OT_ERROR_DROP = 2
-    "NoBufs",                     // OT_ERROR_NO_BUFS = 3
-    "NoRoute",                    // OT_ERROR_NO_ROUTE = 4
-    "Busy",                       // OT_ERROR_BUSY = 5
-    "Parse",                      // OT_ERROR_PARSE = 6
-    "InvalidArgs",                // OT_ERROR_INVALID_ARGS = 7
-    "Security",                   // OT_ERROR_SECURITY = 8
-    "AddressQuery",               // OT_ERROR_ADDRESS_QUERY = 9
-    "NoAddress",                  // OT_ERROR_NO_ADDRESS = 10
-    "Abort",                      // OT_ERROR_ABORT = 11
-    "NotImplemented",             // OT_ERROR_NOT_IMPLEMENTED = 12
-    "InvalidState",               // OT_ERROR_INVALID_STATE = 13
-    "NoAck",                      // OT_ERROR_NO_ACK = 14
-    "ChannelAccessFailure",       // OT_ERROR_CHANNEL_ACCESS_FAILURE = 15
-    "Detached",                   // OT_ERROR_DETACHED = 16
-    "FcsErr",                     // OT_ERROR_FCS = 17
-    "NoFrameReceived",            // OT_ERROR_NO_FRAME_RECEIVED = 18
-    "UnknownNeighbor",            // OT_ERROR_UNKNOWN_NEIGHBOR = 19
-    "InvalidSourceAddress",       // OT_ERROR_INVALID_SOURCE_ADDRESS = 20
-    "AddressFiltered",            // OT_ERROR_ADDRESS_FILTERED = 21
-    "DestinationAddressFiltered", // OT_ERROR_DESTINATION_ADDRESS_FILTERED = 22
-    "NotFound",                   // OT_ERROR_NOT_FOUND = 23
-    "Already",                    // OT_ERROR_ALREADY = 24
-    "ReservedError25",            // otError 25 is reserved
-    "Ipv6AddressCreationFailure", // OT_ERROR_IP6_ADDRESS_CREATION_FAILURE = 26
-    "NotCapable",                 // OT_ERROR_NOT_CAPABLE = 27
-    "ResponseTimeout",            // OT_ERROR_RESPONSE_TIMEOUT = 28
-    "Duplicated",                 // OT_ERROR_DUPLICATED = 29
-    "ReassemblyTimeout",          // OT_ERROR_REASSEMBLY_TIMEOUT = 30
-    "NotTmf",                     // OT_ERROR_NOT_TMF = 31
-    "NonLowpanDataFrame",         // OT_ERROR_NOT_LOWPAN_DATA_FRAME = 32
-    "ReservedError33",            // otError 33 is reserved
-    "LinkMarginLow",              // OT_ERROR_LINK_MARGIN_LOW = 34
-    "InvalidCommand",             // OT_ERROR_INVALID_COMMAND = 35
-    "Pending",                    // OT_ERROR_PENDING = 36
-};
-
-const char *otThreadErrorToString(otError aError)
-{
-    return aError < OT_ARRAY_LENGTH(sThreadErrorStrings) ? sThreadErrorStrings[aError] : "UnknownErrorType";
-}
-
 #if OPENTHREAD_CONFIG_LOG_DEFINE_AS_MACRO_ONLY
 
 const char *otLogLevelToPrefixString(otLogLevel aLogLevel)
diff --git a/src/core/common/logging.hpp b/src/core/common/logging.hpp
index 2dd5a4e..89d49f4 100644
--- a/src/core/common/logging.hpp
+++ b/src/core/common/logging.hpp
@@ -94,6 +94,9 @@
 #define _OT_REGION_BBR_PREFIX "-BBR-----: "
 #define _OT_REGION_MLR_PREFIX "-MLR-----: "
 #define _OT_REGION_DUA_PREFIX "-DUA-----: "
+#define _OT_REGION_BR_PREFIX "-BR------: "
+#define _OT_REGION_SRP_PREFIX "-SRP-----: "
+#define _OT_REGION_DNS_PREFIX "-DNS-----: "
 #else
 #define _OT_REGION_API_PREFIX _OT_REGION_SUFFIX
 #define _OT_REGION_MLE_PREFIX _OT_REGION_SUFFIX
@@ -114,6 +117,9 @@
 #define _OT_REGION_BBR_PREFIX _OT_REGION_SUFFIX
 #define _OT_REGION_MLR_PREFIX _OT_REGION_SUFFIX
 #define _OT_REGION_DUA_PREFIX _OT_REGION_SUFFIX
+#define _OT_REGION_BR_PREFIX _OT_REGION_SUFFIX
+#define _OT_REGION_SRP_PREFIX _OT_REGION_SUFFIX
+#define _OT_REGION_DNS_PREFIX _OT_REGION_SUFFIX
 #endif
 
 /**
@@ -330,6 +336,64 @@
 #define otLogDebgMbedTls(...) otLogDebgMeshCoP(__VA_ARGS__)
 
 /**
+ * @def otLogCritBr
+ *
+ * This function generates a log with level critical for the BR region.
+ *
+ * @param[in]  ...  Arguments for the format specification.
+ *
+ */
+
+/**
+ * @def otLogWarnBr
+ *
+ * This function generates a log with level warning for the BR region.
+ *
+ * @param[in]  ...  Arguments for the format specification.
+ *
+ */
+
+/**
+ * @def otLogNoteBr
+ *
+ * This function generates a log with level note for the BR region.
+ *
+ * @param[in]  ...  Arguments for the format specification.
+ *
+ */
+
+/**
+ * @def otLogInfoBr
+ *
+ * This function generates a log with level info for the BR region.
+ *
+ * @param[in]  ...  Arguments for the format specification.
+ *
+ */
+
+/**
+ * @def otLogDebgBr
+ *
+ * This function generates a log with level debug for the BR region.
+ *
+ * @param[in]  ...  Arguments for the format specification.
+ *
+ */
+#if OPENTHREAD_CONFIG_LOG_BR
+#define otLogCritBr(...) otLogCrit(OT_LOG_REGION_BR, _OT_REGION_BR_PREFIX, __VA_ARGS__)
+#define otLogWarnBr(...) otLogWarn(OT_LOG_REGION_BR, _OT_REGION_BR_PREFIX, __VA_ARGS__)
+#define otLogNoteBr(...) otLogNote(OT_LOG_REGION_BR, _OT_REGION_BR_PREFIX, __VA_ARGS__)
+#define otLogInfoBr(...) otLogInfo(OT_LOG_REGION_BR, _OT_REGION_BR_PREFIX, __VA_ARGS__)
+#define otLogDebgBr(...) otLogDebg(OT_LOG_REGION_BR, _OT_REGION_BR_PREFIX, __VA_ARGS__)
+#else
+#define otLogCritBr(...)
+#define otLogWarnBr(...)
+#define otLogNoteBr(...)
+#define otLogInfoBr(...)
+#define otLogDebgBr(...)
+#endif
+
+/**
  * @def otLogCritMle
  *
  * This function generates a log with level critical for the MLE region.
@@ -1243,6 +1307,122 @@
 #endif
 
 /**
+ * @def otLogCritSrp
+ *
+ * This function generates a log with level critical for the Service Registration Protocol (SRP) region.
+ *
+ * @param[in]  ...  Arguments for the format specification.
+ *
+ */
+
+/**
+ * @def otLogWarnSrp
+ *
+ * This function generates a log with level warning for the Service Registration Protocol (SRP) region.
+ *
+ * @param[in]  ...  Arguments for the format specification.
+ *
+ */
+
+/**
+ * @def otLogNoteSrp
+ *
+ * This function generates a log with level note for the Service Registration Protocol (SRP) region.
+ *
+ * @param[in]  ...  Arguments for the format specification.
+ *
+ */
+
+/**
+ * @def otLogInfoSrp
+ *
+ * This function generates a log with level info for the Service Registration Protocol (SRP) region.
+ *
+ * @param[in]  ...  Arguments for the format specification.
+ *
+ */
+
+/**
+ * @def otLogDebgSrp
+ *
+ * This function generates a log with level debug for the Service Registration Protocol (SRP) region.
+ *
+ * @param[in]  ...  Arguments for the format specification.
+ *
+ */
+#if OPENTHREAD_CONFIG_LOG_SRP
+#define otLogCritSrp(...) otLogCrit(OT_LOG_REGION_SRP, _OT_REGION_SRP_PREFIX, __VA_ARGS__)
+#define otLogWarnSrp(...) otLogWarn(OT_LOG_REGION_SRP, _OT_REGION_SRP_PREFIX, __VA_ARGS__)
+#define otLogNoteSrp(...) otLogNote(OT_LOG_REGION_SRP, _OT_REGION_SRP_PREFIX, __VA_ARGS__)
+#define otLogInfoSrp(...) otLogInfo(OT_LOG_REGION_SRP, _OT_REGION_SRP_PREFIX, __VA_ARGS__)
+#define otLogDebgSrp(...) otLogDebg(OT_LOG_REGION_SRP, _OT_REGION_SRP_PREFIX, __VA_ARGS__)
+#else
+#define otLogCritSrp(...)
+#define otLogWarnSrp(...)
+#define otLogNoteSrp(...)
+#define otLogInfoSrp(...)
+#define otLogDebgSrp(...)
+#endif
+
+/**
+ * @def otLogCritDns
+ *
+ * This function generates a log with level critical for the DNS region.
+ *
+ * @param[in]  ...  Arguments for the format specification.
+ *
+ */
+
+/**
+ * @def otLogWarnDns
+ *
+ * This function generates a log with level warning for the DNS region.
+ *
+ * @param[in]  ...  Arguments for the format specification.
+ *
+ */
+
+/**
+ * @def otLogNoteDns
+ *
+ * This function generates a log with level note for the DNS region.
+ *
+ * @param[in]  ...  Arguments for the format specification.
+ *
+ */
+
+/**
+ * @def otLogInfoDns
+ *
+ * This function generates a log with level info for the DNS region.
+ *
+ * @param[in]  ...  Arguments for the format specification.
+ *
+ */
+
+/**
+ * @def otLogDebgDns
+ *
+ * This function generates a log with level debug for the DNS region.
+ *
+ * @param[in]  ...  Arguments for the format specification.
+ *
+ */
+#if OPENTHREAD_CONFIG_LOG_DNS
+#define otLogCritDns(...) otLogCrit(OT_LOG_REGION_DNS, _OT_REGION_DNS_PREFIX, __VA_ARGS__)
+#define otLogWarnDns(...) otLogWarn(OT_LOG_REGION_DNS, _OT_REGION_DNS_PREFIX, __VA_ARGS__)
+#define otLogNoteDns(...) otLogNote(OT_LOG_REGION_DNS, _OT_REGION_DNS_PREFIX, __VA_ARGS__)
+#define otLogInfoDns(...) otLogInfo(OT_LOG_REGION_DNS, _OT_REGION_DNS_PREFIX, __VA_ARGS__)
+#define otLogDebgDns(...) otLogDebg(OT_LOG_REGION_DNS, _OT_REGION_DNS_PREFIX, __VA_ARGS__)
+#else
+#define otLogCritDns(...)
+#define otLogWarnDns(...)
+#define otLogNoteDns(...)
+#define otLogInfoDns(...)
+#define otLogDebgDns(...)
+#endif
+
+/**
  * @def otLogCritPlat
  *
  * This function generates a log with level critical for the Platform region.
@@ -2078,6 +2258,142 @@
 #endif
 
 /**
+ * @def otDumpCritSrp
+ *
+ * This function generates a memory dump with log level critical and region Service Registration Protocol (SRP).
+ *
+ * @param[in]  aId          A pointer to a NULL-terminated string that is printed before the bytes.
+ * @param[in]  aBuf         A pointer to the buffer.
+ * @param[in]  aLength      Number of bytes to print.
+ *
+ */
+
+/**
+ * @def otDumpWarnSrp
+ *
+ * This function generates a memory dump with log level warning and region Service Registration Protocol (SRP).
+ *
+ * @param[in]  aId          A pointer to a NULL-terminated string that is printed before the bytes.
+ * @param[in]  aBuf         A pointer to the buffer.
+ * @param[in]  aLength      Number of bytes to print.
+ *
+ */
+
+/**
+ * @def otDumpNoteSrp
+ *
+ * This function generates a memory dump with log level note and region Service Registration Protocol (SRP).
+ *
+ * @param[in]  aId          A pointer to a NULL-terminated string that is printed before the bytes.
+ * @param[in]  aBuf         A pointer to the buffer.
+ * @param[in]  aLength      Number of bytes to print.
+ *
+ */
+
+/**
+ * @def otDumpInfoSrp
+ *
+ * This function generates a memory dump with log level info and region Service Registration Protocol (SRP).
+ *
+ * @param[in]  aId          A pointer to a NULL-terminated string that is printed before the bytes.
+ * @param[in]  aBuf         A pointer to the buffer.
+ * @param[in]  aLength      Number of bytes to print.
+ *
+ */
+
+/**
+ * @def otDumpDebgSrp
+ *
+ * This function generates a memory dump with log level debug and region Service Registration Protocol (SRP).
+ *
+ * @param[in]  aId          A pointer to a NULL-terminated string that is printed before the bytes.
+ * @param[in]  aBuf         A pointer to the buffer.
+ * @param[in]  aLength      Number of bytes to print.
+ *
+ */
+#if OPENTHREAD_CONFIG_LOG_SRP
+#define otDumpCritSrp(aId, aBuf, aLength) otDumpCrit(OT_LOG_REGION_SRP, aId, aBuf, aLength)
+#define otDumpWarnSrp(aId, aBuf, aLength) otDumpWarn(OT_LOG_REGION_SRP, aId, aBuf, aLength)
+#define otDumpNoteSrp(aId, aBuf, aLength) otDumpNote(OT_LOG_REGION_SRP, aId, aBuf, aLength)
+#define otDumpInfoSrp(aId, aBuf, aLength) otDumpInfo(OT_LOG_REGION_SRP, aId, aBuf, aLength)
+#define otDumpDebgSrp(aId, aBuf, aLength) otDumpDebg(OT_LOG_REGION_SRP, aId, aBuf, aLength)
+#else
+#define otDumpCritSrp(aId, aBuf, aLength)
+#define otDumpWarnSrp(aId, aBuf, aLength)
+#define otDumpNoteSrp(aId, aBuf, aLength)
+#define otDumpInfoSrp(aId, aBuf, aLength)
+#define otDumpDebgSrp(aId, aBuf, aLength)
+#endif
+
+/**
+ * @def otDumpCritDns
+ *
+ * This function generates a memory dump with log level critical and region DNS.
+ *
+ * @param[in]  aId          A pointer to a NULL-terminated string that is printed before the bytes.
+ * @param[in]  aBuf         A pointer to the buffer.
+ * @param[in]  aLength      Number of bytes to print.
+ *
+ */
+
+/**
+ * @def otDumpWarnDns
+ *
+ * This function generates a memory dump with log level warning and region DNS.
+ *
+ * @param[in]  aId          A pointer to a NULL-terminated string that is printed before the bytes.
+ * @param[in]  aBuf         A pointer to the buffer.
+ * @param[in]  aLength      Number of bytes to print.
+ *
+ */
+
+/**
+ * @def otDumpNoteDns
+ *
+ * This function generates a memory dump with log level note and region DNS.
+ *
+ * @param[in]  aId          A pointer to a NULL-terminated string that is printed before the bytes.
+ * @param[in]  aBuf         A pointer to the buffer.
+ * @param[in]  aLength      Number of bytes to print.
+ *
+ */
+
+/**
+ * @def otDumpInfoDns
+ *
+ * This function generates a memory dump with log level info and region DNS.
+ *
+ * @param[in]  aId          A pointer to a NULL-terminated string that is printed before the bytes.
+ * @param[in]  aBuf         A pointer to the buffer.
+ * @param[in]  aLength      Number of bytes to print.
+ *
+ */
+
+/**
+ * @def otDumpDebgDns
+ *
+ * This function generates a memory dump with log level debug and region DNS.
+ *
+ * @param[in]  aId          A pointer to a NULL-terminated string that is printed before the bytes.
+ * @param[in]  aBuf         A pointer to the buffer.
+ * @param[in]  aLength      Number of bytes to print.
+ *
+ */
+#if OPENTHREAD_CONFIG_LOG_SRP
+#define otDumpCritDns(aId, aBuf, aLength) otDumpCrit(OT_LOG_REGION_SRP, aId, aBuf, aLength)
+#define otDumpWarnDns(aId, aBuf, aLength) otDumpWarn(OT_LOG_REGION_SRP, aId, aBuf, aLength)
+#define otDumpNoteDns(aId, aBuf, aLength) otDumpNote(OT_LOG_REGION_SRP, aId, aBuf, aLength)
+#define otDumpInfoDns(aId, aBuf, aLength) otDumpInfo(OT_LOG_REGION_SRP, aId, aBuf, aLength)
+#define otDumpDebgDns(aId, aBuf, aLength) otDumpDebg(OT_LOG_REGION_SRP, aId, aBuf, aLength)
+#else
+#define otDumpCritDns(aId, aBuf, aLength)
+#define otDumpWarnDns(aId, aBuf, aLength)
+#define otDumpNoteDns(aId, aBuf, aLength)
+#define otDumpInfoDns(aId, aBuf, aLength)
+#define otDumpDebgDns(aId, aBuf, aLength)
+#endif
+
+/**
  * @def otDumpCritMem
  *
  * This function generates a memory dump with log level debug and region memory.
@@ -2193,6 +2509,10 @@
 
 #if OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE
 
+#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
+#error "Dynamic log level is not supported along with multiple OT instance feature"
+#endif
+
 /**
  * Local/private macro to dynamically filter log level.
  */
@@ -2238,14 +2558,26 @@
 /**
  * @def otLogResultPlat
  *
- * This function generates a log for the Plat region according to the error result. If @p aError is `OT_ERROR_NONE`, the
+ * This function generates a log for the Plat region according to the error result. If @p aError is `kErrorNone`, the
  * log level is info. Otherwise the log level is warn.
  *
  * @param[in]  aError    The error result.
  * @param[in]  ...       Arguments for the format specification.
  *
  */
-#define otLogResultPlat(aError, ...) _otLogResult(Plat, aError, OT_FIRST_ARG(__VA_ARGS__) OT_REST_ARGS(__VA_ARGS__))
+#define otLogResultPlat(aError, ...) _otLogResult(Plat, aError, __VA_ARGS__)
+
+/**
+ * @def otLogResultBbr
+ *
+ * This function generates a log for the BBR region according to the error result. If @p aError is `OT_ERROR_NONE`, the
+ * log level is info. Otherwise the log level is warn.
+ *
+ * @param[in]  aError    The error result.
+ * @param[in]  ...       Arguments for the format specification.
+ *
+ */
+#define otLogResultBbr(aError, ...) _otLogResult(Bbr, aError, __VA_ARGS__)
 
 #ifdef __cplusplus
 }
diff --git a/src/core/common/message.cpp b/src/core/common/message.cpp
index 3aeb1c8..05f2bf7 100644
--- a/src/core/common/message.cpp
+++ b/src/core/common/message.cpp
@@ -43,6 +43,10 @@
 
 #if OPENTHREAD_MTD || OPENTHREAD_FTD
 
+#if OPENTHREAD_CONFIG_MESSAGE_USE_HEAP_ENABLE && OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT
+#error "OPENTHREAD_CONFIG_MESSAGE_USE_HEAP_ENABLE conflicts with OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT."
+#endif
+
 #if OPENTHREAD_CONFIG_MESSAGE_USE_HEAP_ENABLE && !OPENTHREAD_CONFIG_DTLS_ENABLE
 #error "OPENTHREAD_CONFIG_MESSAGE_USE_HEAP_ENABLE is strongly discouraged when OPENTHREAD_CONFIG_DTLS_ENABLE is off."
 #endif
@@ -62,7 +66,7 @@
 
 Message *MessagePool::New(Message::Type aType, uint16_t aReserveHeader, Message::Priority aPriority)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     Message *message;
 
     VerifyOrExit((message = static_cast<Message *>(NewBuffer(aPriority))) != nullptr);
@@ -77,7 +81,7 @@
     SuccessOrExit(error = message->SetLength(0));
 
 exit:
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         Free(message);
         message = nullptr;
@@ -111,7 +115,7 @@
 
     while ((
 #if OPENTHREAD_CONFIG_MESSAGE_USE_HEAP_ENABLE
-               buffer = static_cast<Buffer *>(GetInstance().HeapCAlloc(sizeof(Buffer), 1))
+               buffer = static_cast<Buffer *>(Instance::HeapCAlloc(1, sizeof(Buffer)))
 #elif OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT
                buffer = static_cast<Buffer *>(otPlatMessagePoolNew(&GetInstance()))
 #else
@@ -143,7 +147,7 @@
     {
         Buffer *next = aBuffer->GetNextBuffer();
 #if OPENTHREAD_CONFIG_MESSAGE_USE_HEAP_ENABLE
-        GetInstance().HeapFree(aBuffer);
+        Instance::HeapFree(aBuffer);
 #elif OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT
         otPlatMessagePoolFree(&GetInstance(), aBuffer);
 #else
@@ -154,7 +158,7 @@
     }
 }
 
-otError MessagePool::ReclaimBuffers(Message::Priority aPriority)
+Error MessagePool::ReclaimBuffers(Message::Priority aPriority)
 {
     return Get<MeshForwarder>().EvictMessage(aPriority);
 }
@@ -197,9 +201,9 @@
 {
 }
 
-otError Message::ResizeMessage(uint16_t aLength)
+Error Message::ResizeMessage(uint16_t aLength)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     // add buffers
     Buffer * curBuffer = this;
@@ -211,7 +215,7 @@
         if (curBuffer->GetNextBuffer() == nullptr)
         {
             curBuffer->SetNextBuffer(GetMessagePool()->NewBuffer(GetPriority()));
-            VerifyOrExit(curBuffer->GetNextBuffer() != nullptr, error = OT_ERROR_NO_BUFS);
+            VerifyOrExit(curBuffer->GetNextBuffer() != nullptr, error = kErrorNoBufs);
         }
 
         curBuffer = curBuffer->GetNextBuffer();
@@ -258,12 +262,12 @@
     return next;
 }
 
-otError Message::SetLength(uint16_t aLength)
+Error Message::SetLength(uint16_t aLength)
 {
-    otError  error              = OT_ERROR_NONE;
+    Error    error              = kErrorNone;
     uint16_t totalLengthRequest = GetReserved() + aLength;
 
-    VerifyOrExit(totalLengthRequest >= GetReserved(), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(totalLengthRequest >= GetReserved(), error = kErrorInvalidArgs);
 
     SuccessOrExit(error = ResizeMessage(totalLengthRequest));
     GetMetadata().mLength = aLength;
@@ -327,13 +331,13 @@
     return rval;
 }
 
-otError Message::SetPriority(Priority aPriority)
+Error Message::SetPriority(Priority aPriority)
 {
-    otError        error         = OT_ERROR_NONE;
+    Error          error         = kErrorNone;
     uint8_t        priority      = static_cast<uint8_t>(aPriority);
     PriorityQueue *priorityQueue = nullptr;
 
-    VerifyOrExit(priority < kNumPriorities, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(priority < kNumPriorities, error = kErrorInvalidArgs);
 
     VerifyOrExit(IsInAQueue(), GetMetadata().mPriority = priority);
     VerifyOrExit(GetMetadata().mPriority != priority);
@@ -355,9 +359,26 @@
     return error;
 }
 
-otError Message::AppendBytes(const void *aBuf, uint16_t aLength)
+const char *Message::PriorityToString(Priority aPriority)
 {
-    otError  error     = OT_ERROR_NONE;
+    static const char *const kPriorityStrings[] = {
+        "low",    // (0) kPriorityLow
+        "normal", // (1) kPriorityNormal
+        "high",   // (2) kPriorityHigh
+        "net",    // (3) kPriorityNet
+    };
+
+    static_assert(kPriorityLow == 0, "kPriorityLow value is incorrect");
+    static_assert(kPriorityNormal == 1, "kPriorityNormal value is incorrect");
+    static_assert(kPriorityHigh == 2, "kPriorityHigh value is incorrect");
+    static_assert(kPriorityNet == 3, "kPriorityNet value is incorrect");
+
+    return kPriorityStrings[aPriority];
+}
+
+Error Message::AppendBytes(const void *aBuf, uint16_t aLength)
+{
+    Error    error     = kErrorNone;
     uint16_t oldLength = GetLength();
 
     SuccessOrExit(error = SetLength(GetLength() + aLength));
@@ -367,14 +388,14 @@
     return error;
 }
 
-otError Message::PrependBytes(const void *aBuf, uint16_t aLength)
+Error Message::PrependBytes(const void *aBuf, uint16_t aLength)
 {
-    otError error     = OT_ERROR_NONE;
+    Error   error     = kErrorNone;
     Buffer *newBuffer = nullptr;
 
     while (aLength > GetReserved())
     {
-        VerifyOrExit((newBuffer = GetMessagePool()->NewBuffer(GetPriority())) != nullptr, error = OT_ERROR_NO_BUFS);
+        VerifyOrExit((newBuffer = GetMessagePool()->NewBuffer(GetPriority())) != nullptr, error = kErrorNoBufs);
 
         newBuffer->SetNextBuffer(GetNextBuffer());
         SetNextBuffer(newBuffer);
@@ -520,9 +541,51 @@
     return static_cast<uint16_t>(bufPtr - reinterpret_cast<uint8_t *>(aBuf));
 }
 
-otError Message::Read(uint16_t aOffset, void *aBuf, uint16_t aLength) const
+Error Message::Read(uint16_t aOffset, void *aBuf, uint16_t aLength) const
 {
-    return (ReadBytes(aOffset, aBuf, aLength) == aLength) ? OT_ERROR_NONE : OT_ERROR_PARSE;
+    return (ReadBytes(aOffset, aBuf, aLength) == aLength) ? kErrorNone : kErrorParse;
+}
+
+bool Message::CompareBytes(uint16_t aOffset, const void *aBuf, uint16_t aLength) const
+{
+    uint16_t       bytesToCompare = aLength;
+    const uint8_t *bufPtr         = reinterpret_cast<const uint8_t *>(aBuf);
+    Chunk          chunk;
+
+    GetFirstChunk(aOffset, aLength, chunk);
+
+    while (chunk.GetLength() > 0)
+    {
+        VerifyOrExit(memcmp(bufPtr, chunk.GetData(), chunk.GetLength()) == 0);
+        bufPtr += chunk.GetLength();
+        bytesToCompare -= chunk.GetLength();
+        GetNextChunk(aLength, chunk);
+    }
+
+exit:
+    return (bytesToCompare == 0);
+}
+
+bool Message::CompareBytes(uint16_t       aOffset,
+                           const Message &aOtherMessage,
+                           uint16_t       aOtherOffset,
+                           uint16_t       aLength) const
+{
+    uint16_t bytesToCompare = aLength;
+    Chunk    chunk;
+
+    GetFirstChunk(aOffset, aLength, chunk);
+
+    while (chunk.GetLength() > 0)
+    {
+        VerifyOrExit(aOtherMessage.CompareBytes(aOtherOffset, chunk.GetData(), chunk.GetLength()));
+        aOtherOffset += chunk.GetLength();
+        bytesToCompare -= chunk.GetLength();
+        GetNextChunk(aLength, chunk);
+    }
+
+exit:
+    return (bytesToCompare == 0);
 }
 
 void Message::WriteBytes(uint16_t aOffset, const void *aBuf, uint16_t aLength)
@@ -569,12 +632,12 @@
 
 Message *Message::Clone(uint16_t aLength) const
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     Message *messageCopy;
     uint16_t offset;
 
     VerifyOrExit((messageCopy = GetMessagePool()->New(GetType(), GetReserved(), GetPriority())) != nullptr,
-                 error = OT_ERROR_NO_BUFS);
+                 error = kErrorNoBufs);
     SuccessOrExit(error = messageCopy->SetLength(aLength));
     CopyTo(0, 0, aLength, *messageCopy);
 
diff --git a/src/core/common/message.hpp b/src/core/common/message.hpp
index 77a4ffa..f2a16c3 100644
--- a/src/core/common/message.hpp
+++ b/src/core/common/message.hpp
@@ -52,8 +52,23 @@
 #include "thread/child_mask.hpp"
 #include "thread/link_quality.hpp"
 
+/**
+ * This struct represents an opaque (and empty) type for an OpenThread message buffer.
+ *
+ */
+struct otMessage
+{
+};
+
 namespace ot {
 
+namespace Crypto {
+
+class Sha256;
+class HmacSha256;
+
+} // namespace Crypto
+
 /**
  * @addtogroup core-message
  *
@@ -85,38 +100,38 @@
     } while (false)
 
 /**
- * This macro frees a given message buffer if a given `otError` indicates an error.
+ * This macro frees a given message buffer if a given `Error` indicates an error.
  *
  * The parameter @p aMessage can be nullptr in which case this macro does nothing.
  *
  * @param[in] aMessage    A pointer to a `Message` to free (can be nullptr).
- * @param[in] aError      The `otError` to check.
+ * @param[in] aError      The `Error` to check.
  *
  */
-#define FreeMessageOnError(aMessage, aError)                        \
-    do                                                              \
-    {                                                               \
-        if (((aError) != OT_ERROR_NONE) && ((aMessage) != nullptr)) \
-        {                                                           \
-            (aMessage)->Free();                                     \
-        }                                                           \
+#define FreeMessageOnError(aMessage, aError)                     \
+    do                                                           \
+    {                                                            \
+        if (((aError) != kErrorNone) && ((aMessage) != nullptr)) \
+        {                                                        \
+            (aMessage)->Free();                                  \
+        }                                                        \
     } while (false)
 
 /**
- * This macro frees a given message buffer if a given `otError` indicates an error and sets the `aMessage` to `nullptr`.
+ * This macro frees a given message buffer if a given `Error` indicates an error and sets the `aMessage` to `nullptr`.
  *
  * @param[in] aMessage    A pointer to a `Message` to free (can be nullptr).
- * @param[in] aError      The `otError` to check.
+ * @param[in] aError      The `Error` to check.
  *
  */
-#define FreeAndNullMessageOnError(aMessage, aError)                 \
-    do                                                              \
-    {                                                               \
-        if (((aError) != OT_ERROR_NONE) && ((aMessage) != nullptr)) \
-        {                                                           \
-            (aMessage)->Free();                                     \
-            (aMessage) = nullptr;                                   \
-        }                                                           \
+#define FreeAndNullMessageOnError(aMessage, aError)              \
+    do                                                           \
+    {                                                            \
+        if (((aError) != kErrorNone) && ((aMessage) != nullptr)) \
+        {                                                        \
+            (aMessage)->Free();                                  \
+            (aMessage) = nullptr;                                \
+        }                                                        \
     } while (false)
 
 enum
@@ -191,9 +206,10 @@
  * This class represents a Message buffer.
  *
  */
-class Buffer : public otMessage, public LinkedListEntry<Buffer>
+class Buffer : public otMessageBuffer, public LinkedListEntry<Buffer>
 {
     friend class Message;
+    friend class LinkedListEntry<Buffer>;
 
 public:
     /**
@@ -271,7 +287,7 @@
 
     enum
     {
-        kBufferDataSize     = kBufferSize - sizeof(otMessage),
+        kBufferDataSize     = kBufferSize - sizeof(otMessageBuffer),
         kHeadBufferDataSize = kBufferDataSize - sizeof(MessageMetadata),
     };
 
@@ -291,9 +307,11 @@
  * This class represents a message.
  *
  */
-class Message : public Buffer
+class Message : public otMessage, public Buffer
 {
     friend class Checksum;
+    friend class Crypto::HmacSha256;
+    friend class Crypto::Sha256;
     friend class MessagePool;
     friend class MessageQueue;
     friend class PriorityQueue;
@@ -459,11 +477,11 @@
      *
      * @param[in]  aLength  Requested number of bytes in the message.
      *
-     * @retval OT_ERROR_NONE     Successfully set the length of the message.
-     * @retval OT_ERROR_NO_BUFS  Failed to grow the size of the message because insufficient buffers were available.
+     * @retval kErrorNone    Successfully set the length of the message.
+     * @retval kErrorNoBufs  Failed to grow the size of the message because insufficient buffers were available.
      *
      */
-    otError SetLength(uint16_t aLength);
+    Error SetLength(uint16_t aLength);
 
     /**
      * This method returns the number of buffers in the message.
@@ -568,11 +586,21 @@
      *
      * @param[in]  aPriority  The message priority level.
      *
-     * @retval OT_ERROR_NONE           Successfully set the priority for the message.
-     * @retval OT_ERROR_INVALID_ARGS   Priority level is not invalid.
+     * @retval kErrorNone          Successfully set the priority for the message.
+     * @retval kErrorInvalidArgs   Priority level is not invalid.
      *
      */
-    otError SetPriority(Priority aPriority);
+    Error SetPriority(Priority aPriority);
+
+    /**
+     * This static method convert a `Priority` to a string.
+     *
+     * @param[in] aPriority  The priority level.
+     *
+     * @returns A string representation of @p aPriority.
+     *
+     */
+    static const char *PriorityToString(Priority aPriority);
 
     /**
      * This method prepends bytes to the front of the message.
@@ -582,11 +610,11 @@
      * @param[in]  aBuf     A pointer to a data buffer (can be `nullptr` to grow message without writing bytes).
      * @param[in]  aLength  The number of bytes to prepend.
      *
-     * @retval OT_ERROR_NONE     Successfully prepended the bytes.
-     * @retval OT_ERROR_NO_BUFS  Not enough reserved bytes in the message.
+     * @retval kErrorNone    Successfully prepended the bytes.
+     * @retval kErrorNoBufs  Not enough reserved bytes in the message.
      *
      */
-    otError PrependBytes(const void *aBuf, uint16_t aLength);
+    Error PrependBytes(const void *aBuf, uint16_t aLength);
 
     /**
      * This method prepends an object to the front of the message.
@@ -597,11 +625,11 @@
      *
      * @param[in] aObject      A reference to the object to prepend to the message.
      *
-     * @retval OT_ERROR_NONE     Successfully prepended the object.
-     * @retval OT_ERROR_NO_BUFS  Not enough reserved bytes in the message.
+     * @retval kErrorNone    Successfully prepended the object.
+     * @retval kErrorNoBufs  Not enough reserved bytes in the message.
      *
      */
-    template <typename ObjectType> otError Prepend(const ObjectType &aObject)
+    template <typename ObjectType> Error Prepend(const ObjectType &aObject)
     {
         static_assert(!TypeTraits::IsPointer<ObjectType>::kValue, "ObjectType must not be a pointer");
 
@@ -624,11 +652,11 @@
      * @param[in]  aBuf     A pointer to a data buffer (MUST not be `nullptr`).
      * @param[in]  aLength  The number of bytes to append.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the bytes.
-     * @retval OT_ERROR_NO_BUFS  Insufficient available buffers to grow the message.
+     * @retval kErrorNone    Successfully appended the bytes.
+     * @retval kErrorNoBufs  Insufficient available buffers to grow the message.
      *
      */
-    otError AppendBytes(const void *aBuf, uint16_t aLength);
+    Error AppendBytes(const void *aBuf, uint16_t aLength);
 
     /**
      * This method appends an object to the end of the message.
@@ -639,11 +667,11 @@
      *
      * @param[in] aObject      A reference to the object to append to the message.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the object.
-     * @retval OT_ERROR_NO_BUFS  Insufficient available buffers to grow the message.
+     * @retval kErrorNone    Successfully appended the object.
+     * @retval kErrorNoBufs  Insufficient available buffers to grow the message.
      *
      */
-    template <typename ObjectType> otError Append(const ObjectType &aObject)
+    template <typename ObjectType> Error Append(const ObjectType &aObject)
     {
         static_assert(!TypeTraits::IsPointer<ObjectType>::kValue, "ObjectType must not be a pointer");
 
@@ -666,23 +694,23 @@
      * This method reads a given number of bytes from the message.
      *
      * If there are fewer bytes available in the message than the requested read length, the available bytes will be
-     * read and copied into @p aBuf. In this case `OT_ERROR_PARSE` will be returned.
+     * read and copied into @p aBuf. In this case `kErrorParse` will be returned.
      *
      * @param[in]  aOffset  Byte offset within the message to begin reading.
      * @param[out] aBuf     A pointer to a data buffer to copy the read bytes into.
      * @param[in]  aLength  Number of bytes to read.
      *
-     * @retval OT_ERROR_NONE     @p aLength bytes were successfully read from message.
-     * @retval OT_ERROR_PARSE    Not enough bytes remaining in message to read the entire object.
+     * @retval kErrorNone     @p aLength bytes were successfully read from message.
+     * @retval kErrorParse    Not enough bytes remaining in message to read the entire object.
      *
      */
-    otError Read(uint16_t aOffset, void *aBuf, uint16_t aLength) const;
+    Error Read(uint16_t aOffset, void *aBuf, uint16_t aLength) const;
 
     /**
      * This method reads an object from the message.
      *
      * If there are fewer bytes available in the message than the requested object size, the available bytes will be
-     * read and copied into @p aObject (@p aObject will be read partially). In this case `OT_ERROR_PARSE` will
+     * read and copied into @p aObject (@p aObject will be read partially). In this case `kErrorParse` will
      * be returned.
      *
      * @tparam     ObjectType   The object type to read from the message.
@@ -690,11 +718,11 @@
      * @param[in]  aOffset      Byte offset within the message to begin reading.
      * @param[out] aObject      A reference to the object to read into.
      *
-     * @retval OT_ERROR_NONE     Object @p aObject was successfully read from message.
-     * @retval OT_ERROR_PARSE    Not enough bytes remaining in message to read the entire object.
+     * @retval kErrorNone     Object @p aObject was successfully read from message.
+     * @retval kErrorParse    Not enough bytes remaining in message to read the entire object.
      *
      */
-    template <typename ObjectType> otError Read(uint16_t aOffset, ObjectType &aObject) const
+    template <typename ObjectType> Error Read(uint16_t aOffset, ObjectType &aObject) const
     {
         static_assert(!TypeTraits::IsPointer<ObjectType>::kValue, "ObjectType must not be a pointer");
 
@@ -702,6 +730,60 @@
     }
 
     /**
+     * This method compares the bytes in the message at a given offset with a given byte array.
+     *
+     * If there are fewer bytes available in the message than the requested @p aLength, the comparison is treated as
+     * failure (returns FALSE).
+     *
+     * @param[in]  aOffset    Byte offset within the message to read from for the comparison.
+     * @param[in]  aBuf       A pointer to a data buffer to compare with the bytes from message.
+     * @param[in]  aLength    Number of bytes in @p aBuf.
+     *
+     * @returns TRUE if there are enough bytes available in @p aMessage and they match the bytes from @p aBuf,
+     *          FALSE otherwise.
+     *
+     */
+    bool CompareBytes(uint16_t aOffset, const void *aBuf, uint16_t aLength) const;
+
+    /**
+     * This method compares the bytes in the message at a given offset with bytes read from another message.
+     *
+     * If either message has fewer bytes available than the requested @p aLength, the comparison is treated as failure
+     * (returns FALSE).
+     *
+     * @param[in]  aOffset        Byte offset within the message to read from for the comparison.
+     * @param[in]  aOtherMessage  The other message to compare with.
+     * @param[in]  aOtherOffset   Byte offset within @p aOtherMessage to read from for the comparison.
+     * @param[in]  aLength        Number of bytes to compare.
+     *
+     * @returns TRUE if there are enough bytes available in both messages and they all match. FALSE otherwise.
+     *
+     */
+    bool CompareBytes(uint16_t aOffset, const Message &aOtherMessage, uint16_t aOtherOffset, uint16_t aLength) const;
+
+    /**
+     * This method compares the bytes in the message at a given offset with an object.
+     *
+     * The bytes in the message are compared with the bytes in @p aObject. If there are fewer bytes available in the
+     * message than the requested object size, it is treated as failed comparison (returns FALSE).
+     *
+     * @tparam     ObjectType   The object type to compare with the bytes in message.
+     *
+     * @param[in] aOffset      Byte offset within the message to read from for the comparison.
+     * @param[in] aObject      A reference to the object to compare with the message bytes.
+     *
+     * @returns TRUE if there are enough bytes available in @p aMessage and they match the bytes in @p aObject,
+     *          FALSE otherwise.
+     *
+     */
+    template <typename ObjectType> bool Compare(uint16_t aOffset, const ObjectType &aObject) const
+    {
+        static_assert(!TypeTraits::IsPointer<ObjectType>::kValue, "ObjectType must not be a pointer");
+
+        return CompareBytes(aOffset, &aObject, sizeof(ObjectType));
+    }
+
+    /**
      * This method writes bytes to the message.
      *
      * This method will not resize the message. The given data to write (with @p aLength bytes) MUST fit within the
@@ -1251,11 +1333,11 @@
      *
      * @param[in]  aLength  The number of bytes that the message buffer needs to handle.
      *
-     * @retval OT_ERROR_NONE     Successfully resized the message.
-     * @retval OT_ERROR_NO_BUFS  Could not grow the message due to insufficient available message buffers.
+     * @retval kErrorNone    Successfully resized the message.
+     * @retval kErrorNoBufs  Could not grow the message due to insufficient available message buffers.
      *
      */
-    otError ResizeMessage(uint16_t aLength);
+    Error ResizeMessage(uint16_t aLength);
 
 private:
     struct Chunk
@@ -1545,7 +1627,7 @@
 private:
     Buffer *NewBuffer(Message::Priority aPriority);
     void    FreeBuffers(Buffer *aBuffer);
-    otError ReclaimBuffers(Message::Priority aPriority);
+    Error   ReclaimBuffers(Message::Priority aPriority);
 
 #if !OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT && !OPENTHREAD_CONFIG_MESSAGE_USE_HEAP_ENABLE
     uint16_t                  mNumFreeBuffers;
diff --git a/src/core/common/notifier.cpp b/src/core/common/notifier.cpp
index 8b0be4b..34b6472 100644
--- a/src/core/common/notifier.cpp
+++ b/src/core/common/notifier.cpp
@@ -33,6 +33,7 @@
 
 #include "notifier.hpp"
 
+#include "border_router/routing_manager.hpp"
 #include "common/code_utils.hpp"
 #include "common/debug.hpp"
 #include "common/locator-getters.hpp"
@@ -42,7 +43,7 @@
 
 Notifier::Notifier(Instance &aInstance)
     : InstanceLocator(aInstance)
-    , mTask(aInstance, Notifier::EmitEvents, this)
+    , mTask(aInstance, Notifier::EmitEvents)
 {
     for (ExternalCallback &callback : mExternalCallbacks)
     {
@@ -51,9 +52,9 @@
     }
 }
 
-otError Notifier::RegisterCallback(otStateChangedCallback aCallback, void *aContext)
+Error Notifier::RegisterCallback(otStateChangedCallback aCallback, void *aContext)
 {
-    otError           error          = OT_ERROR_NONE;
+    Error             error          = kErrorNone;
     ExternalCallback *unusedCallback = nullptr;
 
     VerifyOrExit(aCallback != nullptr);
@@ -70,10 +71,10 @@
             continue;
         }
 
-        VerifyOrExit((callback.mHandler != aCallback) || (callback.mContext != aContext), error = OT_ERROR_ALREADY);
+        VerifyOrExit((callback.mHandler != aCallback) || (callback.mContext != aContext), error = kErrorAlready);
     }
 
-    VerifyOrExit(unusedCallback != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(unusedCallback != nullptr, error = kErrorNoBufs);
 
     unusedCallback->mHandler = aCallback;
     unusedCallback->mContext = aContext;
@@ -116,7 +117,7 @@
 
 void Notifier::EmitEvents(Tasklet &aTasklet)
 {
-    aTasklet.GetOwner<Notifier>().EmitEvents();
+    aTasklet.Get<Notifier>().EmitEvents();
 }
 
 void Notifier::EmitEvents(void)
@@ -146,7 +147,7 @@
     Get<Utils::ChildSupervisor>().HandleNotifierEvents(events);
 #endif
 #if OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE
-    Get<Utils::DatasetUpdater>().HandleNotifierEvents(events);
+    Get<MeshCoP::DatasetUpdater>().HandleNotifierEvents(events);
 #endif
 #endif // OPENTHREAD_FTD
 #if OPENTHREAD_FTD || OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
@@ -179,6 +180,15 @@
 #if OPENTHREAD_ENABLE_VENDOR_EXTENSION
     Get<Extension::ExtensionBase>().HandleNotifierEvents(events);
 #endif
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+    Get<BorderRouter::RoutingManager>().HandleNotifierEvents(events);
+#endif
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+    Get<Srp::Server>().HandleNotifierEvents(events);
+#endif
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+    Get<Srp::Client>().HandleNotifierEvents(events);
+#endif
 
     for (ExternalCallback &callback : mExternalCallbacks)
     {
@@ -226,7 +236,7 @@
     }
 
 exit:
-    otLogInfoCore("Notifier: StateChanged (0x%08x) %s%s] ", aEvents.GetAsFlags(), didLog ? "... " : "[",
+    otLogInfoCore("Notifier: StateChanged (0x%08x) %s%s]", aEvents.GetAsFlags(), didLog ? "... " : "[",
                   string.AsCString());
 }
 
diff --git a/src/core/common/notifier.hpp b/src/core/common/notifier.hpp
index 8456811..77b162d 100644
--- a/src/core/common/notifier.hpp
+++ b/src/core/common/notifier.hpp
@@ -42,6 +42,7 @@
 #include <openthread/instance.h>
 #include <openthread/platform/toolchain.h>
 
+#include "common/error.hpp"
 #include "common/locator.hpp"
 #include "common/non_copyable.hpp"
 #include "common/tasklet.hpp"
@@ -210,12 +211,12 @@
      * @param[in]  aCallback     A pointer to the handler function that is called to notify of the changes.
      * @param[in]  aContext      A pointer to arbitrary context information.
      *
-     * @retval OT_ERROR_NONE     Successfully registered the callback.
-     * @retval OT_ERROR_ALREADY  The callback was already registered.
-     * @retval OT_ERROR_NO_BUFS  Could not add the callback due to resource constraints.
+     * @retval kErrorNone     Successfully registered the callback.
+     * @retval kErrorAlready  The callback was already registered.
+     * @retval kErrorNoBufs   Could not add the callback due to resource constraints.
      *
      */
-    otError RegisterCallback(otStateChangedCallback aCallback, void *aContext);
+    Error RegisterCallback(otStateChangedCallback aCallback, void *aContext);
 
     /**
      * This method removes/unregisters a previously registered `otStateChangedCallback` handler.
@@ -264,7 +265,7 @@
     /**
      * This template method updates a variable of a type `Type` with a new value and signals the given event.
      *
-     * If the variable is already set to the same value, this method returns `OT_ERROR_ALREADY` and the event is
+     * If the variable is already set to the same value, this method returns `kErrorAlready` and the event is
      * signaled using `SignalIfFirst()` (i.e., signal is scheduled only if event has not been signaled before).
      *
      * The template `Type` should support comparison operator `==` and assignment operator `=`.
@@ -273,18 +274,18 @@
      * @param[in]    aNewValue    The new value.
      * @param[in]    aEvent       The event to signal.
      *
-     * @retval OT_ERROR_NONE      The variable was update successfully and @p aEvent was signaled.
-     * @retval OT_ERROR_ALREADY   The variable was already set to the same value.
+     * @retval kErrorNone      The variable was update successfully and @p aEvent was signaled.
+     * @retval kErrorAlready   The variable was already set to the same value.
      *
      */
-    template <typename Type> otError Update(Type &aVariable, const Type &aNewValue, Event aEvent)
+    template <typename Type> Error Update(Type &aVariable, const Type &aNewValue, Event aEvent)
     {
-        otError error = OT_ERROR_NONE;
+        Error error = kErrorNone;
 
         if (aVariable == aNewValue)
         {
             SignalIfFirst(aEvent);
-            error = OT_ERROR_ALREADY;
+            error = kErrorAlready;
         }
         else
         {
diff --git a/src/core/common/random.hpp b/src/core/common/random.hpp
index 3ddc1af..78deef3 100644
--- a/src/core/common/random.hpp
+++ b/src/core/common/random.hpp
@@ -38,9 +38,8 @@
 
 #include <stdint.h>
 
-#include <openthread/error.h>
-
 #include "common/debug.hpp"
+#include "common/error.hpp"
 #include "common/random_manager.hpp"
 
 namespace ot {
@@ -170,10 +169,10 @@
  * @param[out] aBuffer  A pointer to a buffer to fill with the random bytes.
  * @param[in]  aSize    Size of buffer (number of bytes to fill).
  *
- * @retval OT_ERROR_NONE    Successfully filled buffer with random values.
+ * @retval kErrorNone    Successfully filled buffer with random values.
  *
  */
-inline otError FillBuffer(uint8_t *aBuffer, uint16_t aSize)
+inline Error FillBuffer(uint8_t *aBuffer, uint16_t aSize)
 {
     return RandomManager::CryptoFillBuffer(aBuffer, aSize);
 }
diff --git a/src/core/common/random_manager.cpp b/src/core/common/random_manager.cpp
index de7977e..4c320c3 100644
--- a/src/core/common/random_manager.cpp
+++ b/src/core/common/random_manager.cpp
@@ -41,6 +41,7 @@
 
 #include "common/code_utils.hpp"
 #include "common/debug.hpp"
+#include "common/logging.hpp"
 #include "common/random.hpp"
 #include "crypto/mbedtls.hpp"
 
@@ -57,7 +58,7 @@
 RandomManager::RandomManager(void)
 {
     uint32_t seed;
-    otError  error;
+    Error    error;
 
     OT_UNUSED_VARIABLE(error);
 
@@ -70,10 +71,10 @@
     sCtrDrbg.Init();
 
     error = Random::Crypto::FillBuffer(reinterpret_cast<uint8_t *>(&seed), sizeof(seed));
-    OT_ASSERT(error == OT_ERROR_NONE);
+    OT_ASSERT(error == kErrorNone);
 #else
     error = otPlatEntropyGet(reinterpret_cast<uint8_t *>(&seed), sizeof(seed));
-    OT_ASSERT(error == OT_ERROR_NONE);
+    OT_ASSERT(error == kErrorNone);
 #endif
 
     sPrng.Init(seed);
@@ -190,8 +191,19 @@
 
 void RandomManager::CryptoCtrDrbg::Init(void)
 {
+    int rval;
+
     mbedtls_ctr_drbg_init(&mCtrDrbg);
-    mbedtls_ctr_drbg_seed(&mCtrDrbg, mbedtls_entropy_func, RandomManager::GetMbedTlsEntropyContext(), nullptr, 0);
+
+    rval =
+        mbedtls_ctr_drbg_seed(&mCtrDrbg, mbedtls_entropy_func, RandomManager::GetMbedTlsEntropyContext(), nullptr, 0);
+
+    if (rval != 0)
+    {
+        otLogCritMbedTls("Failed to seed the CTR DRBG");
+    }
+
+    OT_ASSERT(rval == 0);
 }
 
 void RandomManager::CryptoCtrDrbg::Deinit(void)
@@ -199,7 +211,7 @@
     mbedtls_ctr_drbg_free(&mCtrDrbg);
 }
 
-otError RandomManager::CryptoCtrDrbg::FillBuffer(uint8_t *aBuffer, uint16_t aSize)
+Error RandomManager::CryptoCtrDrbg::FillBuffer(uint8_t *aBuffer, uint16_t aSize)
 {
     return ot::Crypto::MbedTls::MapError(
         mbedtls_ctr_drbg_random(&mCtrDrbg, static_cast<unsigned char *>(aBuffer), static_cast<size_t>(aSize)));
diff --git a/src/core/common/random_manager.hpp b/src/core/common/random_manager.hpp
index 7106033..b898575 100644
--- a/src/core/common/random_manager.hpp
+++ b/src/core/common/random_manager.hpp
@@ -37,13 +37,13 @@
 #include "openthread-core-config.h"
 
 #include <stdint.h>
-#include <openthread/error.h>
 
 #if !OPENTHREAD_RADIO
 #include <mbedtls/ctr_drbg.h>
 #include <mbedtls/entropy.h>
 #endif
 
+#include "common/error.hpp"
 #include "common/non_copyable.hpp"
 
 #if (!defined(MBEDTLS_NO_DEFAULT_ENTROPY_SOURCES) && \
@@ -94,10 +94,10 @@
      * @param[out] aBuffer  A pointer to a buffer to fill with the random bytes.
      * @param[in]  aSize    Size of buffer (number of bytes to fill).
      *
-     * @retval OT_ERROR_NONE    Successfully filled buffer with random values.
+     * @retval kErrorNone    Successfully filled buffer with random values.
      *
      */
-    static otError CryptoFillBuffer(uint8_t *aBuffer, uint16_t aSize) { return sCtrDrbg.FillBuffer(aBuffer, aSize); }
+    static Error CryptoFillBuffer(uint8_t *aBuffer, uint16_t aSize) { return sCtrDrbg.FillBuffer(aBuffer, aSize); }
 
     /**
      * This static method returns the initialized mbedtls_ctr_drbg_context.
@@ -139,9 +139,9 @@
     class CryptoCtrDrbg
     {
     public:
-        void    Init(void);
-        void    Deinit(void);
-        otError FillBuffer(uint8_t *aBuffer, uint16_t aSize);
+        void  Init(void);
+        void  Deinit(void);
+        Error FillBuffer(uint8_t *aBuffer, uint16_t aSize);
 
         mbedtls_ctr_drbg_context *GetContext(void) { return &mCtrDrbg; }
 
diff --git a/src/core/common/settings.cpp b/src/core/common/settings.cpp
index 44491d8..70cd799 100644
--- a/src/core/common/settings.cpp
+++ b/src/core/common/settings.cpp
@@ -43,6 +43,9 @@
 #include "thread/mle.hpp"
 
 namespace ot {
+// This array contains critical keys that should be stored in the secure area.
+static const uint16_t kCriticalKeys[] = {SettingsBase::kKeyActiveDataset, SettingsBase::kKeyPendingDataset,
+                                         SettingsBase::kKeySrpEcdsaKey};
 
 // LCOV_EXCL_START
 
@@ -81,16 +84,22 @@
     otLogInfoCore("Non-volatile: %s DadInfo {DadCounter:%2d}", aAction, aDadInfo.GetDadCounter());
 }
 #endif
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+void SettingsBase::LogPrefix(const char *aAction, const char *aPrefixName, const Ip6::Prefix &aOmrPrefix) const
+{
+    otLogInfoCore("Non-volatile: %s %s %s", aAction, aPrefixName, aOmrPrefix.ToString().AsCString());
+}
+#endif
 
 #endif // #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO)
 
 #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_WARN)
 
-void SettingsBase::LogFailure(otError error, const char *aText, bool aIsDelete) const
+void SettingsBase::LogFailure(Error error, const char *aText, bool aIsDelete) const
 {
-    if ((error != OT_ERROR_NONE) && (!aIsDelete || (error != OT_ERROR_NOT_FOUND)))
+    if ((error != kErrorNone) && (!aIsDelete || (error != kErrorNotFound)))
     {
-        otLogWarnCore("Non-volatile: Error %s %s", otThreadErrorToString(error), aText);
+        otLogWarnCore("Non-volatile: Error %s %s", ErrorToString(error), aText);
     }
 }
 
@@ -116,22 +125,27 @@
     otPlatSettingsDeinit(&GetInstance());
 }
 
-otError SettingsDriver::Add(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength)
+void SettingsDriver::SetCriticalKeys(const uint16_t *aKeys, uint16_t aKeysLength)
+{
+    otPlatSettingsSetCriticalKeys(&GetInstance(), aKeys, aKeysLength);
+}
+
+Error SettingsDriver::Add(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength)
 {
     return otPlatSettingsAdd(&GetInstance(), aKey, aValue, aValueLength);
 }
 
-otError SettingsDriver::Delete(uint16_t aKey, int aIndex)
+Error SettingsDriver::Delete(uint16_t aKey, int aIndex)
 {
     return otPlatSettingsDelete(&GetInstance(), aKey, aIndex);
 }
 
-otError SettingsDriver::Get(uint16_t aKey, int aIndex, uint8_t *aValue, uint16_t *aValueLength) const
+Error SettingsDriver::Get(uint16_t aKey, int aIndex, uint8_t *aValue, uint16_t *aValueLength) const
 {
     return otPlatSettingsGet(&GetInstance(), aKey, aIndex, aValue, aValueLength);
 }
 
-otError SettingsDriver::Set(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength)
+Error SettingsDriver::Set(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength)
 {
     return otPlatSettingsSet(&GetInstance(), aKey, aValue, aValueLength);
 }
@@ -158,22 +172,28 @@
 {
 }
 
-otError SettingsDriver::Add(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength)
+void SettingsDriver::SetCriticalKeys(const uint16_t *aKeys, uint16_t aKeysLength)
+{
+    OT_UNUSED_VARIABLE(aKeys);
+    OT_UNUSED_VARIABLE(aKeysLength);
+}
+
+Error SettingsDriver::Add(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength)
 {
     return mFlash.Add(aKey, aValue, aValueLength);
 }
 
-otError SettingsDriver::Delete(uint16_t aKey, int aIndex)
+Error SettingsDriver::Delete(uint16_t aKey, int aIndex)
 {
     return mFlash.Delete(aKey, aIndex);
 }
 
-otError SettingsDriver::Get(uint16_t aKey, int aIndex, uint8_t *aValue, uint16_t *aValueLength) const
+Error SettingsDriver::Get(uint16_t aKey, int aIndex, uint8_t *aValue, uint16_t *aValueLength) const
 {
     return mFlash.Get(aKey, aIndex, aValue, aValueLength);
 }
 
-otError SettingsDriver::Set(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength)
+Error SettingsDriver::Set(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength)
 {
     return mFlash.Set(aKey, aValue, aValueLength);
 }
@@ -188,6 +208,7 @@
 void Settings::Init(void)
 {
     Get<SettingsDriver>().Init();
+    Get<SettingsDriver>().SetCriticalKeys(kCriticalKeys, OT_ARRAY_LENGTH(kCriticalKeys));
 }
 
 void Settings::Deinit(void)
@@ -201,21 +222,21 @@
     otLogInfoCore("Non-volatile: Wiped all info");
 }
 
-otError Settings::SaveOperationalDataset(bool aIsActive, const MeshCoP::Dataset &aDataset)
+Error Settings::SaveOperationalDataset(bool aIsActive, const MeshCoP::Dataset &aDataset)
 {
-    otError error = Save(aIsActive ? kKeyActiveDataset : kKeyPendingDataset, aDataset.GetBytes(), aDataset.GetSize());
+    Error error = Save(aIsActive ? kKeyActiveDataset : kKeyPendingDataset, aDataset.GetBytes(), aDataset.GetSize());
 
     LogFailure(error, "saving OperationalDataset", false);
     return error;
 }
 
-otError Settings::ReadOperationalDataset(bool aIsActive, MeshCoP::Dataset &aDataset) const
+Error Settings::ReadOperationalDataset(bool aIsActive, MeshCoP::Dataset &aDataset) const
 {
-    otError  error  = OT_ERROR_NONE;
+    Error    error  = kErrorNone;
     uint16_t length = MeshCoP::Dataset::kMaxSize;
 
     SuccessOrExit(error = Read(aIsActive ? kKeyActiveDataset : kKeyPendingDataset, aDataset.GetBytes(), length));
-    VerifyOrExit(length <= MeshCoP::Dataset::kMaxSize, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(length <= MeshCoP::Dataset::kMaxSize, error = kErrorNotFound);
 
     aDataset.SetSize(length);
 
@@ -223,18 +244,18 @@
     return error;
 }
 
-otError Settings::DeleteOperationalDataset(bool aIsActive)
+Error Settings::DeleteOperationalDataset(bool aIsActive)
 {
-    otError error = Delete(aIsActive ? kKeyActiveDataset : kKeyPendingDataset);
+    Error error = Delete(aIsActive ? kKeyActiveDataset : kKeyPendingDataset);
 
     LogFailure(error, "deleting OperationalDataset", true);
 
     return error;
 }
 
-otError Settings::ReadNetworkInfo(NetworkInfo &aNetworkInfo) const
+Error Settings::ReadNetworkInfo(NetworkInfo &aNetworkInfo) const
 {
-    otError  error;
+    Error    error;
     uint16_t length = sizeof(NetworkInfo);
 
     aNetworkInfo.Init();
@@ -245,13 +266,13 @@
     return error;
 }
 
-otError Settings::SaveNetworkInfo(const NetworkInfo &aNetworkInfo)
+Error Settings::SaveNetworkInfo(const NetworkInfo &aNetworkInfo)
 {
-    otError     error = OT_ERROR_NONE;
+    Error       error = kErrorNone;
     NetworkInfo prevNetworkInfo;
     uint16_t    length = sizeof(prevNetworkInfo);
 
-    if ((Read(kKeyNetworkInfo, &prevNetworkInfo, length) == OT_ERROR_NONE) && (length == sizeof(NetworkInfo)) &&
+    if ((Read(kKeyNetworkInfo, &prevNetworkInfo, length) == kErrorNone) && (length == sizeof(NetworkInfo)) &&
         (prevNetworkInfo == aNetworkInfo))
     {
         LogNetworkInfo("Re-saved", aNetworkInfo);
@@ -266,9 +287,9 @@
     return error;
 }
 
-otError Settings::DeleteNetworkInfo(void)
+Error Settings::DeleteNetworkInfo(void)
 {
-    otError error;
+    Error error;
 
     SuccessOrExit(error = Delete(kKeyNetworkInfo));
     otLogInfoCore("Non-volatile: Deleted NetworkInfo");
@@ -278,9 +299,9 @@
     return error;
 }
 
-otError Settings::ReadParentInfo(ParentInfo &aParentInfo) const
+Error Settings::ReadParentInfo(ParentInfo &aParentInfo) const
 {
-    otError  error;
+    Error    error;
     uint16_t length = sizeof(ParentInfo);
 
     aParentInfo.Init();
@@ -291,13 +312,13 @@
     return error;
 }
 
-otError Settings::SaveParentInfo(const ParentInfo &aParentInfo)
+Error Settings::SaveParentInfo(const ParentInfo &aParentInfo)
 {
-    otError    error = OT_ERROR_NONE;
+    Error      error = kErrorNone;
     ParentInfo prevParentInfo;
     uint16_t   length = sizeof(ParentInfo);
 
-    if ((Read(kKeyParentInfo, &prevParentInfo, length) == OT_ERROR_NONE) && (length == sizeof(ParentInfo)) &&
+    if ((Read(kKeyParentInfo, &prevParentInfo, length) == kErrorNone) && (length == sizeof(ParentInfo)) &&
         (prevParentInfo == aParentInfo))
     {
         LogParentInfo("Re-saved", aParentInfo);
@@ -312,9 +333,9 @@
     return error;
 }
 
-otError Settings::DeleteParentInfo(void)
+Error Settings::DeleteParentInfo(void)
 {
-    otError error;
+    Error error;
 
     SuccessOrExit(error = Delete(kKeyParentInfo));
     otLogInfoCore("Non-volatile: Deleted ParentInfo");
@@ -324,9 +345,9 @@
     return error;
 }
 
-otError Settings::AddChildInfo(const ChildInfo &aChildInfo)
+Error Settings::AddChildInfo(const ChildInfo &aChildInfo)
 {
-    otError error;
+    Error error;
 
     SuccessOrExit(error = Add(kKeyChildInfo, &aChildInfo, sizeof(aChildInfo)));
     LogChildInfo("Added", aChildInfo);
@@ -336,9 +357,9 @@
     return error;
 }
 
-otError Settings::DeleteAllChildInfo(void)
+Error Settings::DeleteAllChildInfo(void)
 {
-    otError error;
+    Error error;
 
     SuccessOrExit(error = Delete(kKeyChildInfo));
     otLogInfoCore("Non-volatile: Deleted all ChildInfo");
@@ -365,11 +386,11 @@
     }
 }
 
-otError Settings::ChildInfoIterator::Delete(void)
+Error Settings::ChildInfoIterator::Delete(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(!mIsDone, error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(!mIsDone, error = kErrorInvalidState);
     SuccessOrExit(error = Get<SettingsDriver>().Delete(kKeyChildInfo, mIndex));
     LogChildInfo("Removed", mChildInfo);
 
@@ -381,7 +402,7 @@
 void Settings::ChildInfoIterator::Read(void)
 {
     uint16_t length = sizeof(ChildInfo);
-    otError  error;
+    Error    error;
 
     mChildInfo.Init();
     SuccessOrExit(
@@ -389,13 +410,13 @@
     LogChildInfo("Read", mChildInfo);
 
 exit:
-    mIsDone = (error != OT_ERROR_NONE);
+    mIsDone = (error != kErrorNone);
 }
 
 #if OPENTHREAD_CONFIG_DUA_ENABLE
-otError Settings::ReadDadInfo(DadInfo &aDadInfo) const
+Error Settings::ReadDadInfo(DadInfo &aDadInfo) const
 {
-    otError  error;
+    Error    error;
     uint16_t length = sizeof(DadInfo);
 
     aDadInfo.Init();
@@ -406,13 +427,13 @@
     return error;
 }
 
-otError Settings::SaveDadInfo(const DadInfo &aDadInfo)
+Error Settings::SaveDadInfo(const DadInfo &aDadInfo)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     DadInfo  prevDadInfo;
     uint16_t length = sizeof(DadInfo);
 
-    if ((Read(kKeyDadInfo, &prevDadInfo, length) == OT_ERROR_NONE) && (length == sizeof(DadInfo)) &&
+    if ((Read(kKeyDadInfo, &prevDadInfo, length) == kErrorNone) && (length == sizeof(DadInfo)) &&
         (prevDadInfo == aDadInfo))
     {
         LogDadInfo("Re-saved", aDadInfo);
@@ -427,9 +448,9 @@
     return error;
 }
 
-otError Settings::DeleteDadInfo(void)
+Error Settings::DeleteDadInfo(void)
 {
-    otError error;
+    Error error;
 
     SuccessOrExit(error = Delete(kKeyDadInfo));
     otLogInfoCore("Non-volatile: Deleted DadInfo");
@@ -440,24 +461,146 @@
 }
 #endif // OPENTHREAD_CONFIG_DUA_ENABLE
 
-otError Settings::Read(Key aKey, void *aBuffer, uint16_t &aSize) const
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+Error Settings::SaveOmrPrefix(const Ip6::Prefix &aOmrPrefix)
+{
+    Error       error = kErrorNone;
+    Ip6::Prefix prevOmrPrefix;
+    uint16_t    length = sizeof(prevOmrPrefix);
+
+    if ((Read(kKeyOmrPrefix, &prevOmrPrefix, length) == kErrorNone) && (length == sizeof(prevOmrPrefix)) &&
+        (prevOmrPrefix == aOmrPrefix))
+    {
+        LogPrefix("Re-saved", "OMR prefix", aOmrPrefix);
+        ExitNow();
+    }
+
+    SuccessOrExit(error = Save(kKeyOmrPrefix, &aOmrPrefix, sizeof(aOmrPrefix)));
+    LogPrefix("Saved", "OMR prefix", aOmrPrefix);
+
+exit:
+    LogFailure(error, "saving OMR prefix", false);
+    return error;
+}
+
+Error Settings::ReadOmrPrefix(Ip6::Prefix &aOmrPrefix) const
+{
+    Error    error;
+    uint16_t length = sizeof(aOmrPrefix);
+
+    aOmrPrefix.Clear();
+    SuccessOrExit(error = Read(kKeyOmrPrefix, &aOmrPrefix, length));
+    LogPrefix("Read", "OMR prefix", aOmrPrefix);
+
+exit:
+    return error;
+}
+
+Error Settings::SaveOnLinkPrefix(const Ip6::Prefix &aOnLinkPrefix)
+{
+    Error       error = kErrorNone;
+    Ip6::Prefix prevOnLinkPrefix;
+    uint16_t    length = sizeof(prevOnLinkPrefix);
+
+    if ((Read(kKeyOnLinkPrefix, &prevOnLinkPrefix, length) == kErrorNone) && (length == sizeof(prevOnLinkPrefix)) &&
+        (prevOnLinkPrefix == aOnLinkPrefix))
+    {
+        LogPrefix("Re-saved", "on-link prefix", aOnLinkPrefix);
+        ExitNow();
+    }
+
+    SuccessOrExit(error = Save(kKeyOnLinkPrefix, &aOnLinkPrefix, sizeof(aOnLinkPrefix)));
+    LogPrefix("Saved", "on-link prefix", aOnLinkPrefix);
+
+exit:
+    LogFailure(error, "saving on-link prefix", false);
+    return error;
+}
+
+Error Settings::ReadOnLinkPrefix(Ip6::Prefix &aOnLinkPrefix) const
+{
+    Error    error;
+    uint16_t length = sizeof(aOnLinkPrefix);
+
+    aOnLinkPrefix.Clear();
+    SuccessOrExit(error = Read(kKeyOnLinkPrefix, &aOnLinkPrefix, length));
+    LogPrefix("Read", "on-link prefix", aOnLinkPrefix);
+
+exit:
+    return error;
+}
+#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+
+Error Settings::SaveSrpKey(const Crypto::Ecdsa::P256::KeyPair &aKeyPair)
+{
+    Error error = kErrorNone;
+
+    SuccessOrExit(error = Save(kKeySrpEcdsaKey, aKeyPair.GetDerBytes(), aKeyPair.GetDerLength()));
+    otLogInfoCore("Non-volatile: Saved SRP key");
+
+exit:
+    LogFailure(error, "saving SRP key", false);
+    return error;
+}
+
+Error Settings::ReadSrpKey(Crypto::Ecdsa::P256::KeyPair &aKeyPair) const
+{
+    Error    error;
+    uint16_t length = Crypto::Ecdsa::P256::KeyPair::kMaxDerSize;
+
+    SuccessOrExit(error = Read(kKeySrpEcdsaKey, aKeyPair.GetDerBytes(), length));
+    VerifyOrExit(length <= Crypto::Ecdsa::P256::KeyPair::kMaxDerSize, error = kErrorNotFound);
+    aKeyPair.SetDerLength(static_cast<uint8_t>(length));
+    otLogInfoCore("Non-volatile: Read SRP key");
+
+exit:
+    return error;
+}
+
+Error Settings::DeleteSrpKey(void)
+{
+    Error error;
+
+    SuccessOrExit(error = Delete(kKeySrpEcdsaKey));
+    otLogInfoCore("Non-volatile: Deleted SRP key");
+
+exit:
+    LogFailure(error, "deleting SRP key", true);
+    return error;
+}
+
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+
+Error Settings::Read(Key aKey, void *aBuffer, uint16_t &aSize) const
 {
     return Get<SettingsDriver>().Get(aKey, 0, reinterpret_cast<uint8_t *>(aBuffer), &aSize);
 }
 
-otError Settings::Save(Key aKey, const void *aValue, uint16_t aSize)
+Error Settings::Save(Key aKey, const void *aValue, uint16_t aSize)
 {
     return Get<SettingsDriver>().Set(aKey, reinterpret_cast<const uint8_t *>(aValue), aSize);
 }
 
-otError Settings::Add(Key aKey, const void *aValue, uint16_t aSize)
+Error Settings::Add(Key aKey, const void *aValue, uint16_t aSize)
 {
     return Get<SettingsDriver>().Add(aKey, reinterpret_cast<const uint8_t *>(aValue), aSize);
 }
 
-otError Settings::Delete(Key aKey)
+Error Settings::Delete(Key aKey)
 {
     return Get<SettingsDriver>().Delete(aKey, -1);
 }
 
 } // namespace ot
+
+//---------------------------------------------------------------------------------------------------------------------
+// Default/weak implementation of settings platform APIs
+
+OT_TOOL_WEAK void otPlatSettingsSetCriticalKeys(otInstance *aInstance, const uint16_t *aKeys, uint16_t aKeysLength)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+    OT_UNUSED_VARIABLE(aKeys);
+    OT_UNUSED_VARIABLE(aKeysLength);
+}
diff --git a/src/core/common/settings.hpp b/src/core/common/settings.hpp
index 2fe11c4..929fef4 100644
--- a/src/core/common/settings.hpp
+++ b/src/core/common/settings.hpp
@@ -36,6 +36,8 @@
 
 #include "openthread-core-config.h"
 
+#include <openthread/platform/settings.h>
+
 #include "common/clearable.hpp"
 #include "common/encoding.hpp"
 #include "common/equatable.hpp"
@@ -47,6 +49,9 @@
 #if OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE
 #include "utils/slaac_address.hpp"
 #endif
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+#include "crypto/ecdsa.hpp"
+#endif
 
 namespace ot {
 
@@ -75,6 +80,15 @@
     void Deinit(void);
 
     /**
+     * This method sets the critical keys that should be stored in a secure area.
+     *
+     * @param[in]  aKeys        A pointer to an array containing the list of critical keys.
+     * @param[in]  aKeysLength  The number of entries in the @p aKeys array.
+     *
+     */
+    void SetCriticalKeys(const uint16_t *aKeys, uint16_t aKeysLength);
+
+    /**
      * This method adds a value to @p aKey.
      *
      * @param[in]  aKey          The key associated with the value.
@@ -82,11 +96,11 @@
      *                           MUST NOT be nullptr if @p aValueLength is non-zero.
      * @param[in]  aValueLength  The length of the data pointed to by @p aValue. May be zero.
      *
-     * @retval OT_ERROR_NONE     The value was added.
-     * @retval OT_ERROR_NO_BUFS  Not enough space to store the value.
+     * @retval kErrorNone     The value was added.
+     * @retval kErrorNoBufs   Not enough space to store the value.
      *
      */
-    otError Add(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength);
+    Error Add(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength);
 
     /**
      * This method removes a value from @p aKey.
@@ -95,11 +109,11 @@
      * @param[in] aIndex  The index of the value to be removed.
      *                    If set to -1, all values for @p aKey will be removed.
      *
-     * @retval OT_ERROR_NONE       The given key and index was found and removed successfully.
-     * @retval OT_ERROR_NOT_FOUND  The given key or index was not found.
+     * @retval kErrorNone       The given key and index was found and removed successfully.
+     * @retval kErrorNotFound   The given key or index was not found.
      *
      */
-    otError Delete(uint16_t aKey, int aIndex);
+    Error Delete(uint16_t aKey, int aIndex);
 
     /**
      * This method fetches the value identified by @p aKey.
@@ -114,11 +128,11 @@
      *                              At return, the actual length of the setting is written.
      *                              May be nullptr if performing a presence check.
      *
-     * @retval OT_ERROR_NONE        The value was fetched successfully.
-     * @retval OT_ERROR_NOT_FOUND   The key was not found.
+     * @retval kErrorNone        The value was fetched successfully.
+     * @retval kErrorNotFound    The key was not found.
      *
      */
-    otError Get(uint16_t aKey, int aIndex, uint8_t *aValue, uint16_t *aValueLength) const;
+    Error Get(uint16_t aKey, int aIndex, uint8_t *aValue, uint16_t *aValueLength) const;
 
     /**
      * This method sets or replaces the value identified by @p aKey.
@@ -131,11 +145,11 @@
      *                           MUST NOT be nullptr if @p aValueLength is non-zero.
      * @param[in]  aValueLength  The length of the data pointed to by @p aValue. May be zero.
      *
-     * @retval OT_ERROR_NONE     The value was changed.
-     * @retval OT_ERROR_NO_BUFS  Not enough space to store the value.
+     * @retval kErrorNone     The value was changed.
+     * @retval kErrorNoBufs   Not enough space to store the value.
      *
      */
-    otError Set(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength);
+    Error Set(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength);
 
     /**
      * This method remves all values.
@@ -581,14 +595,17 @@
      */
     enum Key
     {
-        kKeyActiveDataset     = 0x0001, ///< Active Operational Dataset
-        kKeyPendingDataset    = 0x0002, ///< Pending Operational Dataset
-        kKeyNetworkInfo       = 0x0003, ///< Thread network information
-        kKeyParentInfo        = 0x0004, ///< Parent information
-        kKeyChildInfo         = 0x0005, ///< Child information
-        kKeyReserved          = 0x0006, ///< Reserved (previously auto-start)
-        kKeySlaacIidSecretKey = 0x0007, ///< Secret key used by SLAAC module for generating semantically opaque IID
-        kKeyDadInfo           = 0x0008, ///< Duplicate Address Detection (DAD) information.
+        kKeyActiveDataset     = OT_SETTINGS_KEY_ACTIVE_DATASET,
+        kKeyPendingDataset    = OT_SETTINGS_KEY_PENDING_DATASET,
+        kKeyNetworkInfo       = OT_SETTINGS_KEY_NETWORK_INFO,
+        kKeyParentInfo        = OT_SETTINGS_KEY_PARENT_INFO,
+        kKeyChildInfo         = OT_SETTINGS_KEY_CHILD_INFO,
+        kKeyReserved          = OT_SETTINGS_KEY_RESERVED,
+        kKeySlaacIidSecretKey = OT_SETTINGS_KEY_SLAAC_IID_SECRET_KEY,
+        kKeyDadInfo           = OT_SETTINGS_KEY_DAD_INFO,
+        kKeyOmrPrefix         = OT_SETTINGS_KEY_OMR_PREFIX,
+        kKeyOnLinkPrefix      = OT_SETTINGS_KEY_ON_LINK_PREFIX,
+        kKeySrpEcdsaKey       = OT_SETTINGS_KEY_SRP_ECDSA_KEY,
     };
 
 protected:
@@ -604,6 +621,9 @@
 #if OPENTHREAD_CONFIG_DUA_ENABLE
     void LogDadInfo(const char *aAction, const DadInfo &aDadInfo) const;
 #endif
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+    void LogPrefix(const char *aAction, const char *aPrefixName, const Ip6::Prefix &aOmrPrefix) const;
+#endif
 #else // (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_UTIL != 0)
     void LogNetworkInfo(const char *, const NetworkInfo &) const {}
     void LogParentInfo(const char *, const ParentInfo &) const {}
@@ -611,12 +631,15 @@
 #if OPENTHREAD_CONFIG_DUA_ENABLE
     void LogDadInfo(const char *, const DadInfo &) const {}
 #endif
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+    void LogPrefix(const char *, const char *, const Ip6::Prefix &) const {}
+#endif
 #endif // (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_UTIL != 0)
 
 #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_WARN) && (OPENTHREAD_CONFIG_LOG_UTIL != 0)
-    void LogFailure(otError aError, const char *aAction, bool aIsDelete) const;
+    void LogFailure(Error aError, const char *aAction, bool aIsDelete) const;
 #else
-    void LogFailure(otError, const char *, bool) const {}
+    void LogFailure(Error, const char *, bool) const {}
 #endif
 };
 
@@ -668,11 +691,11 @@
      * @param[in]   aIsActive   Indicates whether Dataset is active or pending.
      * @param[in]   aDataset    A reference to a `Dataset` object to be saved.
      *
-     * @retval OT_ERROR_NONE              Successfully saved the Dataset.
-     * @retval OT_ERROR_NOT_IMPLEMENTED   The platform does not implement settings functionality.
+     * @retval kErrorNone             Successfully saved the Dataset.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
      *
      */
-    otError SaveOperationalDataset(bool aIsActive, const MeshCoP::Dataset &aDataset);
+    Error SaveOperationalDataset(bool aIsActive, const MeshCoP::Dataset &aDataset);
 
     /**
      * This method reads the Operational Dataset (active or pending).
@@ -680,87 +703,87 @@
      * @param[in]   aIsActive             Indicates whether Dataset is active or pending.
      * @param[out]  aDataset              A reference to a `Dataset` object to output the read content.
      *
-     * @retval OT_ERROR_NONE              Successfully read the Dataset.
-     * @retval OT_ERROR_NOT_FOUND         No corresponding value in the setting store.
-     * @retval OT_ERROR_NOT_IMPLEMENTED   The platform does not implement settings functionality.
+     * @retval kErrorNone             Successfully read the Dataset.
+     * @retval kErrorNotFound         No corresponding value in the setting store.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
      *
      */
-    otError ReadOperationalDataset(bool aIsActive, MeshCoP::Dataset &aDataset) const;
+    Error ReadOperationalDataset(bool aIsActive, MeshCoP::Dataset &aDataset) const;
 
     /**
      * This method deletes the Operational Dataset (active/pending) from settings.
      *
      * @param[in]   aIsActive            Indicates whether Dataset is active or pending.
      *
-     * @retval OT_ERROR_NONE             Successfully deleted the Dataset.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  The platform does not implement settings functionality.
+     * @retval kErrorNone            Successfully deleted the Dataset.
+     * @retval kErrorNotImplemented  The platform does not implement settings functionality.
      *
      */
-    otError DeleteOperationalDataset(bool aIsActive);
+    Error DeleteOperationalDataset(bool aIsActive);
 
     /**
      * This method saves Network Info.
      *
      * @param[in]   aNetworkInfo          A reference to a `NetworkInfo` structure to be saved.
      *
-     * @retval OT_ERROR_NONE              Successfully saved Network Info in settings.
-     * @retval OT_ERROR_NOT_IMPLEMENTED   The platform does not implement settings functionality.
+     * @retval kErrorNone             Successfully saved Network Info in settings.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
      *
      */
-    otError SaveNetworkInfo(const NetworkInfo &aNetworkInfo);
+    Error SaveNetworkInfo(const NetworkInfo &aNetworkInfo);
 
     /**
      * This method reads Network Info.
      *
      * @param[out]   aNetworkInfo         A reference to a `NetworkInfo` structure to output the read content.
      *
-     * @retval OT_ERROR_NONE              Successfully read the Network Info.
-     * @retval OT_ERROR_NOT_FOUND         No corresponding value in the setting store.
-     * @retval OT_ERROR_NOT_IMPLEMENTED   The platform does not implement settings functionality.
+     * @retval kErrorNone             Successfully read the Network Info.
+     * @retval kErrorNotFound         No corresponding value in the setting store.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
      *
      */
-    otError ReadNetworkInfo(NetworkInfo &aNetworkInfo) const;
+    Error ReadNetworkInfo(NetworkInfo &aNetworkInfo) const;
 
     /**
      * This method deletes Network Info from settings.
      *
-     * @retval OT_ERROR_NONE             Successfully deleted the value.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  The platform does not implement settings functionality.
+     * @retval kErrorNone            Successfully deleted the value.
+     * @retval kErrorNotImplemented  The platform does not implement settings functionality.
      *
      */
-    otError DeleteNetworkInfo(void);
+    Error DeleteNetworkInfo(void);
 
     /**
      * This method saves Parent Info.
      *
      * @param[in]   aParentInfo           A reference to a `ParentInfo` structure to be saved.
      *
-     * @retval OT_ERROR_NONE              Successfully saved Parent Info in settings.
-     * @retval OT_ERROR_NOT_IMPLEMENTED   The platform does not implement settings functionality.
+     * @retval kErrorNone             Successfully saved Parent Info in settings.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
      *
      */
-    otError SaveParentInfo(const ParentInfo &aParentInfo);
+    Error SaveParentInfo(const ParentInfo &aParentInfo);
 
     /**
      * This method reads Parent Info.
      *
      * @param[out]   aParentInfo         A reference to a `ParentInfo` structure to output the read content.
      *
-     * @retval OT_ERROR_NONE              Successfully read the Parent Info.
-     * @retval OT_ERROR_NOT_FOUND         No corresponding value in the setting store.
-     * @retval OT_ERROR_NOT_IMPLEMENTED   The platform does not implement settings functionality.
+     * @retval kErrorNone             Successfully read the Parent Info.
+     * @retval kErrorNotFound         No corresponding value in the setting store.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
      *
      */
-    otError ReadParentInfo(ParentInfo &aParentInfo) const;
+    Error ReadParentInfo(ParentInfo &aParentInfo) const;
 
     /**
      * This method deletes Parent Info from settings.
      *
-     * @retval OT_ERROR_NONE             Successfully deleted the value.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  The platform does not implement settings functionality.
+     * @retval kErrorNone            Successfully deleted the value.
+     * @retval kErrorNotImplemented  The platform does not implement settings functionality.
      *
      */
-    otError DeleteParentInfo(void);
+    Error DeleteParentInfo(void);
 
 #if OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE
 
@@ -769,11 +792,11 @@
      *
      * @param[in]   aKey                  The SLAAC IID secret key.
      *
-     * @retval OT_ERROR_NONE              Successfully saved the value.
-     * @retval OT_ERROR_NOT_IMPLEMENTED   The platform does not implement settings functionality.
+     * @retval kErrorNone             Successfully saved the value.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
      *
      */
-    otError SaveSlaacIidSecretKey(const Utils::Slaac::IidSecretKey &aKey)
+    Error SaveSlaacIidSecretKey(const Utils::Slaac::IidSecretKey &aKey)
     {
         return Save(kKeySlaacIidSecretKey, &aKey, sizeof(Utils::Slaac::IidSecretKey));
     }
@@ -783,12 +806,12 @@
      *
      * @param[out]   aKey          A reference to a SLAAC IID secret key to output the read value.
      *
-     * @retval OT_ERROR_NONE              Successfully read the value.
-     * @retval OT_ERROR_NOT_FOUND         No corresponding value in the setting store.
-     * @retval OT_ERROR_NOT_IMPLEMENTED   The platform does not implement settings functionality.
+     * @retval kErrorNone             Successfully read the value.
+     * @retval kErrorNotFound         No corresponding value in the setting store.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
      *
      */
-    otError ReadSlaacIidSecretKey(Utils::Slaac::IidSecretKey &aKey)
+    Error ReadSlaacIidSecretKey(Utils::Slaac::IidSecretKey &aKey)
     {
         uint16_t length = sizeof(aKey);
 
@@ -798,11 +821,11 @@
     /**
      * This method deletes the SLAAC IID secret key value from settings.
      *
-     * @retval OT_ERROR_NONE             Successfully deleted the value.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  The platform does not implement settings functionality.
+     * @retval kErrorNone            Successfully deleted the value.
+     * @retval kErrorNotImplemented  The platform does not implement settings functionality.
      *
      */
-    otError DeleteSlaacIidSecretKey(void) { return Delete(kKeySlaacIidSecretKey); }
+    Error DeleteSlaacIidSecretKey(void) { return Delete(kKeySlaacIidSecretKey); }
 
 #endif // OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE
 
@@ -813,22 +836,22 @@
      *
      * @param[in]   aChildInfo            A reference to a `ChildInfo` structure to be saved/added.
      *
-     * @retval OT_ERROR_NONE              Successfully saved the Child Info in settings.
-     * @retval OT_ERROR_NOT_IMPLEMENTED   The platform does not implement settings functionality.
+     * @retval kErrorNone             Successfully saved the Child Info in settings.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
      *
      */
-    otError AddChildInfo(const ChildInfo &aChildInfo);
+    Error AddChildInfo(const ChildInfo &aChildInfo);
 
     /**
      * This method deletes all Child Info entries from the settings.
      *
      * @note Child Info is a list-based settings property and can contain multiple entries.
      *
-     * @retval OT_ERROR_NONE             Successfully deleted the value.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  The platform does not implement settings functionality.
+     * @retval kErrorNone            Successfully deleted the value.
+     * @retval kErrorNotImplemented  The platform does not implement settings functionality.
      *
      */
-    otError DeleteAllChildInfo(void);
+    Error DeleteAllChildInfo(void);
 
     /**
      * This method enables range-based `for` loop iteration over all child info entries in the `Settings`.
@@ -847,7 +870,7 @@
      * This class defines an iterator to access all Child Info entries in the settings.
      *
      */
-    class ChildInfoIterator : public SettingsBase
+    class ChildInfoIterator : public SettingsBase, public Unequatable<ChildInfoIterator>
     {
         friend class ChildInfoIteratorBuilder;
 
@@ -898,12 +921,12 @@
         /**
          * This method deletes the current Child Info entry.
          *
-         * @retval OT_ERROR_NONE             The entry was deleted successfully.
-         * @retval OT_ERROR_INVALID_STATE    The entry is not valid (iterator has reached end of list).
-         * @retval OT_ERROR_NOT_IMPLEMENTED  The platform does not implement settings functionality.
+         * @retval kErrorNone            The entry was deleted successfully.
+         * @retval kErrorInvalidState    The entry is not valid (iterator has reached end of list).
+         * @retval kErrorNotImplemented  The platform does not implement settings functionality.
          *
          */
-        otError Delete(void);
+        Error Delete(void);
 
         /**
          * This method overloads the `*` dereference operator and gets a reference to `ChildInfo` entry to which the
@@ -932,17 +955,6 @@
             return (mIsDone && aOther.mIsDone) || (!mIsDone && !aOther.mIsDone && (mIndex == aOther.mIndex));
         }
 
-        /**
-         * This method overloads operator `!=` to evaluate whether or not two iterator instances are unequal.
-         *
-         * @param[in]  aOther  The other iterator to compare with.
-         *
-         * @retval TRUE   If the two iterator objects are unequal.
-         * @retval FALSE  If the two iterator objects are not unequal.
-         *
-         */
-        bool operator!=(const ChildInfoIterator &aOther) const { return !(*this == aOther); }
-
     private:
         enum IteratorType
         {
@@ -971,35 +983,117 @@
      *
      * @param[in]   aDadInfo           A reference to a `DadInfo` structure to be saved.
      *
-     * @retval OT_ERROR_NONE              Successfully saved duplicate address detection information in settings.
-     * @retval OT_ERROR_NOT_IMPLEMENTED   The platform does not implement settings functionality.
+     * @retval kErrorNone             Successfully saved duplicate address detection information in settings.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
      *
      */
-    otError SaveDadInfo(const DadInfo &aDadInfo);
+    Error SaveDadInfo(const DadInfo &aDadInfo);
 
     /**
      * This method reads duplicate address detection information.
      *
      * @param[out]   aDadInfo         A reference to a `DadInfo` structure to output the read content.
      *
-     * @retval OT_ERROR_NONE              Successfully read the duplicate address detection information.
-     * @retval OT_ERROR_NOT_FOUND         No corresponding value in the setting store.
-     * @retval OT_ERROR_NOT_IMPLEMENTED   The platform does not implement settings functionality.
+     * @retval kErrorNone             Successfully read the duplicate address detection information.
+     * @retval kErrorNotFound         No corresponding value in the setting store.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
      *
      */
-    otError ReadDadInfo(DadInfo &aDadInfo) const;
+    Error ReadDadInfo(DadInfo &aDadInfo) const;
 
     /**
      * This method deletes duplicate address detection information from settings.
      *
-     * @retval OT_ERROR_NONE             Successfully deleted the value.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  The platform does not implement settings functionality.
+     * @retval kErrorNone            Successfully deleted the value.
+     * @retval kErrorNotImplemented  The platform does not implement settings functionality.
      *
      */
-    otError DeleteDadInfo(void);
+    Error DeleteDadInfo(void);
 
 #endif // OPENTHREAD_CONFIG_DUA_ENABLE
 
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+    /**
+     * This method saves OMR prefix.
+     *
+     * @param[in]  aOmrPrefix  An OMR prefix to be saved.
+     *
+     * @retval kErrorNone             Successfully saved the OMR prefix in settings.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
+     *
+     */
+    Error SaveOmrPrefix(const Ip6::Prefix &aOmrPrefix);
+
+    /**
+     * This method reads OMR prefix.
+     *
+     * @param[out]  aOmrPrefix  A reference to a `Ip6::Prefix` structure to output the OMR prefix.
+     *
+     * @retval kErrorNone            Successfully read the OMR prefix.
+     * @retval kErrorNotFound        No corresponding value in the setting store.
+     * @retval kErrorNotImplemented  The platform does not implement settings functionality.
+     *
+     */
+    Error ReadOmrPrefix(Ip6::Prefix &aOmrPrefix) const;
+
+    /**
+     * This method saves on-link prefix.
+     *
+     * @param[in]  aOnLinkPrefix  An on-link prefix to be saved.
+     *
+     * @retval kErrorNone             Successfully saved the on-link prefix in settings.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
+     *
+     */
+    Error SaveOnLinkPrefix(const Ip6::Prefix &aOnLinkPrefix);
+
+    /**
+     * This method reads on-link prefix.
+     *
+     * @param[out]  aOnLinkPrefix  A reference to a `Ip6::Prefix` structure to output the on-link prefix.
+     *
+     * @retval kErrorNone            Successfully read the on-link prefix.
+     * @retval kErrorNotFound        No corresponding value in the setting store.
+     * @retval kErrorNotImplemented  The platform does not implement settings functionality.
+     *
+     */
+    Error ReadOnLinkPrefix(Ip6::Prefix &aOnLinkPrefix) const;
+#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+    /**
+     * This method saves SRP client ECDSA key pair.
+     *
+     * @param[in]   aKeyPair              A reference to an SRP ECDSA key-pair to save.
+     *
+     * @retval kErrorNone             Successfully saved key-pair information in settings.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
+     *
+     */
+    Error SaveSrpKey(const Crypto::Ecdsa::P256::KeyPair &aKeyPair);
+
+    /**
+     * This method reads SRP client ECDSA key pair.
+     *
+     * @param[out]   aKeyPair             A reference to a ECDA `KeyPair` to output the read content.
+     *
+     * @retval kErrorNone             Successfully read key-pair information.
+     * @retval kErrorNotFound         No corresponding value in the setting store.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
+     *
+     */
+    Error ReadSrpKey(Crypto::Ecdsa::P256::KeyPair &aKeyPair) const;
+
+    /**
+     * This method deletes SRP client ECDSA key pair from settings.
+     *
+     * @retval kErrorNone            Successfully deleted the value.
+     * @retval kErrorNotImplemented  The platform does not implement settings functionality.
+     *
+     */
+    Error DeleteSrpKey(void);
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+
 private:
     class ChildInfoIteratorBuilder : public InstanceLocator
     {
@@ -1013,10 +1107,10 @@
         ChildInfoIterator end(void) { return ChildInfoIterator(GetInstance(), ChildInfoIterator::kEndIterator); }
     };
 
-    otError Read(Key aKey, void *aBuffer, uint16_t &aSize) const;
-    otError Save(Key aKey, const void *aValue, uint16_t aSize);
-    otError Add(Key aKey, const void *aValue, uint16_t aSize);
-    otError Delete(Key aKey);
+    Error Read(Key aKey, void *aBuffer, uint16_t &aSize) const;
+    Error Save(Key aKey, const void *aValue, uint16_t aSize);
+    Error Add(Key aKey, const void *aValue, uint16_t aSize);
+    Error Delete(Key aKey);
 };
 
 } // namespace ot
diff --git a/src/core/common/string.cpp b/src/core/common/string.cpp
index cba642b..150016b 100644
--- a/src/core/common/string.cpp
+++ b/src/core/common/string.cpp
@@ -65,10 +65,10 @@
     return ret;
 }
 
-otError StringBase::Write(char *aBuffer, uint16_t aSize, uint16_t &aLength, const char *aFormat, va_list aArgs)
+Error StringBase::Write(char *aBuffer, uint16_t aSize, uint16_t &aLength, const char *aFormat, va_list aArgs)
 {
-    otError error = OT_ERROR_NONE;
-    int     len;
+    Error error = kErrorNone;
+    int   len;
 
     len = vsnprintf(aBuffer + aLength, aSize - aLength, aFormat, aArgs);
 
@@ -76,12 +76,12 @@
     {
         aLength    = 0;
         aBuffer[0] = 0;
-        error      = OT_ERROR_INVALID_ARGS;
+        error      = kErrorInvalidArgs;
     }
     else if (len >= aSize - aLength)
     {
         aLength = aSize - 1;
-        error   = OT_ERROR_NO_BUFS;
+        error   = kErrorNoBufs;
     }
     else
     {
diff --git a/src/core/common/string.hpp b/src/core/common/string.hpp
index 934b501..c837ca9 100644
--- a/src/core/common/string.hpp
+++ b/src/core/common/string.hpp
@@ -40,9 +40,8 @@
 #include <stdint.h>
 #include <stdio.h>
 
-#include <openthread/error.h>
-
 #include "common/code_utils.hpp"
+#include "common/error.hpp"
 
 namespace ot {
 
@@ -95,11 +94,11 @@
      * @param[in]    aFormat  A pointer to the format string.
      * @param[in]    aArgs    Arguments for the format specification.
      *
-     * @retval OT_ERROR_NONE           Updated the string successfully.
-     * @retval OT_ERROR_NO_BUFS        String could not fit in the storage.
-     * @retval OT_ERROR_INVALID_ARGS   Arguments do not match the format string.
+     * @retval kErrorNone          Updated the string successfully.
+     * @retval kErrorNoBufs        String could not fit in the storage.
+     * @retval kErrorInvalidArgs   Arguments do not match the format string.
      */
-    static otError Write(char *aBuffer, uint16_t aSize, uint16_t &aLength, const char *aFormat, va_list aArgs);
+    static Error Write(char *aBuffer, uint16_t aSize, uint16_t &aLength, const char *aFormat, va_list aArgs);
 };
 
 /**
@@ -182,15 +181,15 @@
      * @param[in] aFormat    A pointer to the format string.
      * @param[in] ...        Arguments for the format specification.
      *
-     * @retval OT_ERROR_NONE           Updated the string successfully.
-     * @retval OT_ERROR_NO_BUFS        String could not fit in the storage.
-     * @retval OT_ERROR_INVALID_ARGS   Arguments do not match the format string.
+     * @retval kErrorNone          Updated the string successfully.
+     * @retval kErrorNoBufs        String could not fit in the storage.
+     * @retval kErrorInvalidArgs   Arguments do not match the format string.
      *
      */
-    otError Set(const char *aFormat, ...)
+    Error Set(const char *aFormat, ...)
     {
         va_list args;
-        otError error;
+        Error   error;
 
         va_start(args, aFormat);
         mLength = 0;
@@ -206,15 +205,15 @@
      * @param[in] aFormat    A pointer to the format string.
      * @param[in] ...        Arguments for the format specification.
      *
-     * @retval OT_ERROR_NONE           Updated the string successfully.
-     * @retval OT_ERROR_NO_BUFS        String could not fit in the storage.
-     * @retval OT_ERROR_INVALID_ARGS   Arguments do not match the format string.
+     * @retval kErrorNone          Updated the string successfully.
+     * @retval kErrorNoBufs        String could not fit in the storage.
+     * @retval kErrorInvalidArgs   Arguments do not match the format string.
      *
      */
-    otError Append(const char *aFormat, ...)
+    Error Append(const char *aFormat, ...)
     {
         va_list args;
-        otError error;
+        Error   error;
 
         va_start(args, aFormat);
         error = Write(mBuffer, kSize, mLength, aFormat, args);
@@ -229,12 +228,12 @@
      * @param[in] aFormat    A pointer to the format string.
      * @param[in] aArgs      Arguments for the format specification (as `va_list`).
      *
-     * @retval OT_ERROR_NONE           Updated the string successfully.
-     * @retval OT_ERROR_NO_BUFS        String could not fit in the storage.
-     * @retval OT_ERROR_INVALID_ARGS   Arguments do not match the format string.
+     * @retval kErrorNone          Updated the string successfully.
+     * @retval kErrorNoBufs        String could not fit in the storage.
+     * @retval kErrorInvalidArgs   Arguments do not match the format string.
      *
      */
-    otError AppendVarArgs(const char *aFormat, va_list aArgs) { return Write(mBuffer, kSize, mLength, aFormat, aArgs); }
+    Error AppendVarArgs(const char *aFormat, va_list aArgs) { return Write(mBuffer, kSize, mLength, aFormat, aArgs); }
 
     /**
      * This method appends an array of bytes in hex representation (using "%02x" style) to the `String` object.
@@ -242,13 +241,13 @@
      * @param[in] aBytes    A pointer to buffer containing the bytes to append.
      * @param[in] aLength   The length of @p aBytes buffer (in bytes).
      *
-     * @retval OT_ERROR_NONE           Updated the string successfully.
-     * @retval OT_ERROR_NO_BUFS        String could not fit in the storage.
+     * @retval kErrorNone          Updated the string successfully.
+     * @retval kErrorNoBufs        String could not fit in the storage.
      *
      */
-    otError AppendHexBytes(const uint8_t *aBytes, uint16_t aLength)
+    Error AppendHexBytes(const uint8_t *aBytes, uint16_t aLength)
     {
-        otError error = OT_ERROR_NONE;
+        Error error = kErrorNone;
 
         while (aLength--)
         {
diff --git a/src/core/common/tasklet.cpp b/src/core/common/tasklet.cpp
index de8bab9..68ff27e 100644
--- a/src/core/common/tasklet.cpp
+++ b/src/core/common/tasklet.cpp
@@ -41,9 +41,8 @@
 
 namespace ot {
 
-Tasklet::Tasklet(Instance &aInstance, Handler aHandler, void *aOwner)
+Tasklet::Tasklet(Instance &aInstance, Handler aHandler)
     : InstanceLocator(aInstance)
-    , OwnerLocator(aOwner)
     , mHandler(aHandler)
     , mNext(nullptr)
 {
diff --git a/src/core/common/tasklet.hpp b/src/core/common/tasklet.hpp
index 198858c..9d9b9e5 100644
--- a/src/core/common/tasklet.hpp
+++ b/src/core/common/tasklet.hpp
@@ -61,7 +61,7 @@
  * This class is used to represent a tasklet.
  *
  */
-class Tasklet : public InstanceLocator, public OwnerLocator
+class Tasklet : public InstanceLocator
 {
     friend class TaskletScheduler;
 
@@ -79,10 +79,9 @@
      *
      * @param[in]  aInstance   A reference to the OpenThread instance object.
      * @param[in]  aHandler    A pointer to a function that is called when the tasklet is run.
-     * @param[in]  aOwner      A pointer to owner of this `Tasklet` object.
      *
      */
-    Tasklet(Instance &aInstance, Handler aHandler, void *aOwner);
+    Tasklet(Instance &aInstance, Handler aHandler);
 
     /**
      * This method puts the tasklet on the tasklet scheduler run queue.
@@ -129,7 +128,7 @@
      *
      */
     TaskletContext(Instance &aInstance, Handler aHandler, void *aContext)
-        : Tasklet(aInstance, aHandler, aContext)
+        : Tasklet(aInstance, aHandler)
         , mContext(aContext)
     {
     }
diff --git a/src/core/common/time.hpp b/src/core/common/time.hpp
index 2f63e61..98c299b 100644
--- a/src/core/common/time.hpp
+++ b/src/core/common/time.hpp
@@ -39,6 +39,8 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include "common/equatable.hpp"
+
 namespace ot {
 
 /**
@@ -55,7 +57,7 @@
  * This class represents a time instance.
  *
  */
-class Time
+class Time : public Unequatable<Time>
 {
 public:
     /**
@@ -157,17 +159,6 @@
     bool operator==(const Time &aOther) const { return mValue == aOther.mValue; }
 
     /**
-     * This method indicates whether two `Time` instance are not equal.
-     *
-     * @param[in]   aOther   A `Time` instance to compare with.
-     *
-     * @retval TRUE    The two `Time` instances are not equal.
-     * @retval FALSE   The two `Time` instances are equal.
-     *
-     */
-    bool operator!=(const Time &aOther) const { return !(*this == aOther); }
-
-    /**
      * This method indicates whether this `Time` instance is strictly before another one.
      *
      * @note The comparison operators correctly take into account the wrapping of `Time` numeric value. For a given
diff --git a/src/core/common/time_ticker.cpp b/src/core/common/time_ticker.cpp
index 10461d0..28834b9 100644
--- a/src/core/common/time_ticker.cpp
+++ b/src/core/common/time_ticker.cpp
@@ -45,7 +45,7 @@
 TimeTicker::TimeTicker(Instance &aInstance)
     : InstanceLocator(aInstance)
     , mReceivers(0)
-    , mTimer(aInstance, HandleTimer, this)
+    , mTimer(aInstance, HandleTimer)
 {
 }
 
@@ -71,7 +71,7 @@
 
 void TimeTicker::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<TimeTicker>().HandleTimer();
+    aTimer.Get<TimeTicker>().HandleTimer();
 }
 
 void TimeTicker::HandleTimer(void)
diff --git a/src/core/common/timer.hpp b/src/core/common/timer.hpp
index 751eff1..7158fb1 100644
--- a/src/core/common/timer.hpp
+++ b/src/core/common/timer.hpp
@@ -67,7 +67,7 @@
  * This class implements a timer.
  *
  */
-class Timer : public InstanceLocator, public OwnerLocator, public LinkedListEntry<Timer>
+class Timer : public InstanceLocator, public LinkedListEntry<Timer>
 {
     friend class TimerScheduler;
     friend class LinkedListEntry<Timer>;
@@ -92,12 +92,10 @@
      *
      * @param[in]  aInstance   A reference to the OpenThread instance.
      * @param[in]  aHandler    A pointer to a function that is called when the timer expires.
-     * @param[in]  aOwner      A pointer to owner of the `Timer` object.
      *
      */
-    Timer(Instance &aInstance, Handler aHandler, void *aOwner)
+    Timer(Instance &aInstance, Handler aHandler)
         : InstanceLocator(aInstance)
-        , OwnerLocator(aOwner)
         , mHandler(aHandler)
         , mFireTime()
         , mNext(this)
@@ -153,11 +151,10 @@
      *
      * @param[in]  aInstance   A reference to the OpenThread instance.
      * @param[in]  aHandler    A pointer to a function that is called when the timer expires.
-     * @param[in]  aOwner      A pointer to the owner of the `TimerMilli` object.
      *
      */
-    TimerMilli(Instance &aInstance, Handler aHandler, void *aOwner)
-        : Timer(aInstance, aHandler, aOwner)
+    TimerMilli(Instance &aInstance, Handler aHandler)
+        : Timer(aInstance, aHandler)
     {
     }
 
@@ -231,7 +228,7 @@
      *
      */
     TimerMilliContext(Instance &aInstance, Handler aHandler, void *aContext)
-        : TimerMilli(aInstance, aHandler, aContext)
+        : TimerMilli(aInstance, aHandler)
         , mContext(aContext)
     {
     }
@@ -375,11 +372,10 @@
      *
      * @param[in]  aInstance   A reference to the OpenThread instance.
      * @param[in]  aHandler    A pointer to a function that is called when the timer expires.
-     * @param[in]  aOwner      A pointer to owner of the `TimerMicro` object.
      *
      */
-    TimerMicro(Instance &aInstance, Handler aHandler, void *aOwner)
-        : Timer(aInstance, aHandler, aOwner)
+    TimerMicro(Instance &aInstance, Handler aHandler)
+        : Timer(aInstance, aHandler)
     {
     }
 
diff --git a/src/core/common/tlvs.cpp b/src/core/common/tlvs.cpp
index 46b3049..3b098e9 100644
--- a/src/core/common/tlvs.cpp
+++ b/src/core/common/tlvs.cpp
@@ -55,14 +55,14 @@
     return reinterpret_cast<const uint8_t *>(this) + (IsExtended() ? sizeof(ExtendedTlv) : sizeof(Tlv));
 }
 
-otError Tlv::AppendTo(Message &aMessage) const
+Error Tlv::AppendTo(Message &aMessage) const
 {
     return aMessage.AppendBytes(this, static_cast<uint16_t>(GetSize()));
 }
 
-otError Tlv::FindTlv(const Message &aMessage, uint8_t aType, uint16_t aMaxSize, Tlv &aTlv)
+Error Tlv::FindTlv(const Message &aMessage, uint8_t aType, uint16_t aMaxSize, Tlv &aTlv)
 {
-    otError  error;
+    Error    error;
     uint16_t offset;
     uint16_t size;
 
@@ -79,14 +79,14 @@
     return error;
 }
 
-otError Tlv::FindTlvOffset(const Message &aMessage, uint8_t aType, uint16_t &aOffset)
+Error Tlv::FindTlvOffset(const Message &aMessage, uint8_t aType, uint16_t &aOffset)
 {
     return Find(aMessage, aType, &aOffset, nullptr, nullptr);
 }
 
-otError Tlv::FindTlvValueOffset(const Message &aMessage, uint8_t aType, uint16_t &aValueOffset, uint16_t &aLength)
+Error Tlv::FindTlvValueOffset(const Message &aMessage, uint8_t aType, uint16_t &aValueOffset, uint16_t &aLength)
 {
-    otError  error;
+    Error    error;
     uint16_t offset;
     uint16_t size;
     bool     isExtendedTlv;
@@ -108,9 +108,9 @@
     return error;
 }
 
-otError Tlv::Find(const Message &aMessage, uint8_t aType, uint16_t *aOffset, uint16_t *aSize, bool *aIsExtendedTlv)
+Error Tlv::Find(const Message &aMessage, uint8_t aType, uint16_t *aOffset, uint16_t *aSize, bool *aIsExtendedTlv)
 {
-    otError  error        = OT_ERROR_NOT_FOUND;
+    Error    error        = kErrorNotFound;
     uint16_t offset       = aMessage.GetOffset();
     uint16_t remainingLen = aMessage.GetLength();
     Tlv      tlv;
@@ -156,7 +156,7 @@
                 *aIsExtendedTlv = (tlv.mLength == kExtendedLength);
             }
 
-            error = OT_ERROR_NONE;
+            error = kErrorNone;
             ExitNow();
         }
 
@@ -168,9 +168,9 @@
     return error;
 }
 
-template <typename UintType> otError Tlv::ReadUintTlv(const Message &aMessage, uint16_t aOffset, UintType &aValue)
+template <typename UintType> Error Tlv::ReadUintTlv(const Message &aMessage, uint16_t aOffset, UintType &aValue)
 {
-    otError error;
+    Error error;
 
     SuccessOrExit(error = ReadTlv(aMessage, aOffset, &aValue, sizeof(aValue)));
     aValue = Encoding::BigEndian::HostSwap<UintType>(aValue);
@@ -180,18 +180,18 @@
 }
 
 // Explicit instantiations of `ReadUintTlv<>()`
-template otError Tlv::ReadUintTlv<uint8_t>(const Message &aMessage, uint16_t aOffset, uint8_t &aValue);
-template otError Tlv::ReadUintTlv<uint16_t>(const Message &aMessage, uint16_t aOffset, uint16_t &aValue);
-template otError Tlv::ReadUintTlv<uint32_t>(const Message &aMessage, uint16_t aOffset, uint32_t &aValue);
+template Error Tlv::ReadUintTlv<uint8_t>(const Message &aMessage, uint16_t aOffset, uint8_t &aValue);
+template Error Tlv::ReadUintTlv<uint16_t>(const Message &aMessage, uint16_t aOffset, uint16_t &aValue);
+template Error Tlv::ReadUintTlv<uint32_t>(const Message &aMessage, uint16_t aOffset, uint32_t &aValue);
 
-otError Tlv::ReadTlv(const Message &aMessage, uint16_t aOffset, void *aValue, uint8_t aMinLength)
+Error Tlv::ReadTlv(const Message &aMessage, uint16_t aOffset, void *aValue, uint8_t aMinLength)
 {
-    otError error = OT_ERROR_NONE;
-    Tlv     tlv;
+    Error error = kErrorNone;
+    Tlv   tlv;
 
     SuccessOrExit(error = aMessage.Read(aOffset, tlv));
-    VerifyOrExit(!tlv.IsExtended() && (tlv.GetLength() >= aMinLength), error = OT_ERROR_PARSE);
-    VerifyOrExit(tlv.GetSize() + aOffset <= aMessage.GetLength(), error = OT_ERROR_PARSE);
+    VerifyOrExit(!tlv.IsExtended() && (tlv.GetLength() >= aMinLength), error = kErrorParse);
+    VerifyOrExit(tlv.GetSize() + aOffset <= aMessage.GetLength(), error = kErrorParse);
 
     aMessage.ReadBytes(aOffset + sizeof(Tlv), aValue, aMinLength);
 
@@ -199,9 +199,9 @@
     return error;
 }
 
-template <typename UintType> otError Tlv::FindUintTlv(const Message &aMessage, uint8_t aType, UintType &aValue)
+template <typename UintType> Error Tlv::FindUintTlv(const Message &aMessage, uint8_t aType, UintType &aValue)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     uint16_t offset;
 
     SuccessOrExit(error = FindTlvOffset(aMessage, aType, offset));
@@ -212,25 +212,25 @@
 }
 
 // Explicit instantiations of `FindUintTlv<>()`
-template otError Tlv::FindUintTlv<uint8_t>(const Message &aMessage, uint8_t aType, uint8_t &aValue);
-template otError Tlv::FindUintTlv<uint16_t>(const Message &aMessage, uint8_t aType, uint16_t &aValue);
-template otError Tlv::FindUintTlv<uint32_t>(const Message &aMessage, uint8_t aType, uint32_t &aValue);
+template Error Tlv::FindUintTlv<uint8_t>(const Message &aMessage, uint8_t aType, uint8_t &aValue);
+template Error Tlv::FindUintTlv<uint16_t>(const Message &aMessage, uint8_t aType, uint16_t &aValue);
+template Error Tlv::FindUintTlv<uint32_t>(const Message &aMessage, uint8_t aType, uint32_t &aValue);
 
-otError Tlv::FindTlv(const Message &aMessage, uint8_t aType, void *aValue, uint8_t aLength)
+Error Tlv::FindTlv(const Message &aMessage, uint8_t aType, void *aValue, uint8_t aLength)
 {
-    otError  error;
+    Error    error;
     uint16_t offset;
     uint16_t length;
 
     SuccessOrExit(error = FindTlvValueOffset(aMessage, aType, offset, length));
-    VerifyOrExit(length >= aLength, error = OT_ERROR_PARSE);
+    VerifyOrExit(length >= aLength, error = kErrorParse);
     aMessage.ReadBytes(offset, aValue, aLength);
 
 exit:
     return error;
 }
 
-template <typename UintType> otError Tlv::AppendUintTlv(Message &aMessage, uint8_t aType, UintType aValue)
+template <typename UintType> Error Tlv::AppendUintTlv(Message &aMessage, uint8_t aType, UintType aValue)
 {
     UintType value = Encoding::BigEndian::HostSwap<UintType>(aValue);
 
@@ -238,14 +238,14 @@
 }
 
 // Explicit instantiations of `AppendUintTlv<>()`
-template otError Tlv::AppendUintTlv<uint8_t>(Message &aMessage, uint8_t aType, uint8_t aValue);
-template otError Tlv::AppendUintTlv<uint16_t>(Message &aMessage, uint8_t aType, uint16_t aValue);
-template otError Tlv::AppendUintTlv<uint32_t>(Message &aMessage, uint8_t aType, uint32_t aValue);
+template Error Tlv::AppendUintTlv<uint8_t>(Message &aMessage, uint8_t aType, uint8_t aValue);
+template Error Tlv::AppendUintTlv<uint16_t>(Message &aMessage, uint8_t aType, uint16_t aValue);
+template Error Tlv::AppendUintTlv<uint32_t>(Message &aMessage, uint8_t aType, uint32_t aValue);
 
-otError Tlv::AppendTlv(Message &aMessage, uint8_t aType, const void *aValue, uint8_t aLength)
+Error Tlv::AppendTlv(Message &aMessage, uint8_t aType, const void *aValue, uint8_t aLength)
 {
-    otError error = OT_ERROR_NONE;
-    Tlv     tlv;
+    Error error = kErrorNone;
+    Tlv   tlv;
 
     OT_ASSERT(aLength <= Tlv::kBaseTlvMaxLength);
 
diff --git a/src/core/common/tlvs.hpp b/src/core/common/tlvs.hpp
index b1941d4..7dfe150 100644
--- a/src/core/common/tlvs.hpp
+++ b/src/core/common/tlvs.hpp
@@ -36,11 +36,11 @@
 
 #include "openthread-core-config.h"
 
-#include <openthread/error.h>
 #include <openthread/thread.h>
 #include <openthread/platform/toolchain.h>
 
 #include "common/encoding.hpp"
+#include "common/error.hpp"
 #include "common/type_traits.hpp"
 
 namespace ot {
@@ -171,11 +171,11 @@
      *
      * @param[in]  aMessage      A reference to the message to append to.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the TLV to the message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient available buffers to grow the message.
+     * @retval kErrorNone     Successfully appended the TLV to the message.
+     * @retval kErrorNoBufs   Insufficient available buffers to grow the message.
      *
      */
-    otError AppendTo(Message &aMessage) const;
+    Error AppendTo(Message &aMessage) const;
 
     /**
      * This static method reads a TLV in a message at a given offset expecting a minimum length for the value.
@@ -185,11 +185,11 @@
      * @param[out]  aValue      A buffer to output the TLV's value, must contain (at least) @p aMinLength bytes.
      * @param[in]   aMinLength  The minimum expected length of TLV and number of bytes to copy into @p aValue buffer.
      *
-     * @retval OT_ERROR_NONE        Successfully read the TLV and copied @p aMinLength into @p aValue.
-     * @retval OT_ERROR_PARSE       The TLV was not well-formed and could not be parsed.
+     * @retval kErrorNone        Successfully read the TLV and copied @p aMinLength into @p aValue.
+     * @retval kErrorParse       The TLV was not well-formed and could not be parsed.
      *
      */
-    static otError ReadTlv(const Message &aMessage, uint16_t aOffset, void *aValue, uint8_t aMinLength);
+    static Error ReadTlv(const Message &aMessage, uint16_t aOffset, void *aValue, uint8_t aMinLength);
 
     /**
      * This static method reads a simple TLV with a single non-integral value in a message at a given offset.
@@ -200,12 +200,12 @@
      * @param[in]   aOffset         The offset into the message pointing to the start of the TLV.
      * @param[out]  aValue          A reference to the value object to output the read value.
      *
-     * @retval OT_ERROR_NONE        Successfully read the TLV and updated the @p aValue.
-     * @retval OT_ERROR_PARSE       The TLV was not well-formed and could not be parsed.
+     * @retval kErrorNone        Successfully read the TLV and updated the @p aValue.
+     * @retval kErrorParse       The TLV was not well-formed and could not be parsed.
      *
      */
     template <typename SimpleTlvType>
-    static otError Read(const Message &aMessage, uint16_t aOffset, typename SimpleTlvType::ValueType &aValue)
+    static Error Read(const Message &aMessage, uint16_t aOffset, typename SimpleTlvType::ValueType &aValue)
     {
         return ReadTlv(aMessage, aOffset, &aValue, sizeof(aValue));
     }
@@ -219,12 +219,12 @@
      * @param[in]   aOffset         The offset into the message pointing to the start of the TLV.
      * @param[out]  aValue          A reference to an unsigned int to output the read value.
      *
-     * @retval OT_ERROR_NONE        Successfully read the TLV and updated the @p aValue.
-     * @retval OT_ERROR_PARSE       The TLV was not well-formed and could not be parsed.
+     * @retval kErrorNone        Successfully read the TLV and updated the @p aValue.
+     * @retval kErrorParse       The TLV was not well-formed and could not be parsed.
      *
      */
     template <typename UintTlvType>
-    static otError Read(const Message &aMessage, uint16_t aOffset, typename UintTlvType::UintValueType &aValue)
+    static Error Read(const Message &aMessage, uint16_t aOffset, typename UintTlvType::UintValueType &aValue)
     {
         return ReadUintTlv(aMessage, aOffset, aValue);
     }
@@ -239,11 +239,11 @@
      * @param[in]   aMaxSize    Maximum number of bytes to read.
      * @param[out]  aTlv        A reference to the TLV that will be copied to.
      *
-     * @retval OT_ERROR_NONE       Successfully copied the TLV.
-     * @retval OT_ERROR_NOT_FOUND  Could not find the TLV with Type @p aType.
+     * @retval kErrorNone       Successfully copied the TLV.
+     * @retval kErrorNotFound   Could not find the TLV with Type @p aType.
      *
      */
-    static otError FindTlv(const Message &aMessage, uint8_t aType, uint16_t aMaxSize, Tlv &aTlv);
+    static Error FindTlv(const Message &aMessage, uint8_t aType, uint16_t aMaxSize, Tlv &aTlv);
 
     /**
      * This static method searches for and reads a requested TLV out of a given message.
@@ -255,11 +255,11 @@
      * @param[in]   aMessage    A reference to the message.
      * @param[out]  aTlv        A reference to the TLV that will be copied to.
      *
-     * @retval OT_ERROR_NONE       Successfully copied the TLV.
-     * @retval OT_ERROR_NOT_FOUND  Could not find the TLV with Type @p aType.
+     * @retval kErrorNone       Successfully copied the TLV.
+     * @retval kErrorNotFound   Could not find the TLV with Type @p aType.
      *
      */
-    template <typename TlvType> static otError FindTlv(const Message &aMessage, TlvType &aTlv)
+    template <typename TlvType> static Error FindTlv(const Message &aMessage, TlvType &aTlv)
     {
         return FindTlv(aMessage, TlvType::kType, sizeof(TlvType), aTlv);
     }
@@ -273,11 +273,11 @@
      * @param[in]   aType       The Type value to search for.
      * @param[out]  aOffset     A reference to the offset of the TLV.
      *
-     * @retval OT_ERROR_NONE       Successfully copied the TLV.
-     * @retval OT_ERROR_NOT_FOUND  Could not find the TLV with Type @p aType.
+     * @retval kErrorNone       Successfully copied the TLV.
+     * @retval kErrorNotFound   Could not find the TLV with Type @p aType.
      *
      */
-    static otError FindTlvOffset(const Message &aMessage, uint8_t aType, uint16_t &aOffset);
+    static Error FindTlvOffset(const Message &aMessage, uint8_t aType, uint16_t &aOffset);
 
     /**
      * This static method finds the offset and length of a given TLV type.
@@ -289,21 +289,18 @@
      * @param[out]  aValueOffset  The offset where the value starts.
      * @param[out]  aLength       The length of the value.
      *
-     * @retval OT_ERROR_NONE       Successfully found the TLV.
-     * @retval OT_ERROR_NOT_FOUND  Could not find the TLV with Type @p aType.
+     * @retval kErrorNone       Successfully found the TLV.
+     * @retval kErrorNotFound   Could not find the TLV with Type @p aType.
      *
      */
-    static otError FindTlvValueOffset(const Message &aMessage,
-                                      uint8_t        aType,
-                                      uint16_t &     aValueOffset,
-                                      uint16_t &     aLength);
+    static Error FindTlvValueOffset(const Message &aMessage, uint8_t aType, uint16_t &aValueOffset, uint16_t &aLength);
 
     /**
      * This static method searches for a TLV with a given type in a message, ensures its length is same or larger than
      * an expected minimum value, and then reads its value into a given buffer.
      *
      * If the TLV length is smaller than the minimum length @p aLength, the TLV is considered invalid. In this case,
-     * this method returns `OT_ERROR_PARSE` and the @p aValue buffer is not updated.
+     * this method returns `kErrorParse` and the @p aValue buffer is not updated.
      *
      * If the TLV length is larger than @p aLength, the TLV is considered valid, but only the first @p aLength bytes
      * of the value are read and copied into the @p aValue buffer.
@@ -315,12 +312,12 @@
      * @param[out]   aValue      A buffer to output the value (must contain at least @p aLength bytes).
      * @param[in]    aLength     The expected (minimum) length of the TLV value.
      *
-     * @retval OT_ERROR_NONE       The TLV was found and read successfully. @p aValue is updated.
-     * @retval OT_ERROR_NOT_FOUND  Could not find the TLV with Type @p aType.
-     * @retval OT_ERROR_PARSE      TLV was found but it was not well-formed and could not be parsed.
+     * @retval kErrorNone       The TLV was found and read successfully. @p aValue is updated.
+     * @retval kErrorNotFound   Could not find the TLV with Type @p aType.
+     * @retval kErrorParse      TLV was found but it was not well-formed and could not be parsed.
      *
      */
-    template <typename TlvType> static otError Find(const Message &aMessage, void *aValue, uint8_t aLength)
+    template <typename TlvType> static Error Find(const Message &aMessage, void *aValue, uint8_t aLength)
     {
         return FindTlv(aMessage, TlvType::kType, aValue, aLength);
     }
@@ -330,7 +327,7 @@
      * same or larger than the expected `ValueType` object size, and then reads its value into a value object reference.
      *
      * If the TLV length is smaller than the size of @p aValue, the TLV is considered invalid. In this case, this
-     * method returns `OT_ERROR_PARSE` and the @p aValue is not updated.
+     * method returns `kErrorParse` and the @p aValue is not updated.
      *
      * If the TLV length is larger than the size of @p aValue, the TLV is considered valid, but the size of
      * `ValueType` bytes are read and copied into the @p aValue.
@@ -341,13 +338,13 @@
      * @param[in]    aType           The TLV type to search for.
      * @param[out]   aValue          A reference to the value object to output the read value.
      *
-     * @retval OT_ERROR_NONE         The TLV was found and read successfully. @p aValue is updated.
-     * @retval OT_ERROR_NOT_FOUND    Could not find the TLV with Type @p aType.
-     * @retval OT_ERROR_PARSE        TLV was found but it was not well-formed and could not be parsed.
+     * @retval kErrorNone         The TLV was found and read successfully. @p aValue is updated.
+     * @retval kErrorNotFound     Could not find the TLV with Type @p aType.
+     * @retval kErrorParse        TLV was found but it was not well-formed and could not be parsed.
      *
      */
     template <typename SimpleTlvType>
-    static otError Find(const Message &aMessage, typename SimpleTlvType::ValueType &aValue)
+    static Error Find(const Message &aMessage, typename SimpleTlvType::ValueType &aValue)
     {
         return FindTlv(aMessage, SimpleTlvType::kType, &aValue, sizeof(aValue));
     }
@@ -357,20 +354,20 @@
      * into a given `uint` reference variable.
      *
      * If the TLV length is smaller than size of integral value, the TLV is considered invalid. In this case, this
-     * method returns `OT_ERROR_PARSE` and the @p aValue is not updated.
+     * method returns `kErrorParse` and the @p aValue is not updated.
      *
      * @tparam       UintTlvType     The simple TLV type to find (must be a sub-class of `UintTlvInfo`)
      *
      * @param[in]    aMessage        A reference to the message.
      * @param[out]   aValue          A reference to an unsigned int value to output the TLV's value.
      *
-     * @retval OT_ERROR_NONE         The TLV was found and read successfully. @p aValue is updated.
-     * @retval OT_ERROR_NOT_FOUND    Could not find the TLV with Type @p aType.
-     * @retval OT_ERROR_PARSE        TLV was found but it was not well-formed and could not be parsed.
+     * @retval kErrorNone         The TLV was found and read successfully. @p aValue is updated.
+     * @retval kErrorNotFound     Could not find the TLV with Type @p aType.
+     * @retval kErrorParse        TLV was found but it was not well-formed and could not be parsed.
      *
      */
     template <typename UintTlvType>
-    static otError Find(const Message &aMessage, typename UintTlvType::UintValueType &aValue)
+    static Error Find(const Message &aMessage, typename UintTlvType::UintValueType &aValue)
     {
         return FindUintTlv(aMessage, UintTlvType::kType, aValue);
     }
@@ -386,11 +383,11 @@
      * @param[in]  aValue        A buffer containing the TLV value.
      * @param[in]  aLength       The value length (in bytes).
      *
-     * @retval OT_ERROR_NONE     Successfully appended the TLV to the message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient available buffers to grow the message.
+     * @retval kErrorNone     Successfully appended the TLV to the message.
+     * @retval kErrorNoBufs   Insufficient available buffers to grow the message.
      *
      */
-    template <typename TlvType> static otError Append(Message &aMessage, const void *aValue, uint8_t aLength)
+    template <typename TlvType> static Error Append(Message &aMessage, const void *aValue, uint8_t aLength)
     {
         return AppendTlv(aMessage, TlvType::kType, aValue, aLength);
     }
@@ -405,12 +402,12 @@
      * @param[in]  aMessage      A reference to the message to append to.
      * @param[in]  aValue        A reference to the object containing TLV's value.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the TLV to the message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient available buffers to grow the message.
+     * @retval kErrorNone     Successfully appended the TLV to the message.
+     * @retval kErrorNoBufs   Insufficient available buffers to grow the message.
      *
      */
     template <typename SimpleTlvType>
-    static otError Append(Message &aMessage, const typename SimpleTlvType::ValueType &aValue)
+    static Error Append(Message &aMessage, const typename SimpleTlvType::ValueType &aValue)
     {
         return AppendTlv(aMessage, SimpleTlvType::kType, &aValue, sizeof(aValue));
     }
@@ -425,11 +422,11 @@
      * @param[in]  aMessage      A reference to the message to append to.
      * @param[in]  aValue        An unsigned int value to use as TLV's value.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the TLV to the message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient available buffers to grow the message.
+     * @retval kErrorNone     Successfully appended the TLV to the message.
+     * @retval kErrorNoBufs   Insufficient available buffers to grow the message.
      *
      */
-    template <typename UintTlvType> static otError Append(Message &aMessage, typename UintTlvType::UintValueType aValue)
+    template <typename UintTlvType> static Error Append(Message &aMessage, typename UintTlvType::UintValueType aValue)
     {
         return AppendUintTlv(aMessage, UintTlvType::kType, aValue);
     }
@@ -454,22 +451,17 @@
      * @param[out]  aSize          A pointer to a variable to output the size (total number of bytes) of the TLV.
      * @param[out]  aIsExtendedTlv A pointer to a boolean variable to output whether the found TLV is extended or not.
      *
-     * @retval OT_ERROR_NONE       Successfully found the TLV.
-     * @retval OT_ERROR_NOT_FOUND  Could not find the TLV with Type @p aType.
+     * @retval kErrorNone       Successfully found the TLV.
+     * @retval kErrorNotFound   Could not find the TLV with Type @p aType.
      *
      */
-    static otError Find(const Message &aMessage,
-                        uint8_t        aType,
-                        uint16_t *     aOffset,
-                        uint16_t *     aSize,
-                        bool *         aIsExtendedTlv);
+    static Error Find(const Message &aMessage, uint8_t aType, uint16_t *aOffset, uint16_t *aSize, bool *aIsExtendedTlv);
 
-    static otError FindTlv(const Message &aMessage, uint8_t aType, void *aValue, uint8_t aLength);
-    static otError AppendTlv(Message &aMessage, uint8_t aType, const void *aValue, uint8_t aLength);
-    template <typename UintType>
-    static otError ReadUintTlv(const Message &aMessage, uint16_t aOffset, UintType &aValue);
-    template <typename UintType> static otError FindUintTlv(const Message &aMessage, uint8_t aType, UintType &aValue);
-    template <typename UintType> static otError AppendUintTlv(Message &aMessage, uint8_t aType, UintType aValue);
+    static Error FindTlv(const Message &aMessage, uint8_t aType, void *aValue, uint8_t aLength);
+    static Error AppendTlv(Message &aMessage, uint8_t aType, const void *aValue, uint8_t aLength);
+    template <typename UintType> static Error ReadUintTlv(const Message &aMessage, uint16_t aOffset, UintType &aValue);
+    template <typename UintType> static Error FindUintTlv(const Message &aMessage, uint8_t aType, UintType &aValue);
+    template <typename UintType> static Error AppendUintTlv(Message &aMessage, uint8_t aType, UintType aValue);
 
     uint8_t mType;
     uint8_t mLength;
diff --git a/src/core/common/trickle_timer.cpp b/src/core/common/trickle_timer.cpp
index b8f4655..bfa3aec 100644
--- a/src/core/common/trickle_timer.cpp
+++ b/src/core/common/trickle_timer.cpp
@@ -39,40 +39,28 @@
 
 namespace ot {
 
-TrickleTimer::TrickleTimer(Instance &aInstance,
-#ifdef ENABLE_TRICKLE_TIMER_SUPPRESSION_SUPPORT
-                           uint32_t aRedundancyConstant,
-#endif
-                           Handler aTransmitHandler,
-                           Handler aIntervalExpiredHandler,
-                           void *  aOwner)
-    : TimerMilli(aInstance, TrickleTimer::HandleTimer, aOwner)
-#ifdef ENABLE_TRICKLE_TIMER_SUPPRESSION_SUPPORT
-    , mRedundancyConstant(aRedundancyConstant)
-    , mCounter(0)
-#endif
+TrickleTimer::TrickleTimer(Instance &aInstance, Handler aHandler)
+    : TimerMilli(aInstance, TrickleTimer::HandleTimer)
     , mIntervalMin(0)
     , mIntervalMax(0)
     , mInterval(0)
     , mTimeInInterval(0)
-    , mTransmitHandler(aTransmitHandler)
-    , mIntervalExpiredHandler(aIntervalExpiredHandler)
-    , mMode(kModeNormal)
-    , mIsRunning(false)
-    , mInTransmitPhase(false)
+    , mRedundancyConstant(0)
+    , mCounter(0)
+    , mHandler(aHandler)
+    , mMode(kModeTrickle)
+    , mPhase(kBeforeRandomTime)
 {
-    OT_ASSERT(aTransmitHandler != nullptr);
 }
 
-void TrickleTimer::Start(uint32_t aIntervalMin, uint32_t aIntervalMax, Mode aMode)
+void TrickleTimer::Start(Mode aMode, uint32_t aIntervalMin, uint32_t aIntervalMax, uint16_t aRedundancyConstant)
 {
-    OT_ASSERT(aIntervalMax >= aIntervalMin);
-    OT_ASSERT(aIntervalMin != 0 || aIntervalMax != 0);
+    OT_ASSERT((aIntervalMax >= aIntervalMin) && (aIntervalMin > 0));
 
-    mIntervalMin = aIntervalMin;
-    mIntervalMax = aIntervalMax;
-    mMode        = aMode;
-    mIsRunning   = true;
+    mIntervalMin        = aIntervalMin;
+    mIntervalMax        = aIntervalMax;
+    mRedundancyConstant = aRedundancyConstant;
+    mMode               = aMode;
 
     // Select interval randomly from range [Imin, Imax].
     mInterval = Random::NonCrypto::GetUint32InRange(mIntervalMin, mIntervalMax + 1);
@@ -80,17 +68,22 @@
     StartNewInterval();
 }
 
-void TrickleTimer::Stop(void)
+void TrickleTimer::IndicateConsistent(void)
 {
-    mIsRunning = false;
-    TimerMilli::Stop();
+    if (mCounter < kInfiniteRedundancyConstant)
+    {
+        mCounter++;
+    }
 }
 
 void TrickleTimer::IndicateInconsistent(void)
 {
+    VerifyOrExit(mMode == kModeTrickle);
+
     // If interval is equal to minimum when an "inconsistent" event
     // is received, do nothing.
-    VerifyOrExit(mIsRunning && (mInterval != mIntervalMin));
+
+    VerifyOrExit(IsRunning() && (mInterval != mIntervalMin));
 
     mInterval = mIntervalMin;
     StartNewInterval();
@@ -103,33 +96,22 @@
 {
     uint32_t halfInterval;
 
-#ifdef ENABLE_TRICKLE_TIMER_SUPPRESSION_SUPPORT
-    mCounter = 0;
-#endif
-
-    mInTransmitPhase = true;
-
     switch (mMode)
     {
-    case kModeNormal:
-        halfInterval = mInterval / 2;
-        VerifyOrExit(halfInterval < mInterval, mTimeInInterval = halfInterval);
-
-        // Select a random point in the interval taken from the range [I/2, I).
-        mTimeInInterval = Random::NonCrypto::GetUint32InRange(halfInterval, mInterval);
-        break;
-
     case kModePlainTimer:
         mTimeInInterval = mInterval;
         break;
 
-    case kModeMPL:
-        // Select a random point in interval taken from the range [0, I].
-        mTimeInInterval = Random::NonCrypto::GetUint32InRange(0, mInterval + 1);
+    case kModeTrickle:
+        // Select a random point in the interval taken from the range [I/2, I).
+        halfInterval = mInterval / 2;
+        mTimeInInterval =
+            (halfInterval < mInterval) ? Random::NonCrypto::GetUint32InRange(halfInterval, mInterval) : halfInterval;
+        mCounter = 0;
+        mPhase   = kBeforeRandomTime;
         break;
     }
 
-exit:
     TimerMilli::Start(mTimeInInterval);
 }
 
@@ -140,71 +122,52 @@
 
 void TrickleTimer::HandleTimer(void)
 {
-    if (mInTransmitPhase)
-    {
-        HandleEndOfTimeInInterval();
-    }
-    else
-    {
-        HandleEndOfInterval();
-    }
-}
-
-void TrickleTimer::HandleEndOfTimeInInterval(void)
-{
-#ifdef ENABLE_TRICKLE_TIMER_SUPPRESSION_SUPPORT
-    // Trickle transmits if and only if the counter `c` is less
-    // than the redundancy constant `k`.
-    if (mRedundancyConstant == 0 || mCounter < mRedundancyConstant)
-#endif
-    {
-        bool shouldContinue = mTransmitHandler(*this);
-        VerifyOrExit(shouldContinue, Stop());
-    }
-
     switch (mMode)
     {
     case kModePlainTimer:
-        // Select a random interval in [Imin, Imax] and restart.
         mInterval = Random::NonCrypto::GetUint32InRange(mIntervalMin, mIntervalMax + 1);
         StartNewInterval();
         break;
 
-    case kModeNormal:
-    case kModeMPL:
-        // Waiting for the rest of the interval to elapse.
-        mInTransmitPhase = false;
-        TimerMilli::Start(mInterval - mTimeInInterval);
+    case kModeTrickle:
+        switch (mPhase)
+        {
+        case kBeforeRandomTime:
+            // We reached end of random `mTimeInInterval` (aka `t`)
+            // within the current interval. Trickle timer invokes
+            // handler if and only if the counter is less than the
+            // redundancy constant.
+
+            mPhase = kAfterRandomTime;
+            TimerMilli::Start(mInterval - mTimeInInterval);
+            VerifyOrExit(mCounter < mRedundancyConstant);
+            break;
+
+        case kAfterRandomTime:
+            // Interval has expired. Double the interval length and
+            // ensure result is below max.
+
+            if (mInterval == 0)
+            {
+                mInterval = 1;
+            }
+            else if (mInterval <= mIntervalMax - mInterval)
+            {
+                mInterval *= 2;
+            }
+            else
+            {
+                mInterval = mIntervalMax;
+            }
+
+            StartNewInterval();
+            ExitNow(); // Exit so to not call `mHanlder`
+        }
+
         break;
     }
 
-exit:
-    return;
-}
-
-void TrickleTimer::HandleEndOfInterval(void)
-{
-    // Double the interval and ensure result is below max.
-    if (mInterval == 0)
-    {
-        mInterval = 1;
-    }
-    else if (mInterval <= mIntervalMax - mInterval)
-    {
-        mInterval *= 2;
-    }
-    else
-    {
-        mInterval = mIntervalMax;
-    }
-
-    if (mIntervalExpiredHandler)
-    {
-        bool shouldContinue = mIntervalExpiredHandler(*this);
-        VerifyOrExit(shouldContinue, Stop());
-    }
-
-    StartNewInterval();
+    mHandler(*this);
 
 exit:
     return;
diff --git a/src/core/common/trickle_timer.hpp b/src/core/common/trickle_timer.hpp
index e8ed1d0..0f65c55 100644
--- a/src/core/common/trickle_timer.hpp
+++ b/src/core/common/trickle_timer.hpp
@@ -36,6 +36,7 @@
 
 #include "openthread-core-config.h"
 
+#include "common/numeric_limits.hpp"
 #include "common/timer.hpp"
 
 namespace ot {
@@ -61,41 +62,38 @@
      * This enumeration defines the modes of operation for the `TrickleTimer`.
      *
      */
-    enum Mode
+    enum Mode : uint8_t
     {
-        kModeNormal,     ///< Runs the normal trickle logic (as per RFC6206).
-        kModePlainTimer, ///< Runs a plain timer with random interval selected between min/max intervals.
-        kModeMPL,        ///< Runs the trickle logic modified for MPL.
+        kModeTrickle,    ///< Operate as the normal trickle logic (as per RFC 6206).
+        kModePlainTimer, ///< Operate as a plain periodic timer with random interval selected within min/max intervals.
+    };
+
+    enum : uint16_t
+    {
+        /**
+         * Special value for redundancy constant (aka `k`) to indicate infinity (when used, it disables trickle timer's
+         * suppression behavior, invoking the handler callback independent of number of "consistent" events).
+         *
+         */
+        kInfiniteRedundancyConstant = NumericLimits<uint16_t>::Max(),
     };
 
     /**
-     * This function pointer is called when the timer expires.
+     * This function pointer is called when the timer expires (i.e., transmission should happen).
      *
      * @param[in]  aTimer  A reference to the trickle timer.
      *
-     * @retval TRUE   If the trickle timer should continue running.
-     * @retval FALSE  If the trickle timer should stop running.
-     *
      */
-    typedef bool (*Handler)(TrickleTimer &aTimer);
+    typedef void (&Handler)(TrickleTimer &aTimer);
 
     /**
      * This constructor initializes a `TrickleTimer` instance.
      *
-     * @param[in]  aInstance                A reference to the OpenThread instance.
-     * @param[in]  aRedundancyConstant      The redundancy constant for the timer, also known as `k`.
-     * @param[in]  aTransmitHandler         A pointer to a function that is called when transmission should occur.
-     * @param[in]  aIntervalExpiredHandler  An optional pointer to a function that is called when the interval expires.
-     * @param[in]  aOwner                   A pointer to owner of the `TrickleTimer` object.
+     * @param[in]  aInstance   A reference to the OpenThread instance.
+     * @param[in]  aHandler    A handler which is called when transmission should occur.
      *
      */
-    TrickleTimer(Instance &aInstance,
-#ifdef ENABLE_TRICKLE_TIMER_SUPPRESSION_SUPPORT
-                 uint32_t aRedundancyConstant,
-#endif
-                 Handler aTransmitHandler,
-                 Handler aIntervalExpiredHandler,
-                 void *  aOwner);
+    TrickleTimer(Instance &aInstance, Handler aHandler);
 
     /**
      * This method indicates whether or not the trickle timer instance is running.
@@ -104,60 +102,85 @@
      * @retval FALSE  If the trickle timer is not running.
      *
      */
-    bool IsRunning(void) const { return mIsRunning; }
+    bool IsRunning(void) const { return TimerMilli::IsRunning(); }
+
+    /**
+     * This method gets the current operation mode of the trickle timer.
+     *
+     * @returns The current operation mode of the timer.
+     *
+     */
+    Mode GetMode(void) const { return mMode; }
 
     /**
      * This method starts the trickle timer.
      *
-     * @param[in]  aIntervalMin  The minimum interval for the timer in milliseconds.
-     * @param[in]  aIntervalMax  The maximum interval for the timer in milliseconds.
-     * @param[in]  aMode         The operating mode for the timer.
+     * @param[in]  aMode                The operation mode of timer (trickle or plain periodic mode).
+     * @param[in]  aIntervalMin         The minimum interval for the timer in milliseconds.
+     * @param[in]  aIntervalMax         The maximum interval for the timer in milliseconds.
+     * @param[in]  aRedundancyConstant  The redundancy constant for the timer, also known as `k`. The default value
+     *                                  is set to `kInfiniteRedundancyConstant` which disables the suppression behavior
+     *                                  (i.e., handler is always invoked independent of number of "consistent" events).
      *
      */
-    void Start(uint32_t aIntervalMin, uint32_t aIntervalMax, Mode aMode);
+    void Start(Mode     aMode,
+               uint32_t aIntervalMin,
+               uint32_t aIntervalMax,
+               uint16_t aRedundancyConstant = kInfiniteRedundancyConstant);
 
     /**
      * This method stops the trickle timer.
      *
      */
-    void Stop(void);
+    void Stop(void) { TimerMilli::Stop(); }
 
-#ifdef ENABLE_TRICKLE_TIMER_SUPPRESSION_SUPPORT
     /**
-     * This method indicates to the trickle timer a 'consistent' state.
+     * This method indicates to the trickle timer a 'consistent' event.
+     *
+     * The 'consistent' events are used to control suppression behavior. The trickle timer keeps track of the number of
+     * 'consistent' events in each interval. The timer handler is invoked only if the number of `consistent` events
+     * received in the interval is less than the redundancy constant.
      *
      */
-    void IndicateConsistent(void) { mCounter++; }
-#endif
+    void IndicateConsistent(void);
 
     /**
-     * This method indicates to the trickle timer an 'inconsistent' state.
+     * This method indicates to the trickle timer an 'inconsistent' event.
+     *
+     * Receiving an 'inconsistent' event causes the trickle timer to reset (i.e., start with interval set to the min
+     * value) unless the current interval being used is already equal to the min interval.
      *
      */
     void IndicateInconsistent(void);
 
 private:
+    enum Phase : uint8_t
+    {
+        kBeforeRandomTime, // Trickle timer is before random time `t` in the current interval.
+        kAfterRandomTime,  // Trickle timer is after random time `t` in the current interval.
+    };
+
     void        StartNewInterval(void);
     static void HandleTimer(Timer &aTimer);
     void        HandleTimer(void);
     void        HandleEndOfTimeInInterval(void);
     void        HandleEndOfInterval(void);
-    void        StartAt(void) {} // Shadow base class `TimerMilli` method to ensure it is hidden.
 
-#ifdef ENABLE_TRICKLE_TIMER_SUPPRESSION_SUPPORT
-    const uint32_t mRedundancyConstant; // Redundancy constant (aka 'k').
-    uint32_t       mCounter;            // A counter for number of "consistent" transmissions (aka 'c').
-#endif
+    // Shadow base class `TimerMilli` methods to ensure they are hidden.
+    void StartAt(void) {}
+    void FireAt(void) {}
+    void FireAtIfEarlier(void) {}
+    void GetFireTime(void) {}
 
-    uint32_t mIntervalMin;            // Minimum interval (aka `Imin`).
-    uint32_t mIntervalMax;            // Maximum interval (aka `Imax`).
-    uint32_t mInterval;               // Current interval (aka `I`).
-    uint32_t mTimeInInterval;         // Time in interval (aka `t`).
-    Handler  mTransmitHandler;        // Transmit handler callback.
-    Handler  mIntervalExpiredHandler; // Interval expired handler callback.
-    Mode     mMode;                   // Trickle timer mode.
-    bool     mIsRunning : 1;          // Indicates if the trickle timer is running.
-    bool     mInTransmitPhase : 1;    // Indicates if in transmit phase (before time `t` in current interval `I`).
+    uint32_t mIntervalMin;        // Minimum interval (aka `Imin`).
+    uint32_t mIntervalMax;        // Maximum interval (aka `Imax`).
+    uint32_t mInterval;           // Current interval (aka `I`).
+    uint32_t mTimeInInterval;     // Time in interval (aka `t`).
+    uint16_t mRedundancyConstant; // Redundancy constant (aka 'k').
+    uint16_t mCounter;            // A counter for number of "consistent" transmissions (aka 'c').
+    Handler  mHandler;            // Handler callback.
+    Mode     mMode;               // Trickle timer operation mode.
+    Phase    mPhase;              // Trickle timer phase (before or after time `t` in the current interval).
 };
 
 /**
diff --git a/src/core/config/border_router.h b/src/core/config/border_router.h
index 2284367..bdddb0e 100644
--- a/src/core/config/border_router.h
+++ b/src/core/config/border_router.h
@@ -55,4 +55,14 @@
 #define OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE 0
 #endif
 
+/**
+ * @def OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+ *
+ * Define to 1 to enable (Duckhorn) Border Routing support.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+#define OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE 0
+#endif
+
 #endif // CONFIG_BORDER_ROUTER_H_
diff --git a/src/core/config/coap.h b/src/core/config/coap.h
index 712c57e..c3ee709 100644
--- a/src/core/config/coap.h
+++ b/src/core/config/coap.h
@@ -68,6 +68,26 @@
 #endif
 
 /**
+ * @def OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+ *
+ * Define to 1 to enable the CoAP Block-Wise Transfer.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE
+#define OPENTHREAD_CONFIG_COAP_BLOCKWISE_TRANSFER_ENABLE 0
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_COAP_MAX_BLOCK_LENGTH
+ *
+ * This setting configures the maximum length of one block during a CoAP Block-Wise Transfer.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_COAP_MAX_BLOCK_LENGTH
+#define OPENTHREAD_CONFIG_COAP_MAX_BLOCK_LENGTH 1024
+#endif
+
+/**
  * @def OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE
  *
  * Define to 1 to enable the CoAP Secure API.
diff --git a/src/core/config/dataset_updater.h b/src/core/config/dataset_updater.h
index 1cff726..4055748 100644
--- a/src/core/config/dataset_updater.h
+++ b/src/core/config/dataset_updater.h
@@ -52,7 +52,7 @@
  *
  */
 #ifndef OPENTHREAD_CONFIG_DATASET_UPDATER_DEFAULT_DELAY
-#define OPENTHREAD_CONFIG_DATASET_UPDATER_DEFAULT_DELAY 1000
+#define OPENTHREAD_CONFIG_DATASET_UPDATER_DEFAULT_DELAY 30000
 #endif
 
 /**
diff --git a/src/core/config/dns_client.h b/src/core/config/dns_client.h
index 914bc84..e7c4d08 100644
--- a/src/core/config/dns_client.h
+++ b/src/core/config/dns_client.h
@@ -46,23 +46,68 @@
 #endif
 
 /**
- * @def OPENTHREAD_CONFIG_DNS_RESPONSE_TIMEOUT
+ * @def OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
  *
- * Maximum time that DNS Client waits for response in milliseconds.
+ * Define to 1 to enable DNS based Service Discovery (DNS-SD) client.
  *
  */
-#ifndef OPENTHREAD_CONFIG_DNS_RESPONSE_TIMEOUT
-#define OPENTHREAD_CONFIG_DNS_RESPONSE_TIMEOUT 3000
+#ifndef OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+#define OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE 1
 #endif
 
 /**
- * @def OPENTHREAD_CONFIG_DNS_MAX_RETRANSMIT
+ * @def OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_IP6_ADDRESS
  *
- * Maximum number of retransmissions for DNS client.
+ * Specifies the default DNS server IPv6 address.
+ *
+ * It MUST be a C string representation of the server IPv6 address.
+ *
+ * Default value is set to "2001:4860:4860::8888" which is the Google Public DNS IPv6 address.
  *
  */
-#ifndef OPENTHREAD_CONFIG_DNS_MAX_RETRANSMIT
-#define OPENTHREAD_CONFIG_DNS_MAX_RETRANSMIT 2
+#ifndef OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_IP6_ADDRESS
+#define OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_IP6_ADDRESS "2001:4860:4860::8888"
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_PORT
+ *
+ * Specifies the default DNS server port number.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_PORT
+#define OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_PORT 53
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_RESPONSE_TIMEOUT
+ *
+ * Specifies the default wait time that DNS client waits for a response from server (in milliseconds).
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_RESPONSE_TIMEOUT
+#define OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_RESPONSE_TIMEOUT 6000
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_MAX_TX_ATTEMPTS
+ *
+ * Specifies the default maximum number of DNS query tx attempts with no response before reporting failure.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_MAX_TX_ATTEMPTS
+#define OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_MAX_TX_ATTEMPTS 3
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_NO_RECURSION_FLAG
+ *
+ * Specifies the default "recursion desired" flag (indicates whether the server can resolve the query recursively or
+ * not).
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_RECURSION_DESIRED_FLAG
+#define OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_RECURSION_DESIRED_FLAG 1
 #endif
 
 #endif // CONFIG_DNS_CLIENT_H_
diff --git a/src/core/config/dnssd_server.h b/src/core/config/dnssd_server.h
new file mode 100644
index 0000000..1d8c215
--- /dev/null
+++ b/src/core/config/dnssd_server.h
@@ -0,0 +1,58 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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
+ *   This file includes compile-time configurations for the DNS-SD Server.
+ *
+ */
+
+#ifndef CONFIG_DNSSD_SERVER_H_
+#define CONFIG_DNSSD_SERVER_H_
+
+/**
+ * @def OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
+ *
+ * Define to 1 to enable DNS-SD Server support.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
+#define OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE 0
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_DNSSD_SERVER_PORT
+ *
+ * Define the the DNS-SD Server port.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_DNSSD_SERVER_PORT
+#define OPENTHREAD_CONFIG_DNSSD_SERVER_PORT 53
+#endif
+
+#endif // CONFIG_DNSSD_SERVER_H_
diff --git a/src/core/config/dtls.h b/src/core/config/dtls.h
new file mode 100644
index 0000000..e27918c
--- /dev/null
+++ b/src/core/config/dtls.h
@@ -0,0 +1,60 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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
+ *   This file includes compile-time configurations for DTLS.
+ *
+ */
+
+#ifndef CONFIG_DTLS_H_
+#define CONFIG_DTLS_H_
+
+#include "config/border_router.h"
+#include "config/coap.h"
+#include "config/commissioner.h"
+#include "config/joiner.h"
+
+/**
+ * @def OPENTHREAD_CONFIG_DTLS_MAX_CONTENT_LEN
+ *
+ * The max length of the OpenThread dtls content buffer.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_DTLS_MAX_CONTENT_LEN
+#define OPENTHREAD_CONFIG_DTLS_MAX_CONTENT_LEN MBEDTLS_SSL_MAX_CONTENT_LEN
+#endif
+
+#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE || OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE || \
+    OPENTHREAD_CONFIG_COMMISSIONER_ENABLE || OPENTHREAD_CONFIG_JOINER_ENABLE
+#define OPENTHREAD_CONFIG_DTLS_ENABLE 1
+#else
+#define OPENTHREAD_CONFIG_DTLS_ENABLE 0
+#endif
+
+#endif // CONFIG_DTLS_H_
diff --git a/src/core/config/logging.h b/src/core/config/logging.h
index f8e3b0a..8649d37 100644
--- a/src/core/config/logging.h
+++ b/src/core/config/logging.h
@@ -220,7 +220,7 @@
  *
  */
 #ifndef OPENTHREAD_CONFIG_LOG_PLATFORM
-#define OPENTHREAD_CONFIG_LOG_PLATFORM 0
+#define OPENTHREAD_CONFIG_LOG_PLATFORM 1
 #endif
 
 /**
@@ -300,6 +300,36 @@
 #endif
 
 /**
+ * @def OPENTHREAD_CONFIG_LOG_BR
+ *
+ * Define to Border Router (BR) region logging.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_LOG_BR
+#define OPENTHREAD_CONFIG_LOG_BR 1
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_LOG_SRP
+ *
+ * Define to enable Service Registration Protocol (SRP) region logging.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_LOG_SRP
+#define OPENTHREAD_CONFIG_LOG_SRP 1
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_LOG_DNS
+ *
+ * Define to enable DNS region logging.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_LOG_DNS
+#define OPENTHREAD_CONFIG_LOG_DNS 1
+#endif
+
+/**
  * @def OPENTHREAD_CONFIG_LOG_PREPEND_LEVEL
  *
  * Define to prepend the log level to all log messages.
diff --git a/src/core/config/mac.h b/src/core/config/mac.h
index 7ab7b87..f733f88 100644
--- a/src/core/config/mac.h
+++ b/src/core/config/mac.h
@@ -66,11 +66,11 @@
  *
  * The default maximum number of retries allowed after a transmission failure for direct transmissions.
  *
- * Equivalent to macMaxFrameRetries, default value is 3.
+ * Equivalent to macMaxFrameRetries, default value is 15.
  *
  */
 #ifndef OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_DIRECT
-#define OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_DIRECT 3
+#define OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_DIRECT 15
 #endif
 
 /**
@@ -407,4 +407,14 @@
 #define OPENTHREAD_CONFIG_CSL_RECEIVE_TIME_AHEAD 2
 #endif
 
+/**
+ * @def OPENTHREAD_CONFIG_MAC_SCAN_DURATION
+ *
+ * This setting configures the default scan duration in milliseconds.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_MAC_SCAN_DURATION
+#define OPENTHREAD_CONFIG_MAC_SCAN_DURATION 300
+#endif
+
 #endif // CONFIG_MAC_H_
diff --git a/src/core/config/mle.h b/src/core/config/mle.h
index 5c2ee78..e6131bd 100644
--- a/src/core/config/mle.h
+++ b/src/core/config/mle.h
@@ -164,6 +164,26 @@
 #endif
 
 /**
+ * @def OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_DELAY_TO_RESET_BACKOFF_INTERVAL
+ *
+ * Specifies the delay wait interval (in milliseconds) used by attach backoff feature after a successful attach before
+ * it resets the current backoff interval back to the minimum value.
+ *
+ * If it is set to zero then the device resets its backoff attach interval immediately after a successful attach. With
+ * a non-zero value, if after a successful attach, the device happens to detach within the delay interval, the reattach
+ *  process resumes with the previous backoff interval (as if the attach did not happen).
+ *
+ * This behavior is helpful in the situation where a battery-powered device has poor link quality to its parent and
+ * therefore attaches and detaches frequently from the parent.  Using a non-zero wait interval ensures that the attach
+ * backoff interval does not reset on each attach and that the device does not drain its battery quickly trying to
+ * re-attach too frequently.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_DELAY_TO_RESET_BACKOFF_INTERVAL
+#define OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_DELAY_TO_RESET_BACKOFF_INTERVAL 20000
+#endif
+
+/**
  * @def OPENTHREAD_CONFIG_MLE_SEND_LINK_REQUEST_ON_ADV_TIMEOUT
  *
  * Define to 1 to send an MLE Link Request when MAX_NEIGHBOR_AGE is reached for a neighboring router.
diff --git a/src/core/config/openthread-core-config-check.h b/src/core/config/openthread-core-config-check.h
index 0b82531..6ee4eb9 100644
--- a/src/core/config/openthread-core-config-check.h
+++ b/src/core/config/openthread-core-config-check.h
@@ -28,32 +28,7 @@
 
 /**
  * @file
- *   Sanity checking for configuration options.
- */
-
-#ifndef OPENTHREAD_CORE_CONFIG_CHECK_H_
-#define OPENTHREAD_CORE_CONFIG_CHECK_H_
-
-#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE && OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE
-#error "OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE and OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE must not both be set."
-#endif
-
-#if OPENTHREAD_ENABLE_DHCP6_MULTICAST_SOLICIT && !OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE
-#error "OPENTHREAD_ENABLE_DHCP6_MULTICAST_SOLICIT requires OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE to be also set."
-#endif
-
-#if OPENTHREAD_ENABLE_DHCP6_MULTICAST_SOLICIT && OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE
-#error "OPENTHREAD_ENABLE_DHCP6_MULTICAST_SOLICIT requires DHCPv6 server on Border Router side to be enabled."
-#endif
-
-#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
-#if OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE
-#error "Dynamic log level is not supported along with multiple OT instance feature"
-#endif
-#endif
-
-/*
- * Removed or replaced OPENTHREAD_CONFIG options.
+ *   Checking for configuration options. Removed or replaced OPENTHREAD_CONFIG options.
  *
  * The checks below verify that none of the older configuration definition are
  * still defined and being used. The list below is not necessarily complete and
@@ -63,6 +38,9 @@
  *
  */
 
+#ifndef OPENTHREAD_CORE_CONFIG_CHECK_H_
+#define OPENTHREAD_CORE_CONFIG_CHECK_H_
+
 #if defined(OPENTHREAD_CONFIG_DISABLE_CCA_ON_LAST_ATTEMPT) ||     \
     defined(OPENTHREAD_CONFIG_DISABLE_CSMA_CA_ON_LAST_ATTEMPT) || \
     defined(OPENTHREAD_CONFIG_MAC_DISABLE_CSMA_CA_ON_LAST_ATTEMPT)
@@ -91,14 +69,16 @@
 #error "OPENTHREAD_ENABLE_CERT_LOG was replaced by OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE."
 #endif
 
-//---------------------------------------------------------------------------------------------------------------------
-
 #ifdef OPENTHREAD_ENABLE_MULTIPLE_INSTANCES
 #error "OPENTHREAD_ENABLE_MULTIPLE_INSTANCES was replaced by OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE."
 #endif
 
+#ifdef OPENTHREAD_CONFIG_NCP_UART_ENABLE
+#error "OPENTHREAD_CONFIG_NCP_UART_ENABLE was replaced by OPENTHREAD_CONFIG_NCP_HDLC_ENABLE."
+#endif
+
 #ifdef OPENTHREAD_ENABLE_NCP_UART
-#error "OPENTHREAD_ENABLE_NCP_UART was replaced by OPENTHREAD_CONFIG_NCP_UART_ENABLE."
+#error "OPENTHREAD_ENABLE_NCP_UART was replaced by OPENTHREAD_CONFIG_NCP_HDLC_ENABLE."
 #endif
 
 #ifdef OPENTHREAD_ENABLE_NCP_SPI
@@ -217,8 +197,6 @@
 #error "OPENTHREAD_ENABLE_SPINEL_VENDOR_SUPPORT was replaced by OPENTHREAD_ENABLE_NCP_VENDOR_HOOK."
 #endif
 
-//---------------------------------------------------------------------------------------------------------------------
-
 #ifdef OPENTHREAD_CONFIG_MAX_TX_ATTEMPTS_INDIRECT_POLLS
 #error \
     "OPENTHREAD_CONFIG_MAX_TX_ATTEMPTS_INDIRECT_POLLS was replaced by OPENTHREAD_CONFIG_MAC_MAX_TX_ATTEMPTS_INDIRECT_POLLS."
@@ -496,49 +474,6 @@
     "OPENTHREAD_CONFIG_COAP_ACK_TIMEOUT_MILLIS was removed. Use otCoapSendRequestWithParameters to configure CoAP transmission parameters."
 #endif
 
-#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
-#if (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2)
-#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE"
-#endif
-
-#if !OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
-#error "OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE is required for OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE"
-#endif
-
-#endif // OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
-
-#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
-#if (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2)
-#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE"
-#endif
-#endif // OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
-
-#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
-#if (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2)
-#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE"
-#endif
-#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
-
-#if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
-#if !OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
-#error "OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE is required for OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE"
-#endif
-#endif
-
-#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
-#if (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2)
-#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE"
-#endif
-#endif
-
-#if OPENTHREAD_CONFIG_DUA_ENABLE && (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2)
-#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_DUA_ENABLE"
-#endif
-
-#if OPENTHREAD_CONFIG_MLR_ENABLE && (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2)
-#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_MLR_ENABLE"
-#endif
-
 #ifdef OPENTHREAD_CONFIG_LOG_OUTPUT_NCP_SPINEL
 #error "OPENTHREAD_CONFIG_LOG_OUTPUT_NCP_SPINEL is removed, use OPENTHREAD_CONFIG_LOG_OUTPUT_APP instead"
 #endif
@@ -548,8 +483,12 @@
        "(and OPENTHREAD_CONFIG_LOG_DEFINE_AS_MACRO_ONLY)"
 #endif
 
-#if OPENTHREAD_CONFIG_MESSAGE_USE_HEAP_ENABLE && OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT
-#error "OPENTHREAD_CONFIG_MESSAGE_USE_HEAP_ENABLE conflicts with OPENTHREAD_CONFIG_PLATFORM_MESSAGE_MANAGEMENT."
+#ifdef OPENTHREAD_CONFIG_DNS_RESPONSE_TIMEOUT
+#error "OPENTHREAD_CONFIG_DNS_RESPONSE_TIMEOUT was replaced by OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_RESPONSE_TIMEOUT"
+#endif
+
+#ifdef OPENTHREAD_CONFIG_DNS_MAX_RETRANSMIT
+#error "OPENTHREAD_CONFIG_DNS_MAX_RETRANSMIT was replaced by OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_MAX_TX_ATTEMPTS"
 #endif
 
 #endif // OPENTHREAD_CORE_CONFIG_CHECK_H_
diff --git a/src/core/config/openthread-core-default-config.h b/src/core/config/openthread-core-default-config.h
index a2076b3..5697d72 100644
--- a/src/core/config/openthread-core-default-config.h
+++ b/src/core/config/openthread-core-default-config.h
@@ -36,6 +36,7 @@
 #define OPENTHREAD_CORE_DEFAULT_CONFIG_H_
 
 #include "config/coap.h"
+#include "config/srp_server.h"
 
 /**
  * @def OPENTHREAD_CONFIG_STACK_VENDOR_OUI
@@ -265,7 +266,10 @@
  *
  */
 #ifndef OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE
-#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+// Internal heap doesn't support size larger than 64K bytes.
+#define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE (63 * 1024)
+#elif OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE
 #define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE (3072 * sizeof(void *))
 #else
 #define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE (1568 * sizeof(void *))
@@ -279,7 +283,10 @@
  *
  */
 #ifndef OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS
-#if OPENTHREAD_CONFIG_ECDSA_ENABLE
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+// Internal heap doesn't support size larger than 64K bytes.
+#define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS (63 * 1024)
+#elif OPENTHREAD_CONFIG_ECDSA_ENABLE
 #define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS 2600
 #else
 #define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS 384
diff --git a/src/core/config/ping_sender.h b/src/core/config/ping_sender.h
new file mode 100644
index 0000000..18aba57
--- /dev/null
+++ b/src/core/config/ping_sender.h
@@ -0,0 +1,81 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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
+ *   This file includes compile-time configurations for ping sender module.
+ *
+ */
+
+#ifndef CONFIG_PING_SENDER_H_
+#define CONFIG_PING_SENDER_H_
+
+/**
+ * @def OPENTHREAD_CONFIG_PING_SENDER_ENABLE
+ *
+ * Define to 1 to enable ping sender module.
+ *
+ * Ping sender module implements sending ICMPv6 Echo Request messages and processing ICMPv6 Echo Reply messages.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_PING_SENDER_ENABLE
+#define OPENTHREAD_CONFIG_PING_SENDER_ENABLE 0
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_INTEVRAL
+ *
+ * Specifies the default ping interval (time between sending echo requests) in milliseconds.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_INTEVRAL
+#define OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_INTEVRAL 1000
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_SIZE
+ *
+ * Specifies the default ping data size in bytes. The data size specifies the Echo Request data payload which excludes
+ * the IPv6 and ICMPv6 headers.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_SIZE
+#define OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_SIZE 8
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_COUNT
+ *
+ * Specifies the default ping count (number of ping messages to send).
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_COUNT
+#define OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_COUNT 1
+#endif
+
+#endif // CONFIG_PING_SENDER_H_
diff --git a/src/core/config/srp_client.h b/src/core/config/srp_client.h
new file mode 100644
index 0000000..e251151
--- /dev/null
+++ b/src/core/config/srp_client.h
@@ -0,0 +1,221 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file includes compile-time configurations for the SRP (Service Registration Protocol) Client.
+ *
+ */
+
+#ifndef CONFIG_SRP_CLIENT_H_
+#define CONFIG_SRP_CLIENT_H_
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+ *
+ * Define to 1 to enable SRP Client support.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+#define OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE 0
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+ *
+ * Define to 1 to enable SRP Client auto-start feature and its APIs.
+ *
+ * When enabled, the SRP client can be configured to automatically start when it detects the presence of an SRP server
+ *  (by monitoring the Thread Network Data for SRP Server Service entries).
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+#define OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE 1
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE
+ *
+ * Define the default mode (enabled or disabled) of auto-start mode.
+ *
+ * This config is applicable/used only when `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE` is enabled.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE
+#define OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE 0
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE
+ *
+ * Define to 1 for the SRP client implementation to provide APIs that get/set the domain name.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE
+#define OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE 0
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_CLIENT_DEFAULT_LEASE
+ *
+ * Specifies the default requested lease interval (in seconds). Set to two hours.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_DEFAULT_LEASE
+#define OPENTHREAD_CONFIG_SRP_CLIENT_DEFAULT_LEASE (2 * 60 * 60)
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_CLIENT_DEFAULT_KEY_LEASE
+ *
+ * Specifies the default requested key lease interval (in seconds). Set to 14 days.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_DEFAULT_KEY_LEASE
+#define OPENTHREAD_CONFIG_SRP_CLIENT_DEFAULT_KEY_LEASE (14 * 24 * 60 * 60)
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_CLIENT_LEASE_RENEW_GUARD_INTERVAL
+ *
+ * Specifies the guard interval (in seconds) for lease renew time. The guard interval determines how much earlier
+ * (relative to the lease expiration time) the SRP client will send an SRP update for lease renewal.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_LEASE_RENEW_GUARD_INTERVAL
+#define OPENTHREAD_CONFIG_SRP_CLIENT_LEASE_RENEW_GUARD_INTERVAL 120 // two minutes in seconds
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_CLIENT_EARLY_LEASE_RENEW_FACTOR_NUMERATOR
+ *
+ * Specifies the numerator of early lease renewal factor.
+ *
+ * This value is used for opportunistic early refresh behave. When sending an SRP update, the services that are not yet
+ * expired but are close, are allowed to refresh early and are included in the SRP update.
+ *
+ * The "early lease renewal interval" is used to determine if a service can renew early. The interval is calculated by
+ * multiplying the accepted lease interval by the "early lease renewal factor" which is given as a fraction (numerator
+ * and denominator).
+ *
+ * If the factor is set to zero (numerator=0, denominator=1), the opportunistic early refresh behavior is disabled.
+ * If  denominator is set to zero (the factor is set to infinity), then all services (including previously registered
+ * ones) are always included in SRP update message.
+ *
+ * Default value is 1/2 (i.e., services that are within half of the lease interval are allowed to refresh early).
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_EARLY_LEASE_RENEW_FACTOR_NUMERATOR
+#define OPENTHREAD_CONFIG_SRP_CLIENT_EARLY_LEASE_RENEW_FACTOR_NUMERATOR 1
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_CLIENT_EARLY_LEASE_RENEW_FACTOR_DENOMINATOR
+ *
+ * Specifies the denominator of early lease renewal factor.
+ *
+ * Please see OPENTHREAD_CONFIG_SRP_CLIENT_EARLY_LEASE_RENEW_FACTOR_NUMERATOR for more details.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_EARLY_LEASE_RENEW_FACTOR_DENOMINATOR
+#define OPENTHREAD_CONFIG_SRP_CLIENT_EARLY_LEASE_RENEW_FACTOR_DENOMINATOR 2
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_DELAY
+ *
+ * Specifies the (short) delay (in msec) after an update is required before SRP client sends the update message.
+ *
+ * When there is a change (e.g., a new service is added/removed) that requires an update, the SRP client will wait for
+ * a short delay before preparing and sending an SRP update message to server. This allows user to provide more change
+ * that are then all sent in same update message. The delay is only applied on the first change that triggers an
+ * update message transmission. Subsequent changes (API calls) while waiting for the tx to start will not reset the
+ * delay timer.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_DELAY
+#define OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_DELAY 10
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_CLIENT_MIN_RETRY_WAIT_INTERVAL
+ *
+ * Specifies the minimum wait interval (in msec) between SRP update message retries.
+ *
+ * The update message is retransmitted if there is no response from server or if server rejects the update. The wait
+ * interval starts from the minimum value and is increased by the growth factor every failure up to the max value.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_MIN_RETRY_WAIT_INTERVAL
+#define OPENTHREAD_CONFIG_SRP_CLIENT_MIN_RETRY_WAIT_INTERVAL 1800
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_CLIENT_MAX_RETRY_WAIT_INTERVAL
+ *
+ * Specifies the maximum wait interval (in msec) between SRP update message retries.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_MAX_RETRY_WAIT_INTERVAL
+#define OPENTHREAD_CONFIG_SRP_CLIENT_MAX_RETRY_WAIT_INTERVAL (1 * 60 * 60 * 1000) // 1 hour in ms.
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_CLIENT_RETRY_WAIT_INTERVAL_JITTER
+ *
+ * Specifies jitter (in msec) for retry wait interval. If the current retry wait interval is smaller than the jitter
+ * then the the wait interval itself is used as jitter (e.g., with jitter 500 msec and if retry interval is 300ms
+ * the retry interval is then randomly selected from [0, 2*300] ms).
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_RETRY_WAIT_INTERVAL_JITTER
+#define OPENTHREAD_CONFIG_SRP_CLIENT_RETRY_WAIT_INTERVAL_JITTER 500
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_CLIENT_RETRY_INTERVAL_GROWTH_FACTOR_NUMERATOR
+ *
+ * Specifies the numerator of the retry wait interval growth factor fraction. The growth factor is represented as
+ * a fraction (e.g., for 1.5, we can use 15 as the numerator and 10 as the denominator).
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_RETRY_INTERVAL_GROWTH_FACTOR_NUMERATOR
+#define OPENTHREAD_CONFIG_SRP_CLIENT_RETRY_INTERVAL_GROWTH_FACTOR_NUMERATOR 17
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_CLIENT_RETRY_INTERVAL_GROWTH_FACTOR_DENOMINATOR
+ *
+ * Specifies the denominator of the retry wait interval growth factor fraction.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_RETRY_INTERVAL_GROWTH_FACTOR_DENOMINATOR
+#define OPENTHREAD_CONFIG_SRP_CLIENT_RETRY_INTERVAL_GROWTH_FACTOR_DENOMINATOR 10
+#endif
+
+#endif // CONFIG_SRP_CLIENT_H_
diff --git a/src/core/config/srp_server.h b/src/core/config/srp_server.h
new file mode 100644
index 0000000..c0d9e30
--- /dev/null
+++ b/src/core/config/srp_server.h
@@ -0,0 +1,93 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file includes compile-time configurations for the SRP (Service Registration Protocol) Server.
+ *
+ */
+
+#ifndef CONFIG_SRP_SERVER_H_
+#define CONFIG_SRP_SERVER_H_
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+ *
+ * Define to 1 to enable SRP Server support.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+#define OPENTHREAD_CONFIG_SRP_SERVER_ENABLE 0
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_SERVER_UDP_PORT
+ *
+ * Specifies the SRP Server UDP port.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_SERVER_UDP_PORT
+#define OPENTHREAD_CONFIG_SRP_SERVER_UDP_PORT 0
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_NUMBER
+ *
+ * Specifies the Thread Network Data Service number for SRP Server.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_NUMBER
+#define OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_NUMBER 0x5du
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_UPDATE_TIMEOUT
+ *
+ * Specifies the timeout value (in milliseconds) for the service update handler.
+ *
+ * The default timeout value is the sum of the maximum total mDNS probing delays
+ * and a loose IPC timeout of 250ms. It is recommended that this configuration should
+ * not use a value smaller than the default value here, if an Advertising Proxy is used
+ * to handle the service update events.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_UPDATE_TIMEOUT
+#define OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_UPDATE_TIMEOUT ((4 * 250u) + 250u)
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_SERVER_MAX_ADDRESSES_NUM
+ *
+ * Specifies the maximum number of addresses the SRP server can handle for a host.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_SRP_SERVER_MAX_ADDRESSES_NUM
+#define OPENTHREAD_CONFIG_SRP_SERVER_MAX_ADDRESSES_NUM 2
+#endif
+
+#endif // CONFIG_SRP_SERVER_H_
diff --git a/src/core/crypto/aes_ccm.hpp b/src/core/crypto/aes_ccm.hpp
index 9efae0e..67744b3 100644
--- a/src/core/crypto/aes_ccm.hpp
+++ b/src/core/crypto/aes_ccm.hpp
@@ -38,8 +38,7 @@
 
 #include <stdint.h>
 
-#include <openthread/error.h>
-
+#include "common/error.hpp"
 #include "crypto/aes_ecb.hpp"
 #include "mac/mac_types.hpp"
 
diff --git a/src/core/crypto/ecdsa.cpp b/src/core/crypto/ecdsa.cpp
index e46e6c2..216dfd3 100644
--- a/src/core/crypto/ecdsa.cpp
+++ b/src/core/crypto/ecdsa.cpp
@@ -50,7 +50,7 @@
 
 #if OPENTHREAD_CONFIG_ECDSA_ENABLE
 
-otError P256::KeyPair::Generate(void)
+Error P256::KeyPair::Generate(void)
 {
     mbedtls_pk_context pk;
     int                ret;
@@ -74,26 +74,26 @@
 exit:
     mbedtls_pk_free(&pk);
 
-    return (ret >= 0) ? OT_ERROR_NONE : MbedTls::MapError(ret);
+    return (ret >= 0) ? kErrorNone : MbedTls::MapError(ret);
 }
 
-otError P256::KeyPair::Parse(void *aContext) const
+Error P256::KeyPair::Parse(void *aContext) const
 {
-    otError             error = OT_ERROR_NONE;
+    Error               error = kErrorNone;
     mbedtls_pk_context *pk    = reinterpret_cast<mbedtls_pk_context *>(aContext);
 
     mbedtls_pk_init(pk);
 
-    VerifyOrExit(mbedtls_pk_setup(pk, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)) == 0, error = OT_ERROR_FAILED);
-    VerifyOrExit(mbedtls_pk_parse_key(pk, mDerBytes, mDerLength, nullptr, 0) == 0, error = OT_ERROR_PARSE);
+    VerifyOrExit(mbedtls_pk_setup(pk, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY)) == 0, error = kErrorFailed);
+    VerifyOrExit(mbedtls_pk_parse_key(pk, mDerBytes, mDerLength, nullptr, 0) == 0, error = kErrorParse);
 
 exit:
     return error;
 }
 
-otError P256::KeyPair::GetPublicKey(PublicKey &aPublicKey) const
+Error P256::KeyPair::GetPublicKey(PublicKey &aPublicKey) const
 {
-    otError              error;
+    Error                error;
     mbedtls_pk_context   pk;
     mbedtls_ecp_keypair *keyPair;
     int                  ret;
@@ -113,9 +113,9 @@
     return error;
 }
 
-otError P256::KeyPair::Sign(const Sha256::Hash &aHash, Signature &aSignature) const
+Error P256::KeyPair::Sign(const Sha256::Hash &aHash, Signature &aSignature) const
 {
-    otError               error;
+    Error                 error;
     mbedtls_pk_context    pk;
     mbedtls_ecp_keypair * keypair;
     mbedtls_ecdsa_context ecdsa;
@@ -138,7 +138,7 @@
         mbedtls_ecdsa_sign_det(&ecdsa.grp, &r, &s, &ecdsa.d, aHash.GetBytes(), Sha256::Hash::kSize, MBEDTLS_MD_SHA256);
     VerifyOrExit(ret == 0, error = MbedTls::MapError(ret));
 
-    OT_ASSERT(mbedtls_mpi_size(&r) == kMpiSize);
+    OT_ASSERT(mbedtls_mpi_size(&r) <= kMpiSize);
 
     ret = mbedtls_mpi_write_binary(&r, aSignature.mShared.mMpis.mR, kMpiSize);
     VerifyOrExit(ret == 0, error = MbedTls::MapError(ret));
@@ -155,9 +155,9 @@
     return error;
 }
 
-otError P256::PublicKey::Verify(const Sha256::Hash &aHash, const Signature &aSignature) const
+Error P256::PublicKey::Verify(const Sha256::Hash &aHash, const Signature &aSignature) const
 {
-    otError               error = OT_ERROR_NONE;
+    Error                 error = kErrorNone;
     mbedtls_ecdsa_context ecdsa;
     mbedtls_mpi           r;
     mbedtls_mpi           s;
@@ -184,7 +184,7 @@
     VerifyOrExit(ret == 0, error = MbedTls::MapError(ret));
 
     ret = mbedtls_ecdsa_verify(&ecdsa.grp, aHash.GetBytes(), Sha256::Hash::kSize, &ecdsa.Q, &r, &s);
-    VerifyOrExit(ret == 0, error = OT_ERROR_SECURITY);
+    VerifyOrExit(ret == 0, error = kErrorSecurity);
 
 exit:
     mbedtls_mpi_free(&s);
@@ -194,14 +194,14 @@
     return error;
 }
 
-otError Sign(uint8_t *      aOutput,
-             uint16_t &     aOutputLength,
-             const uint8_t *aInputHash,
-             uint16_t       aInputHashLength,
-             const uint8_t *aPrivateKey,
-             uint16_t       aPrivateKeyLength)
+Error Sign(uint8_t *      aOutput,
+           uint16_t &     aOutputLength,
+           const uint8_t *aInputHash,
+           uint16_t       aInputHashLength,
+           const uint8_t *aPrivateKey,
+           uint16_t       aPrivateKeyLength)
 {
-    otError               error = OT_ERROR_NONE;
+    Error                 error = kErrorNone;
     mbedtls_ecdsa_context ctx;
     mbedtls_pk_context    pkCtx;
     mbedtls_ecp_keypair * keypair;
@@ -215,26 +215,26 @@
 
     // Parse a private key in PEM format.
     VerifyOrExit(mbedtls_pk_parse_key(&pkCtx, aPrivateKey, aPrivateKeyLength, nullptr, 0) == 0,
-                 error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit(mbedtls_pk_get_type(&pkCtx) == MBEDTLS_PK_ECKEY, error = OT_ERROR_INVALID_ARGS);
+                 error = kErrorInvalidArgs);
+    VerifyOrExit(mbedtls_pk_get_type(&pkCtx) == MBEDTLS_PK_ECKEY, error = kErrorInvalidArgs);
 
     keypair = mbedtls_pk_ec(pkCtx);
     OT_ASSERT(keypair != nullptr);
 
-    VerifyOrExit(mbedtls_ecdsa_from_keypair(&ctx, keypair) == 0, error = OT_ERROR_FAILED);
+    VerifyOrExit(mbedtls_ecdsa_from_keypair(&ctx, keypair) == 0, error = kErrorFailed);
 
     // Sign using ECDSA.
     VerifyOrExit(mbedtls_ecdsa_sign(&ctx.grp, &rMpi, &sMpi, &ctx.d, aInputHash, aInputHashLength,
                                     mbedtls_ctr_drbg_random, Random::Crypto::MbedTlsContextGet()) == 0,
-                 error = OT_ERROR_FAILED);
-    VerifyOrExit(mbedtls_mpi_size(&rMpi) + mbedtls_mpi_size(&sMpi) <= aOutputLength, error = OT_ERROR_NO_BUFS);
+                 error = kErrorFailed);
+    VerifyOrExit(mbedtls_mpi_size(&rMpi) + mbedtls_mpi_size(&sMpi) <= aOutputLength, error = kErrorNoBufs);
 
     // Concatenate the two octet sequences in the order R and then S.
-    VerifyOrExit(mbedtls_mpi_write_binary(&rMpi, aOutput, mbedtls_mpi_size(&rMpi)) == 0, error = OT_ERROR_FAILED);
+    VerifyOrExit(mbedtls_mpi_write_binary(&rMpi, aOutput, mbedtls_mpi_size(&rMpi)) == 0, error = kErrorFailed);
     aOutputLength = static_cast<uint16_t>(mbedtls_mpi_size(&rMpi));
 
     VerifyOrExit(mbedtls_mpi_write_binary(&sMpi, aOutput + aOutputLength, mbedtls_mpi_size(&sMpi)) == 0,
-                 error = OT_ERROR_FAILED);
+                 error = kErrorFailed);
     aOutputLength += mbedtls_mpi_size(&sMpi);
 
 exit:
diff --git a/src/core/crypto/ecdsa.hpp b/src/core/crypto/ecdsa.hpp
index 772e489..8cf3265 100644
--- a/src/core/crypto/ecdsa.hpp
+++ b/src/core/crypto/ecdsa.hpp
@@ -39,8 +39,7 @@
 #include <stdint.h>
 #include <stdlib.h>
 
-#include <openthread/error.h>
-
+#include "common/error.hpp"
 #include "crypto/sha256.hpp"
 
 namespace ot {
@@ -146,24 +145,24 @@
         /**
          * This method generates and populates the `KeyPair` with a new public/private keys.
          *
-         * @retval OT_ERROR_NONE         A new key pair was generated successfully.
-         * @retval OT_ERROR_NO_BUFS      Failed to allocate buffer for key generation.
-         * @retval OT_ERROR_NOT_CAPABLE  Feature not supported.
-         * @retval OT_ERROR_FAILED       Failed to generate key.
+         * @retval kErrorNone         A new key pair was generated successfully.
+         * @retval kErrorNoBufs       Failed to allocate buffer for key generation.
+         * @retval kErrorNotCapable   Feature not supported.
+         * @retval kErrorFailed       Failed to generate key.
          *
          */
-        otError Generate(void);
+        Error Generate(void);
 
         /**
          * This method gets the associated public key from the `KeyPair`.
          *
          * @param[out] aPublicKey     A reference to a `PublicKey` to output the value.
          *
-         * @retval OT_ERROR_NONE      Public key was retrieved successfully, and @p aPublicKey is updated.
-         * @retval OT_ERROR_PARSE     The key-pair DER format could not be parsed (invalid format).
+         * @retval kErrorNone      Public key was retrieved successfully, and @p aPublicKey is updated.
+         * @retval kErrorParse     The key-pair DER format could not be parsed (invalid format).
          *
          */
-        otError GetPublicKey(PublicKey &aPublicKey) const;
+        Error GetPublicKey(PublicKey &aPublicKey) const;
 
         /**
          * This method gets the pointer to start of the buffer containing the key-pair info in DER format.
@@ -211,16 +210,16 @@
          * @param[in]  aHash               The SHA-256 hash value of the message to use for signature calculation.
          * @param[out] aSignature          A reference to a `Signature` to output the calculated signature value.
          *
-         * @retval OT_ERROR_NONE           The signature was calculated successfully and @p aSignature was updated.
-         * @retval OT_ERROR_PARSE          The key-pair DER format could not be parsed (invalid format).
-         * @retval OT_ERROR_INVALID_ARGS   The @p aHash is invalid.
-         * @retval OT_ERROR_NO_BUFS        Failed to allocate buffer for signature calculation.
+         * @retval kErrorNone           The signature was calculated successfully and @p aSignature was updated.
+         * @retval kErrorParse          The key-pair DER format could not be parsed (invalid format).
+         * @retval kErrorInvalidArgs    The @p aHash is invalid.
+         * @retval kErrorNoBufs         Failed to allocate buffer for signature calculation.
          *
          */
-        otError Sign(const Sha256::Hash &aHash, Signature &aSignature) const;
+        Error Sign(const Sha256::Hash &aHash, Signature &aSignature) const;
 
     private:
-        otError Parse(void *aContext) const;
+        Error Parse(void *aContext) const;
 
         uint8_t mDerBytes[kMaxDerSize];
         uint8_t mDerLength;
@@ -257,13 +256,13 @@
          * @param[in] aHash                The SHA-256 hash value of a message to use for signature verification.
          * @param[in] aSignature           The signature value to verify.
          *
-         * @retval OT_ERROR_NONE           The signature was verified successfully.
-         * @retval OT_ERROR_SECURITY       The signature is invalid.
-         * @retval OT_ERROR_INVALID_ARGS   The key or has is invalid.
-         * @retval OT_ERROR_NO_BUFS        Failed to allocate buffer for signature verification
+         * @retval kErrorNone          The signature was verified successfully.
+         * @retval kErrorSecurity      The signature is invalid.
+         * @retval kErrorInvalidArgs   The key or has is invalid.
+         * @retval kErrorNoBufs        Failed to allocate buffer for signature verification
          *
          */
-        otError Verify(const Sha256::Hash &aHash, const Signature &aSignature) const;
+        Error Verify(const Sha256::Hash &aHash, const Signature &aSignature) const;
 
     private:
         uint8_t mData[kSize];
@@ -280,18 +279,18 @@
  * @param[in]     aPrivateKey        A private key in PEM format.
  * @param[in]     aPrivateKeyLength  The length of the @p aPrivateKey buffer.
  *
- * @retval  OT_ERROR_NONE         ECDSA sign has been created successfully.
- * @retval  OT_ERROR_NO_BUFS      Output buffer is too small.
- * @retval  OT_ERROR_INVALID_ARGS Private key is not valid EC Private Key.
- * @retval  OT_ERROR_FAILED       Error during signing.
+ * @retval  kErrorNone         ECDSA sign has been created successfully.
+ * @retval  kErrorNoBufs       Output buffer is too small.
+ * @retval  kErrorInvalidArgs  Private key is not valid EC Private Key.
+ * @retval  kErrorFailed       Error during signing.
  *
  */
-otError Sign(uint8_t *      aOutput,
-             uint16_t &     aOutputLength,
-             const uint8_t *aInputHash,
-             uint16_t       aInputHashLength,
-             const uint8_t *aPrivateKey,
-             uint16_t       aPrivateKeyLength);
+Error Sign(uint8_t *      aOutput,
+           uint16_t &     aOutputLength,
+           const uint8_t *aInputHash,
+           uint16_t       aInputHashLength,
+           const uint8_t *aPrivateKey,
+           uint16_t       aPrivateKeyLength);
 
 /**
  * @}
diff --git a/src/core/crypto/hkdf_sha256.cpp b/src/core/crypto/hkdf_sha256.cpp
index fd10bef..dad7b0f 100644
--- a/src/core/crypto/hkdf_sha256.cpp
+++ b/src/core/crypto/hkdf_sha256.cpp
@@ -75,13 +75,13 @@
 
         if (iter != 0)
         {
-            hmac.Update(hash.GetBytes(), sizeof(hash));
+            hmac.Update(hash);
         }
 
         hmac.Update(aInfo, aInfoLength);
 
         iter++;
-        hmac.Update(&iter, sizeof(iter));
+        hmac.Update(iter);
         hmac.Finish(hash);
 
         copyLength = (aOutputKeyLength > sizeof(hash)) ? sizeof(hash) : aOutputKeyLength;
diff --git a/src/core/crypto/hmac_sha256.cpp b/src/core/crypto/hmac_sha256.cpp
index e88a6c0..add3e3a 100644
--- a/src/core/crypto/hmac_sha256.cpp
+++ b/src/core/crypto/hmac_sha256.cpp
@@ -33,6 +33,8 @@
 
 #include "hmac_sha256.hpp"
 
+#include "common/message.hpp"
+
 namespace ot {
 namespace Crypto {
 
@@ -54,9 +56,22 @@
     mbedtls_md_hmac_starts(&mContext, aKey, aKeyLength);
 }
 
-void HmacSha256::Update(const uint8_t *aBuf, uint16_t aBufLength)
+void HmacSha256::Update(const void *aBuf, uint16_t aBufLength)
 {
-    mbedtls_md_hmac_update(&mContext, aBuf, aBufLength);
+    mbedtls_md_hmac_update(&mContext, reinterpret_cast<const uint8_t *>(aBuf), aBufLength);
+}
+
+void HmacSha256::Update(const Message &aMessage, uint16_t aOffset, uint16_t aLength)
+{
+    Message::Chunk chunk;
+
+    aMessage.GetFirstChunk(aOffset, aLength, chunk);
+
+    while (chunk.GetLength() > 0)
+    {
+        Update(chunk.GetData(), chunk.GetLength());
+        aMessage.GetNextChunk(aLength, chunk);
+    }
 }
 
 void HmacSha256::Finish(Hash &aHash)
diff --git a/src/core/crypto/hmac_sha256.hpp b/src/core/crypto/hmac_sha256.hpp
index 2d0d8d9..5fcd2a0 100644
--- a/src/core/crypto/hmac_sha256.hpp
+++ b/src/core/crypto/hmac_sha256.hpp
@@ -43,6 +43,9 @@
 #include "crypto/sha256.hpp"
 
 namespace ot {
+
+class Message;
+
 namespace Crypto {
 
 /**
@@ -66,19 +69,19 @@
     typedef Sha256::Hash Hash;
 
     /**
-     * Constructor for initialization of mbedtls_md_context_t.
+     * Constructor for `HmacSha256`.
      *
      */
     HmacSha256(void);
 
     /**
-     * Destructor for freeing of mbedtls_md_context_t.
+     * Destructor for `HmacSha256`.
      *
      */
     ~HmacSha256(void);
 
     /**
-     * This method sets the key.
+     * This method sets the key and starts the HMAC computation.
      *
      * @param[in]  aKey        A pointer to the key.
      * @param[in]  aKeyLength  The key length in bytes.
@@ -93,7 +96,31 @@
      * @param[in]  aBufLength  The length of @p aBuf in bytes.
      *
      */
-    void Update(const uint8_t *aBuf, uint16_t aBufLength);
+    void Update(const void *aBuf, uint16_t aBufLength);
+
+    /**
+     * This method inputs an object (treated as a sequence of bytes) into the HMAC computation.
+     *
+     * @tparam    ObjectType   The object type.
+     *
+     * @param[in] aObject      A reference to the object.
+     *
+     */
+    template <typename ObjectType> void Update(const ObjectType &aObject)
+    {
+        static_assert(!TypeTraits::IsPointer<ObjectType>::kValue, "ObjectType must not be a pointer");
+        return Update(&aObject, sizeof(ObjectType));
+    }
+
+    /**
+     * This method inputs the bytes read from a given message into the HMAC computation.
+     *
+     * @param[in] aMessage    The message to read the data from.
+     * @param[in] aOffset     The offset into @p aMessage to start to read.
+     * @param[in] aLength     The number of bytes to read.
+     *
+     */
+    void Update(const Message &aMessage, uint16_t aOffset, uint16_t aLength);
 
     /**
      * This method finalizes the hash computation.
diff --git a/src/core/crypto/mbedtls.cpp b/src/core/crypto/mbedtls.cpp
index a5ba8c3..4b27b4a 100644
--- a/src/core/crypto/mbedtls.cpp
+++ b/src/core/crypto/mbedtls.cpp
@@ -36,7 +36,6 @@
 #include <mbedtls/ctr_drbg.h>
 #include <mbedtls/debug.h>
 #include <mbedtls/entropy.h>
-#include <mbedtls/error.h>
 #include <mbedtls/platform.h>
 #include <mbedtls/threading.h>
 
@@ -44,39 +43,26 @@
 #include <mbedtls/pem.h>
 #endif
 
+#include "common/error.hpp"
 #include "common/instance.hpp"
 
 namespace ot {
 namespace Crypto {
 
-#if !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE && OPENTHREAD_CONFIG_ENABLE_BUILTIN_MBEDTLS_MANAGEMENT
-
-static void *CAlloc(size_t aCount, size_t aSize)
-{
-    return Instance::Get().HeapCAlloc(aCount, aSize);
-}
-
-static void Free(void *aPointer)
-{
-    Instance::Get().HeapFree(aPointer);
-}
-
-#endif // !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE && OPENTHREAD_CONFIG_ENABLE_BUILTIN_MBEDTLS_MANAGEMENT
-
 MbedTls::MbedTls(void)
 {
-#if !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE && OPENTHREAD_CONFIG_ENABLE_BUILTIN_MBEDTLS_MANAGEMENT
+#if OPENTHREAD_CONFIG_ENABLE_BUILTIN_MBEDTLS_MANAGEMENT
 #ifdef MBEDTLS_DEBUG_C
     // mbedTLS's debug level is almost the same as OpenThread's
     mbedtls_debug_set_threshold(OPENTHREAD_CONFIG_LOG_LEVEL);
 #endif
-    mbedtls_platform_set_calloc_free(CAlloc, Free);
-#endif // !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE && OPENTHREAD_CONFIG_ENABLE_BUILTIN_MBEDTLS_MANAGEMENT
+    mbedtls_platform_set_calloc_free(Instance::HeapCAlloc, Instance::HeapFree);
+#endif // OPENTHREAD_CONFIG_ENABLE_BUILTIN_MBEDTLS_MANAGEMENT
 }
 
-otError MbedTls::MapError(int aMbedTlsError)
+Error MbedTls::MapError(int aMbedTlsError)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     switch (aMbedTlsError)
     {
@@ -116,7 +102,7 @@
     case MBEDTLS_ERR_SSL_BAD_INPUT_DATA:
     case MBEDTLS_ERR_CTR_DRBG_REQUEST_TOO_BIG:
     case MBEDTLS_ERR_CTR_DRBG_INPUT_TOO_BIG:
-        error = OT_ERROR_INVALID_ARGS;
+        error = kErrorInvalidArgs;
         break;
 
 #if OPENTHREAD_CONFIG_ECDSA_ENABLE
@@ -133,7 +119,7 @@
     case MBEDTLS_ERR_SSL_ALLOC_FAILED:
     case MBEDTLS_ERR_SSL_WANT_WRITE:
     case MBEDTLS_ERR_ENTROPY_MAX_SOURCES:
-        error = OT_ERROR_NO_BUFS;
+        error = kErrorNoBufs;
         break;
 
 #ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
@@ -149,29 +135,29 @@
     case MBEDTLS_ERR_SSL_PEER_VERIFY_FAILED:
     case MBEDTLS_ERR_THREADING_BAD_INPUT_DATA:
     case MBEDTLS_ERR_THREADING_MUTEX_ERROR:
-        error = OT_ERROR_SECURITY;
+        error = kErrorSecurity;
         break;
 
 #ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
     case MBEDTLS_ERR_X509_FATAL_ERROR:
-        error = OT_ERROR_FAILED;
+        error = kErrorFailed;
         break;
 #endif
     case MBEDTLS_ERR_SSL_TIMEOUT:
     case MBEDTLS_ERR_SSL_WANT_READ:
-        error = OT_ERROR_BUSY;
+        error = kErrorBusy;
         break;
 
 #if OPENTHREAD_CONFIG_ECDSA_ENABLE
     case MBEDTLS_ERR_ECP_FEATURE_UNAVAILABLE:
-        error = OT_ERROR_NOT_CAPABLE;
+        error = kErrorNotCapable;
         break;
 #endif
 
     default:
         if (aMbedTlsError < 0)
         {
-            error = OT_ERROR_FAILED;
+            error = kErrorFailed;
         }
 
         break;
diff --git a/src/core/crypto/mbedtls.hpp b/src/core/crypto/mbedtls.hpp
index b2df719..569815f 100644
--- a/src/core/crypto/mbedtls.hpp
+++ b/src/core/crypto/mbedtls.hpp
@@ -38,6 +38,7 @@
 
 #include <openthread/instance.h>
 
+#include "common/error.hpp"
 #include "common/non_copyable.hpp"
 
 namespace ot {
@@ -68,10 +69,10 @@
      *
      * @param[in] aMbedTlsError  The mbed TLS error.
      *
-     * @returns The mapped otError.
+     * @returns The mapped Error.
      *
      */
-    static otError MapError(int aMbedTlsError);
+    static Error MapError(int aMbedTlsError);
 };
 
 /**
diff --git a/src/core/crypto/pbkdf2_cmac.cpp b/src/core/crypto/pbkdf2_cmac.cpp
index 1af07f0..988a577 100644
--- a/src/core/crypto/pbkdf2_cmac.cpp
+++ b/src/core/crypto/pbkdf2_cmac.cpp
@@ -31,26 +31,29 @@
  *   This file implements PBKDF2 using AES-CMAC-PRF-128
  */
 
-#include "pbkdf2_cmac.h"
+#include "pbkdf2_cmac.hpp"
 
+#include <mbedtls/cmac.h>
 #include <string.h>
 
 #include "common/debug.hpp"
 
-#include <mbedtls/cmac.h>
+namespace ot {
+namespace Crypto {
+namespace Pbkdf2 {
 
 #if OPENTHREAD_FTD
 
-void otPbkdf2Cmac(const uint8_t *aPassword,
-                  uint16_t       aPasswordLen,
-                  const uint8_t *aSalt,
-                  uint16_t       aSaltLen,
-                  uint32_t       aIterationCounter,
-                  uint16_t       aKeyLen,
-                  uint8_t *      aKey)
+void GenerateKey(const uint8_t *aPassword,
+                 uint16_t       aPasswordLen,
+                 const uint8_t *aSalt,
+                 uint16_t       aSaltLen,
+                 uint32_t       aIterationCounter,
+                 uint16_t       aKeyLen,
+                 uint8_t *      aKey)
 {
     const size_t kBlockSize = MBEDTLS_CIPHER_BLKSIZE_MAX;
-    uint8_t      prfInput[OT_PBKDF2_SALT_MAX_LEN + 4]; // Salt || INT(), for U1 calculation
+    uint8_t      prfInput[kMaxSaltLength + 4]; // Salt || INT(), for U1 calculation
     long         prfOne[kBlockSize / sizeof(long)];
     long         prfTwo[kBlockSize / sizeof(long)];
     long         keyBlock[kBlockSize / sizeof(long)];
@@ -112,3 +115,7 @@
 }
 
 #endif // OPENTHREAD_FTD
+
+} // namespace Pbkdf2
+} // namespace Crypto
+} // namespace ot
diff --git a/src/core/crypto/pbkdf2_cmac.h b/src/core/crypto/pbkdf2_cmac.hpp
similarity index 71%
rename from src/core/crypto/pbkdf2_cmac.h
rename to src/core/crypto/pbkdf2_cmac.hpp
index fffb5ba..7c8559c 100644
--- a/src/core/crypto/pbkdf2_cmac.h
+++ b/src/core/crypto/pbkdf2_cmac.hpp
@@ -29,25 +29,34 @@
 /**
  * @file
  * @brief
- *  This file defines the PBKDF2 using CMAC C APIs.
+ *  This file includes definitions for performing Password-Based Key Derivation Function 2 (PBKDF2) using CMAC.
  */
 
-#ifndef PBKDF2_CMAC_H_
-#define PBKDF2_CMAC_H_
+#ifndef PBKDF2_CMAC_HPP_
+#define PBKDF2_CMAC_HPP_
 
 #include "openthread-core-config.h"
 
-#include <stdbool.h>
 #include <stdint.h>
 
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define OT_PBKDF2_SALT_MAX_LEN 30 // salt prefix (6) + extended panid (8) + network name (16)
+namespace ot {
+namespace Crypto {
+namespace Pbkdf2 {
 
 /**
- * This method perform PKCS#5 PBKDF2 using CMAC (AES-CMAC-PRF-128).
+ * @addtogroup core-security
+ *
+ * @{
+ *
+ */
+
+enum : uint16_t
+{
+    kMaxSaltLength = 30, ///< Max SALT length: salt prefix (6) + extended panid (8) + network name (16)
+};
+
+/**
+ * This function performs PKCS#5 PBKDF2 using CMAC (AES-CMAC-PRF-128).
  *
  * @param[in]     aPassword          Password to use when generating key.
  * @param[in]     aPasswordLen       Length of password.
@@ -58,16 +67,21 @@
  * @param[out]    aKey               A pointer to the generated key.
  *
  */
-void otPbkdf2Cmac(const uint8_t *aPassword,
-                  uint16_t       aPasswordLen,
-                  const uint8_t *aSalt,
-                  uint16_t       aSaltLen,
-                  uint32_t       aIterationCounter,
-                  uint16_t       aKeyLen,
-                  uint8_t *      aKey);
+void GenerateKey(const uint8_t *aPassword,
+                 uint16_t       aPasswordLen,
+                 const uint8_t *aSalt,
+                 uint16_t       aSaltLen,
+                 uint32_t       aIterationCounter,
+                 uint16_t       aKeyLen,
+                 uint8_t *      aKey);
 
-#ifdef __cplusplus
-} // extern "C"
-#endif
+/**
+ * @}
+ *
+ */
 
-#endif // PBKDF2_CMAC_H_
+} // namespace Pbkdf2
+} // namespace Crypto
+} // namespace ot
+
+#endif // PBKDF2_CMAC_HPP_
diff --git a/src/core/crypto/sha256.cpp b/src/core/crypto/sha256.cpp
index 340fe55..9721d29 100644
--- a/src/core/crypto/sha256.cpp
+++ b/src/core/crypto/sha256.cpp
@@ -33,6 +33,8 @@
 
 #include "sha256.hpp"
 
+#include "common/message.hpp"
+
 namespace ot {
 namespace Crypto {
 
@@ -51,9 +53,22 @@
     mbedtls_sha256_starts_ret(&mContext, 0);
 }
 
-void Sha256::Update(const uint8_t *aBuf, uint16_t aBufLength)
+void Sha256::Update(const void *aBuf, uint16_t aBufLength)
 {
-    mbedtls_sha256_update_ret(&mContext, aBuf, aBufLength);
+    mbedtls_sha256_update_ret(&mContext, reinterpret_cast<const uint8_t *>(aBuf), aBufLength);
+}
+
+void Sha256::Update(const Message &aMessage, uint16_t aOffset, uint16_t aLength)
+{
+    Message::Chunk chunk;
+
+    aMessage.GetFirstChunk(aOffset, aLength, chunk);
+
+    while (chunk.GetLength() > 0)
+    {
+        Update(chunk.GetData(), chunk.GetLength());
+        aMessage.GetNextChunk(aLength, chunk);
+    }
 }
 
 void Sha256::Finish(Hash &aHash)
diff --git a/src/core/crypto/sha256.hpp b/src/core/crypto/sha256.hpp
index 326f435..627a5f5 100644
--- a/src/core/crypto/sha256.hpp
+++ b/src/core/crypto/sha256.hpp
@@ -44,8 +44,12 @@
 
 #include "common/clearable.hpp"
 #include "common/equatable.hpp"
+#include "common/type_traits.hpp"
 
 namespace ot {
+
+class Message;
+
 namespace Crypto {
 
 /**
@@ -84,13 +88,13 @@
     };
 
     /**
-     * Constructor for initializing mbedtls_sha256_context.
+     * Constructor for `Sha256` object.
      *
      */
     Sha256(void);
 
     /**
-     * Destructor for freeing mbedtls_sha256_context.
+     * Destructor for `Sha256` object.
      *
      */
     ~Sha256(void);
@@ -108,7 +112,31 @@
      * @param[in]  aBufLength  The length of @p aBuf in bytes.
      *
      */
-    void Update(const uint8_t *aBuf, uint16_t aBufLength);
+    void Update(const void *aBuf, uint16_t aBufLength);
+
+    /**
+     * This method inputs an object (treated as a sequence of bytes) into the SHA-256 computation.
+     *
+     * @tparam    ObjectType   The object type.
+     *
+     * @param[in] aObject      A reference to the object.
+     *
+     */
+    template <typename ObjectType> void Update(const ObjectType &aObject)
+    {
+        static_assert(!TypeTraits::IsPointer<ObjectType>::kValue, "ObjectType must not be a pointer");
+        return Update(&aObject, sizeof(ObjectType));
+    }
+
+    /**
+     * This method inputs the bytes read from a given message into the SHA-256 computation.
+     *
+     * @param[in] aMessage    The message to read the data from.
+     * @param[in] aOffset     The offset into @p aMessage to start to read.
+     * @param[in] aLength     The number of bytes to read.
+     *
+     */
+    void Update(const Message &aMessage, uint16_t aOffset, uint16_t aLength);
 
     /**
      * This method finalizes the hash computation.
diff --git a/src/core/diags/factory_diags.cpp b/src/core/diags/factory_diags.cpp
index 783b845..11c9087 100644
--- a/src/core/diags/factory_diags.cpp
+++ b/src/core/diags/factory_diags.cpp
@@ -58,7 +58,7 @@
     OT_UNUSED_VARIABLE(aOutput);
     OT_UNUSED_VARIABLE(aOutputMaxLen);
 
-    return OT_ERROR_INVALID_COMMAND;
+    return ot::kErrorInvalidCommand;
 }
 
 namespace ot {
@@ -79,15 +79,15 @@
 {
 }
 
-otError Diags::ProcessChannel(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
+Error Diags::ProcessChannel(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
 {
-    otError error = OT_ERROR_NONE;
-    long    value;
+    Error error = kErrorNone;
+    long  value;
 
-    VerifyOrExit(aArgsLength == 1, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aArgsLength == 1, error = kErrorInvalidArgs);
 
     SuccessOrExit(error = ParseLong(aArgs[0], value));
-    VerifyOrExit(value >= Radio::kChannelMin && value <= Radio::kChannelMax, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(value >= Radio::kChannelMin && value <= Radio::kChannelMax, error = kErrorInvalidArgs);
 
     otPlatDiagChannelSet(static_cast<uint8_t>(value));
 
@@ -96,12 +96,12 @@
     return error;
 }
 
-otError Diags::ProcessPower(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
+Error Diags::ProcessPower(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
 {
-    otError error = OT_ERROR_NONE;
-    long    value;
+    Error error = kErrorNone;
+    long  value;
 
-    VerifyOrExit(aArgsLength == 1, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aArgsLength == 1, error = kErrorInvalidArgs);
 
     SuccessOrExit(error = ParseLong(aArgs[0], value));
 
@@ -112,7 +112,7 @@
     return error;
 }
 
-otError Diags::ProcessStart(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
+Error Diags::ProcessStart(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
 {
     OT_UNUSED_VARIABLE(aArgsLength);
     OT_UNUSED_VARIABLE(aArgs);
@@ -121,10 +121,10 @@
 
     otPlatDiagModeSet(true);
 
-    return OT_ERROR_NONE;
+    return kErrorNone;
 }
 
-otError Diags::ProcessStop(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
+Error Diags::ProcessStop(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
 {
     OT_UNUSED_VARIABLE(aArgsLength);
     OT_UNUSED_VARIABLE(aArgs);
@@ -133,7 +133,7 @@
 
     otPlatDiagModeSet(false);
 
-    return OT_ERROR_NONE;
+    return kErrorNone;
 }
 
 extern "C" void otPlatDiagAlarmFired(otInstance *aInstance)
@@ -162,11 +162,11 @@
     mStats.Clear();
 }
 
-otError Diags::ProcessChannel(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
+Error Diags::ProcessChannel(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(otPlatDiagModeGet(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(otPlatDiagModeGet(), error = kErrorInvalidState);
 
     if (aArgsLength == 0)
     {
@@ -177,7 +177,7 @@
         long value;
 
         SuccessOrExit(error = ParseLong(aArgs[0], value));
-        VerifyOrExit(value >= Radio::kChannelMin && value <= Radio::kChannelMax, error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit(value >= Radio::kChannelMin && value <= Radio::kChannelMax, error = kErrorInvalidArgs);
 
         mChannel = static_cast<uint8_t>(value);
         IgnoreError(Get<Radio>().Receive(mChannel));
@@ -191,11 +191,11 @@
     return error;
 }
 
-otError Diags::ProcessPower(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
+Error Diags::ProcessPower(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(otPlatDiagModeGet(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(otPlatDiagModeGet(), error = kErrorInvalidState);
 
     if (aArgsLength == 0)
     {
@@ -219,12 +219,12 @@
     return error;
 }
 
-otError Diags::ProcessRepeat(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
+Error Diags::ProcessRepeat(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(otPlatDiagModeGet(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(aArgsLength > 0, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(otPlatDiagModeGet(), error = kErrorInvalidState);
+    VerifyOrExit(aArgsLength > 0, error = kErrorInvalidArgs);
 
     if (strcmp(aArgs[0], "stop") == 0)
     {
@@ -236,13 +236,13 @@
     {
         long value;
 
-        VerifyOrExit(aArgsLength == 2, error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit(aArgsLength == 2, error = kErrorInvalidArgs);
 
         SuccessOrExit(error = ParseLong(aArgs[0], value));
         mTxPeriod = static_cast<uint32_t>(value);
 
         SuccessOrExit(error = ParseLong(aArgs[1], value));
-        VerifyOrExit(value <= OT_RADIO_FRAME_MAX_SIZE, error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit(value <= OT_RADIO_FRAME_MAX_SIZE, error = kErrorInvalidArgs);
         mTxLen = static_cast<uint8_t>(value);
 
         mRepeatActive = true;
@@ -257,19 +257,19 @@
     return error;
 }
 
-otError Diags::ProcessSend(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
+Error Diags::ProcessSend(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
 {
-    otError error = OT_ERROR_NONE;
-    long    value;
+    Error error = kErrorNone;
+    long  value;
 
-    VerifyOrExit(otPlatDiagModeGet(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(aArgsLength == 2, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(otPlatDiagModeGet(), error = kErrorInvalidState);
+    VerifyOrExit(aArgsLength == 2, error = kErrorInvalidArgs);
 
     SuccessOrExit(error = ParseLong(aArgs[0], value));
     mTxPackets = static_cast<uint32_t>(value);
 
     SuccessOrExit(error = ParseLong(aArgs[1], value));
-    VerifyOrExit(value <= OT_RADIO_FRAME_MAX_SIZE, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(value <= OT_RADIO_FRAME_MAX_SIZE, error = kErrorInvalidArgs);
     mTxLen = static_cast<uint8_t>(value);
 
     snprintf(aOutput, aOutputMaxLen, "sending %#x packet(s), length %#x\r\nstatus 0x%02x\r\n",
@@ -281,14 +281,14 @@
     return error;
 }
 
-otError Diags::ProcessStart(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
+Error Diags::ProcessStart(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
 {
     OT_UNUSED_VARIABLE(aArgsLength);
     OT_UNUSED_VARIABLE(aArgs);
 
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(!Get<ThreadNetif>().IsUp(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(!Get<ThreadNetif>().IsUp(), error = kErrorInvalidState);
 
     otPlatDiagChannelSet(mChannel);
     otPlatDiagTxPowerSet(mTxPower);
@@ -307,11 +307,11 @@
     return error;
 }
 
-otError Diags::ProcessStats(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
+Error Diags::ProcessStats(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(otPlatDiagModeGet(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(otPlatDiagModeGet(), error = kErrorInvalidState);
 
     if ((aArgsLength == 1) && (strcmp(aArgs[0], "clear") == 0))
     {
@@ -320,7 +320,7 @@
     }
     else
     {
-        VerifyOrExit(aArgsLength == 0, error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit(aArgsLength == 0, error = kErrorInvalidArgs);
         snprintf(aOutput, aOutputMaxLen,
                  "received packets: %d\r\nsent packets: %d\r\n"
                  "first received packet: rssi=%d, lqi=%d\r\n"
@@ -335,14 +335,14 @@
     return error;
 }
 
-otError Diags::ProcessStop(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
+Error Diags::ProcessStop(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
 {
     OT_UNUSED_VARIABLE(aArgsLength);
     OT_UNUSED_VARIABLE(aArgs);
 
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(otPlatDiagModeGet(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(otPlatDiagModeGet(), error = kErrorInvalidState);
 
     otPlatAlarmMilliStop(&GetInstance());
     otPlatDiagModeSet(false);
@@ -375,12 +375,12 @@
     IgnoreError(Get<Radio>().Transmit(*static_cast<Mac::TxFrame *>(mTxPacket)));
 }
 
-otError Diags::ProcessRadio(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
+Error Diags::ProcessRadio(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
 {
-    otError error = OT_ERROR_INVALID_ARGS;
+    Error error = kErrorInvalidArgs;
 
-    VerifyOrExit(otPlatDiagModeGet(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(aArgsLength > 0, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(otPlatDiagModeGet(), error = kErrorInvalidState);
+    VerifyOrExit(aArgsLength > 0, error = kErrorInvalidArgs);
 
     if (strcmp(aArgs[0], "sleep") == 0)
     {
@@ -401,7 +401,7 @@
     {
         otRadioState state = Get<Radio>().GetState();
 
-        error = OT_ERROR_NONE;
+        error = kErrorNone;
 
         switch (state)
         {
@@ -454,9 +454,9 @@
     }
 }
 
-void Diags::ReceiveDone(otRadioFrame *aFrame, otError aError)
+void Diags::ReceiveDone(otRadioFrame *aFrame, Error aError)
 {
-    if (aError == OT_ERROR_NONE)
+    if (aError == kErrorNone)
     {
         // for sensitivity test, only record the rssi and lqi for the first and last packet
         if (mStats.mReceivedPackets == 0)
@@ -474,9 +474,9 @@
     otPlatDiagRadioReceived(&GetInstance(), aFrame, aError);
 }
 
-void Diags::TransmitDone(otError aError)
+void Diags::TransmitDone(Error aError)
 {
-    if (aError == OT_ERROR_NONE)
+    if (aError == kErrorNone)
     {
         mStats.mSentPackets++;
 
@@ -499,19 +499,19 @@
 
 #endif // OPENTHREAD_RADIO
 
-void Diags::AppendErrorResult(otError aError, char *aOutput, size_t aOutputMaxLen)
+void Diags::AppendErrorResult(Error aError, char *aOutput, size_t aOutputMaxLen)
 {
-    if (aError != OT_ERROR_NONE)
+    if (aError != kErrorNone)
     {
         snprintf(aOutput, aOutputMaxLen, "failed\r\nstatus %#x\r\n", aError);
     }
 }
 
-otError Diags::ParseLong(char *aString, long &aLong)
+Error Diags::ParseLong(char *aString, long &aLong)
 {
     char *endptr;
     aLong = strtol(aString, &endptr, 0);
-    return (*endptr == '\0') ? OT_ERROR_NONE : OT_ERROR_PARSE;
+    return (*endptr == '\0') ? kErrorNone : kErrorParse;
 }
 
 void Diags::ProcessLine(const char *aString, char *aOutput, size_t aOutputMaxLen)
@@ -522,12 +522,12 @@
         kMaxCommandBuffer = OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE,
     };
 
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     char    buffer[kMaxCommandBuffer];
     char *  aArgsector[kMaxArgs];
     uint8_t argCount = 0;
 
-    VerifyOrExit(StringLength(aString, kMaxCommandBuffer) < kMaxCommandBuffer, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(StringLength(aString, kMaxCommandBuffer) < kMaxCommandBuffer, error = kErrorNoBufs);
 
     strcpy(buffer, aString);
     error = ot::Utils::CmdLineParser::ParseCmd(buffer, argCount, aArgsector, kMaxArgs);
@@ -536,16 +536,16 @@
 
     switch (error)
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         aOutput[0] = '\0'; // In case there is no output.
         IgnoreError(ProcessCmd(argCount, &aArgsector[0], aOutput, aOutputMaxLen));
         break;
 
-    case OT_ERROR_NO_BUFS:
+    case kErrorNoBufs:
         snprintf(aOutput, aOutputMaxLen, "failed: command string too long\r\n");
         break;
 
-    case OT_ERROR_INVALID_ARGS:
+    case kErrorInvalidArgs:
         snprintf(aOutput, aOutputMaxLen, "failed: command string contains too many arguments\r\n");
         break;
 
@@ -555,9 +555,9 @@
     }
 }
 
-otError Diags::ProcessCmd(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
+Error Diags::ProcessCmd(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     // This `rcp` command is for debugging and testing only, building only when NDEBUG is not defined
     // so that it will be excluded from release build.
@@ -594,7 +594,7 @@
 
 exit:
     // Add more platform specific diagnostics features here.
-    if (error == OT_ERROR_INVALID_COMMAND && aArgsLength > 1)
+    if (error == kErrorInvalidCommand && aArgsLength > 1)
     {
         snprintf(aOutput, aOutputMaxLen, "diag feature '%s' is not supported\r\n", aArgs[0]);
     }
diff --git a/src/core/diags/factory_diags.hpp b/src/core/diags/factory_diags.hpp
index 3c3dd41..78f7814 100644
--- a/src/core/diags/factory_diags.hpp
+++ b/src/core/diags/factory_diags.hpp
@@ -40,6 +40,7 @@
 
 #include <openthread/platform/radio.h>
 
+#include "common/error.hpp"
 #include "common/locator.hpp"
 #include "common/non_copyable.hpp"
 
@@ -77,12 +78,12 @@
      * @param[out]  aOutput        The diagnostics execution result.
      * @param[in]   aOutputMaxLen  The output buffer size.
      *
-     * @retval  OT_ERROR_INVALID_ARGS       The command is supported but invalid arguments provided.
-     * @retval  OT_ERROR_NONE               The command is successfully process.
-     * @retval  OT_ERROR_NOT_IMPLEMENTED    The command is not supported.
+     * @retval  kErrorInvalidArgs       The command is supported but invalid arguments provided.
+     * @retval  kErrorNone              The command is successfully process.
+     * @retval  kErrorNotImplemented    The command is not supported.
      *
      */
-    otError ProcessCmd(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
+    Error ProcessCmd(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
 
     /**
      * This method indicates whether or not the factory diagnostics mode is enabled.
@@ -103,28 +104,28 @@
      * The radio driver calls this method to notify OpenThread diagnostics module of a received frame.
      *
      * @param[in]  aFrame  A pointer to the received frame or nullptr if the receive operation failed.
-     * @param[in]  aError  OT_ERROR_NONE when successfully received a frame,
-     *                     OT_ERROR_ABORT when reception was aborted and a frame was not received,
-     *                     OT_ERROR_NO_BUFS when a frame could not be received due to lack of rx buffer space.
+     * @param[in]  aError  kErrorNone when successfully received a frame,
+     *                     kErrorAbort when reception was aborted and a frame was not received,
+     *                     kErrorNoBufs when a frame could not be received due to lack of rx buffer space.
      *
      */
-    void ReceiveDone(otRadioFrame *aFrame, otError aError);
+    void ReceiveDone(otRadioFrame *aFrame, Error aError);
 
     /**
      * The radio driver calls this method to notify OpenThread diagnostics module that the transmission has completed.
      *
-     * @param[in]  aError  OT_ERROR_NONE when the frame was transmitted,
-     *                     OT_ERROR_CHANNEL_ACCESS_FAILURE tx could not take place due to activity on channel,
-     *                     OT_ERROR_ABORT when transmission was aborted for other reasons.
+     * @param[in]  aError  kErrorNone when the frame was transmitted,
+     *                     kErrorChannelAccessFailure tx could not take place due to activity on channel,
+     *                     kErrorAbort when transmission was aborted for other reasons.
      *
      */
-    void TransmitDone(otError aError);
+    void TransmitDone(Error aError);
 
 private:
     struct Command
     {
         const char *mName;
-        otError (Diags::*mCommand)(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
+        Error (Diags::*mCommand)(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
     };
 
     struct Stats
@@ -139,19 +140,19 @@
         uint8_t  mLastLqi;
     };
 
-    otError ProcessChannel(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
-    otError ProcessPower(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
-    otError ProcessRadio(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
-    otError ProcessRepeat(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
-    otError ProcessSend(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
-    otError ProcessStart(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
-    otError ProcessStats(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
-    otError ProcessStop(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
+    Error ProcessChannel(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
+    Error ProcessPower(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
+    Error ProcessRadio(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
+    Error ProcessRepeat(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
+    Error ProcessSend(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
+    Error ProcessStart(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
+    Error ProcessStats(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
+    Error ProcessStop(uint8_t aArgsLength, char *aArgs[], char *aOutput, size_t aOutputMaxLen);
 
     void TransmitPacket(void);
 
-    static void    AppendErrorResult(otError aError, char *aOutput, size_t aOutputMaxLen);
-    static otError ParseLong(char *aString, long &aLong);
+    static void  AppendErrorResult(Error aError, char *aOutput, size_t aOutputMaxLen);
+    static Error ParseLong(char *aString, long &aLong);
 
     static const struct Command sCommands[];
 
diff --git a/src/core/mac/channel_mask.cpp b/src/core/mac/channel_mask.cpp
index 097e0c9..f10b28a 100644
--- a/src/core/mac/channel_mask.cpp
+++ b/src/core/mac/channel_mask.cpp
@@ -44,7 +44,7 @@
     uint8_t num     = 0;
     uint8_t channel = kChannelIteratorFirst;
 
-    while (GetNextChannel(channel) == OT_ERROR_NONE)
+    while (GetNextChannel(channel) == kErrorNone)
     {
         num++;
     }
@@ -52,9 +52,9 @@
     return num;
 }
 
-otError ChannelMask::GetNextChannel(uint8_t &aChannel) const
+Error ChannelMask::GetNextChannel(uint8_t &aChannel) const
 {
-    otError error = OT_ERROR_NOT_FOUND;
+    Error error = kErrorNotFound;
 
     if (aChannel == kChannelIteratorFirst)
     {
@@ -65,7 +65,7 @@
     {
         if (ContainsChannel(aChannel))
         {
-            ExitNow(error = OT_ERROR_NONE);
+            ExitNow(error = kErrorNone);
         }
     }
 
@@ -98,18 +98,18 @@
     InfoString string;
     uint8_t    channel  = kChannelIteratorFirst;
     bool       addComma = false;
-    otError    error;
+    Error      error;
 
     IgnoreError(string.Append("{"));
 
     error = GetNextChannel(channel);
 
-    while (error == OT_ERROR_NONE)
+    while (error == kErrorNone)
     {
         uint8_t rangeStart = channel;
         uint8_t rangeEnd   = channel;
 
-        while ((error = GetNextChannel(channel)) == OT_ERROR_NONE)
+        while ((error = GetNextChannel(channel)) == kErrorNone)
         {
             if (channel != rangeEnd + 1)
             {
diff --git a/src/core/mac/channel_mask.hpp b/src/core/mac/channel_mask.hpp
index b90ad65..e9dbbd4 100644
--- a/src/core/mac/channel_mask.hpp
+++ b/src/core/mac/channel_mask.hpp
@@ -39,6 +39,7 @@
 #include <limits.h>
 #include <openthread/platform/radio.h>
 
+#include "common/equatable.hpp"
 #include "common/string.hpp"
 #include "radio/radio.hpp"
 
@@ -61,7 +62,7 @@
  * It is a wrapper class around a `uint32_t` bit vector representing a set of channels.
  *
  */
-class ChannelMask
+class ChannelMask : public Unequatable<ChannelMask>
 {
 public:
     enum
@@ -201,11 +202,11 @@
      *                               On entry it should contain the previous channel or `kChannelIteratorFirst`.
      *                               On exit it contains the next channel.
      *
-     * @retval  OT_ERROR_NONE        Got the next channel, @p aChannel updated successfully.
-     * @retval  OT_ERROR_NOT_FOUND   No next channel in the channel mask (note: @p aChannel may be changed).
+     * @retval  kErrorNone       Got the next channel, @p aChannel updated successfully.
+     * @retval  kErrorNotFound   No next channel in the channel mask (note: @p aChannel may be changed).
      *
      */
-    otError GetNextChannel(uint8_t &aChannel) const;
+    Error GetNextChannel(uint8_t &aChannel) const;
 
     /**
      * This method randomly chooses a channel from the channel mask.
@@ -226,16 +227,6 @@
     bool operator==(const ChannelMask &aAnother) const { return (mMask == aAnother.mMask); }
 
     /**
-     * This method overloads `!=` operator to indicate whether two masks are different.
-     *
-     * @param[in] aAnother     A reference to another mask to compare with the current one.
-     *
-     * @returns TRUE if the two masks are different, FALSE otherwise.
-     *
-     */
-    bool operator!=(const ChannelMask &aAnother) const { return !(*this == aAnother); }
-
-    /**
      * This method converts the channel mask into a human-readable string.
      *
      * Examples of possible output:
diff --git a/src/core/mac/data_poll_handler.cpp b/src/core/mac/data_poll_handler.cpp
index cc939b6..b407479 100644
--- a/src/core/mac/data_poll_handler.cpp
+++ b/src/core/mac/data_poll_handler.cpp
@@ -47,16 +47,16 @@
 {
 }
 
-inline otError DataPollHandler::Callbacks::PrepareFrameForChild(Mac::TxFrame &aFrame,
-                                                                FrameContext &aContext,
-                                                                Child &       aChild)
+inline Error DataPollHandler::Callbacks::PrepareFrameForChild(Mac::TxFrame &aFrame,
+                                                              FrameContext &aContext,
+                                                              Child &       aChild)
 {
     return Get<IndirectSender>().PrepareFrameForChild(aFrame, aContext, aChild);
 }
 
 inline void DataPollHandler::Callbacks::HandleSentFrameToChild(const Mac::TxFrame &aFrame,
                                                                const FrameContext &aContext,
-                                                               otError             aError,
+                                                               Error               aError,
                                                                Child &             aChild)
 {
     Get<IndirectSender>().HandleSentFrameToChild(aFrame, aContext, aError, aChild);
@@ -183,7 +183,7 @@
     frame = &aTxFrames.GetTxFrame();
 #endif
 
-    VerifyOrExit(mCallbacks.PrepareFrameForChild(*frame, mFrameContext, *mIndirectTxChild) == OT_ERROR_NONE,
+    VerifyOrExit(mCallbacks.PrepareFrameForChild(*frame, mFrameContext, *mIndirectTxChild) == kErrorNone,
                  frame = nullptr);
 
     if (mIndirectTxChild->GetIndirectTxAttempts() > 0)
@@ -210,7 +210,7 @@
     return frame;
 }
 
-void DataPollHandler::HandleSentFrame(const Mac::TxFrame &aFrame, otError aError)
+void DataPollHandler::HandleSentFrame(const Mac::TxFrame &aFrame, Error aError)
 {
     Child *child = mIndirectTxChild;
 
@@ -223,7 +223,7 @@
     ProcessPendingPolls();
 }
 
-void DataPollHandler::HandleSentFrame(const Mac::TxFrame &aFrame, otError aError, Child &aChild)
+void DataPollHandler::HandleSentFrame(const Mac::TxFrame &aFrame, Error aError, Child &aChild)
 {
     if (aChild.IsFramePurgePending())
     {
@@ -236,21 +236,21 @@
 
     switch (aError)
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         aChild.ResetIndirectTxAttempts();
         aChild.SetFrameReplacePending(false);
         break;
 
-    case OT_ERROR_NO_ACK:
+    case kErrorNoAck:
         aChild.IncrementIndirectTxAttempts();
 
         otLogInfoMac("Indirect tx to child %04x failed, attempt %d/%d", aChild.GetRloc16(),
                      aChild.GetIndirectTxAttempts(), kMaxPollTriggeredTxAttempts);
 
-        // Fall through
+        OT_FALL_THROUGH;
 
-    case OT_ERROR_CHANNEL_ACCESS_FAILURE:
-    case OT_ERROR_ABORT:
+    case kErrorChannelAccessFailure:
+    case kErrorAbort:
 
         if (aChild.IsFrameReplacePending())
         {
diff --git a/src/core/mac/data_poll_handler.hpp b/src/core/mac/data_poll_handler.hpp
index 6d62853..1e962b0 100644
--- a/src/core/mac/data_poll_handler.hpp
+++ b/src/core/mac/data_poll_handler.hpp
@@ -90,7 +90,7 @@
     class ChildInfo
     {
         friend class DataPollHandler;
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
         friend class CslTxScheduler;
 #endif
 
@@ -171,27 +171,27 @@
          * @param[out] aContext  A reference to a `FrameContext` where the context for the new frame would be placed.
          * @param[in]  aChild    The child for which to prepare the frame.
          *
-         * @retval OT_ERROR_NONE   Frame was prepared successfully.
-         * @retval OT_ERROR_ABORT  Indirect transmission to child should be aborted (no frame for the child).
+         * @retval kErrorNone   Frame was prepared successfully.
+         * @retval kErrorAbort  Indirect transmission to child should be aborted (no frame for the child).
          *
          */
-        otError PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &aContext, Child &aChild);
+        Error PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &aContext, Child &aChild);
 
         /**
          * This callback method notifies the end of indirect frame transmission to a child.
          *
          * @param[in]  aFrame     The transmitted frame.
          * @param[in]  aContext   The context associated with the frame when it was prepared.
-         * @param[in]  aError     OT_ERROR_NONE when the frame was transmitted successfully,
-         *                        OT_ERROR_NO_ACK when the frame was transmitted but no ACK was received,
-         *                        OT_ERROR_CHANNEL_ACCESS_FAILURE tx failed due to activity on the channel,
-         *                        OT_ERROR_ABORT when transmission was aborted for other reasons.
+         * @param[in]  aError     kErrorNone when the frame was transmitted successfully,
+         *                        kErrorNoAck when the frame was transmitted but no ACK was received,
+         *                        kErrorChannelAccessFailure tx failed due to activity on the channel,
+         *                        kErrorAbort when transmission was aborted for other reasons.
          * @param[in]  aChild     The child to which the frame was transmitted.
          *
          */
         void HandleSentFrameToChild(const Mac::TxFrame &aFrame,
                                     const FrameContext &aContext,
-                                    otError             aError,
+                                    Error               aError,
                                     Child &             aChild);
 
         /**
@@ -272,9 +272,9 @@
     // Callbacks from MAC
     void          HandleDataPoll(Mac::RxFrame &aFrame);
     Mac::TxFrame *HandleFrameRequest(Mac::TxFrames &aTxFrames);
-    void          HandleSentFrame(const Mac::TxFrame &aFrame, otError aError);
+    void          HandleSentFrame(const Mac::TxFrame &aFrame, Error aError);
 
-    void HandleSentFrame(const Mac::TxFrame &aFrame, otError aError, Child &aChild);
+    void HandleSentFrame(const Mac::TxFrame &aFrame, Error aError, Child &aChild);
     void ProcessPendingPolls(void);
 
     // In the current implementation of `DataPollHandler`, we can have a
diff --git a/src/core/mac/data_poll_sender.cpp b/src/core/mac/data_poll_sender.cpp
index 0d36fd7..a92c003 100644
--- a/src/core/mac/data_poll_sender.cpp
+++ b/src/core/mac/data_poll_sender.cpp
@@ -52,7 +52,7 @@
     , mPollPeriod(0)
     , mExternalPollPeriod(0)
     , mFastPollsUsers(0)
-    , mTimer(aInstance, DataPollSender::HandlePollTimer, this)
+    , mTimer(aInstance, DataPollSender::HandlePollTimer)
     , mEnabled(false)
     , mAttachMode(false)
     , mRetxMode(false)
@@ -94,14 +94,14 @@
     mEnabled              = false;
 }
 
-otError DataPollSender::SendDataPoll(void)
+Error DataPollSender::SendDataPoll(void)
 {
-    otError error;
+    Error error;
 
-    VerifyOrExit(mEnabled, error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(!Get<Mac::Mac>().GetRxOnWhenIdle(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(mEnabled, error = kErrorInvalidState);
+    VerifyOrExit(!Get<Mac::Mac>().GetRxOnWhenIdle(), error = kErrorInvalidState);
 
-    VerifyOrExit(GetParent().IsStateValidOrRestoring(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(GetParent().IsStateValidOrRestoring(), error = kErrorInvalidState);
 
     mTimer.Stop();
 
@@ -111,23 +111,23 @@
 
     switch (error)
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         otLogDebgMac("Sending data poll");
         ScheduleNextPoll(kUsePreviousPollPeriod);
         break;
 
-    case OT_ERROR_INVALID_STATE:
+    case kErrorInvalidState:
         otLogWarnMac("Data poll tx requested while data polling was not enabled!");
         StopPolling();
         break;
 
-    case OT_ERROR_ALREADY:
+    case kErrorAlready:
         otLogDebgMac("Data poll tx requested when a previous data request still in send queue.");
         ScheduleNextPoll(kUsePreviousPollPeriod);
         break;
 
     default:
-        otLogWarnMac("Unexpected error %s requesting data poll", otThreadErrorToString(error));
+        otLogWarnMac("Unexpected error %s requesting data poll", ErrorToString(error));
         ScheduleNextPoll(kRecalculatePollPeriod);
         break;
     }
@@ -136,15 +136,15 @@
 }
 
 #if OPENTHREAD_CONFIG_MULTI_RADIO
-otError DataPollSender::GetPollDestinationAddress(Mac::Address &aDest, Mac::RadioType &aRadioType) const
+Error DataPollSender::GetPollDestinationAddress(Mac::Address &aDest, Mac::RadioType &aRadioType) const
 #else
-otError DataPollSender::GetPollDestinationAddress(Mac::Address &aDest) const
+Error DataPollSender::GetPollDestinationAddress(Mac::Address &aDest) const
 #endif
 {
-    otError         error  = OT_ERROR_NONE;
+    Error           error  = kErrorNone;
     const Neighbor &parent = GetParent();
 
-    VerifyOrExit(parent.IsStateValidOrRestoring(), error = OT_ERROR_ABORT);
+    VerifyOrExit(parent.IsStateValidOrRestoring(), error = kErrorAbort);
 
     // Use extended address attaching to a new parent (i.e. parent is the parent candidate).
     if ((Get<Mac::Mac>().GetShortAddress() == Mac::kShortAddrInvalid) ||
@@ -165,13 +165,13 @@
     return error;
 }
 
-otError DataPollSender::SetExternalPollPeriod(uint32_t aPeriod)
+Error DataPollSender::SetExternalPollPeriod(uint32_t aPeriod)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     if (aPeriod != 0)
     {
-        VerifyOrExit(aPeriod >= OPENTHREAD_CONFIG_MAC_MINIMUM_POLL_PERIOD, error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit(aPeriod >= OPENTHREAD_CONFIG_MAC_MINIMUM_POLL_PERIOD, error = kErrorInvalidArgs);
 
         // Clipped by the maximal value.
         if (aPeriod > kMaxExternalPeriod)
@@ -206,7 +206,7 @@
     return period;
 }
 
-void DataPollSender::HandlePollSent(Mac::TxFrame &aFrame, otError aError)
+void DataPollSender::HandlePollSent(Mac::TxFrame &aFrame, Error aError)
 {
     Mac::Address macDest;
     bool         shouldRecalculatePollPeriod = false;
@@ -228,7 +228,7 @@
 
     switch (aError)
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
 
         if (mRemainingFastPolls != 0)
         {
@@ -250,8 +250,8 @@
 
         break;
 
-    case OT_ERROR_CHANNEL_ACCESS_FAILURE:
-    case OT_ERROR_ABORT:
+    case kErrorChannelAccessFailure:
+    case kErrorAbort:
         mRetxMode                   = true;
         shouldRecalculatePollPeriod = true;
         break;
@@ -259,8 +259,8 @@
     default:
         mPollTxFailureCounter++;
 
-        otLogInfoMac("Failed to send data poll, error:%s, retx:%d/%d", otThreadErrorToString(aError),
-                     mPollTxFailureCounter, kMaxPollRetxAttempts);
+        otLogInfoMac("Failed to send data poll, error:%s, retx:%d/%d", ErrorToString(aError), mPollTxFailureCounter,
+                     kMaxPollRetxAttempts);
 
         if (mPollTxFailureCounter < kMaxPollRetxAttempts)
         {
@@ -499,7 +499,7 @@
 
 void DataPollSender::HandlePollTimer(Timer &aTimer)
 {
-    IgnoreError(aTimer.GetOwner<DataPollSender>().SendDataPoll());
+    IgnoreError(aTimer.Get<DataPollSender>().SendDataPoll());
 }
 
 uint32_t DataPollSender::GetDefaultPollPeriod(void) const
diff --git a/src/core/mac/data_poll_sender.hpp b/src/core/mac/data_poll_sender.hpp
index 8eb9f06..d37d468 100644
--- a/src/core/mac/data_poll_sender.hpp
+++ b/src/core/mac/data_poll_sender.hpp
@@ -92,13 +92,13 @@
     /**
      * This method enqueues a data poll (an IEEE 802.15.4 Data Request) message.
      *
-     * @retval OT_ERROR_NONE           Successfully enqueued a data poll message
-     * @retval OT_ERROR_ALREADY        A data poll message is already enqueued.
-     * @retval OT_ERROR_INVALID_STATE  Device is not in rx-off-when-idle mode.
-     * @retval OT_ERROR_NO_BUFS        Insufficient message buffers available.
+     * @retval kErrorNone          Successfully enqueued a data poll message
+     * @retval kErrorAlready       A data poll message is already enqueued.
+     * @retval kErrorInvalidState  Device is not in rx-off-when-idle mode.
+     * @retval kErrorNoBufs        Insufficient message buffers available.
      *
      */
-    otError SendDataPoll(void);
+    Error SendDataPoll(void);
 
     /**
      * This method sets/clears a user-specified/external data poll period.
@@ -111,16 +111,16 @@
      * value is larger than the child timeout.
      *
      * A non-zero `aPeriod` should be larger than or equal to `OPENTHREAD_CONFIG_MAC_MINIMUM_POLL_PERIOD` (10ms) or
-     * this method returns `OT_ERROR_INVALID_ARGS`. If a non-zero `aPeriod` is larger than maximum value of
+     * this method returns `kErrorInvalidArgs`. If a non-zero `aPeriod` is larger than maximum value of
      * `0x3FFFFFF ((1 << 26) - 1)`, it would be clipped to this value.
      *
      * @param[in]  aPeriod  The data poll period in milliseconds.
      *
-     * @retval OT_ERROR_NONE           Successfully set/cleared user-specified poll period.
-     * @retval OT_ERROR_INVALID_ARGS   If aPeriod is invalid.
+     * @retval kErrorNone           Successfully set/cleared user-specified poll period.
+     * @retval kErrorInvalidArgs    If aPeriod is invalid.
      *
      */
-    otError SetExternalPollPeriod(uint32_t aPeriod);
+    Error SetExternalPollPeriod(uint32_t aPeriod);
 
     /**
      * This method gets the current user-specified/external data poll period.
@@ -137,22 +137,22 @@
      * @param[out] aDest       Reference to a `MAC::Address` to output the poll destination address (on success).
      * @param[out] aRadioType  Reference to a `Mac::RadioType` to output the link type (on success).
      *
-     * @retval OT_ERROR_NONE   @p aDest and @p aRadioType were updated successfully.
-     * @retval OT_ERROR_ABORT  Abort the data poll transmission (not currently attached to any parent).
+     * @retval kErrorNone   @p aDest and @p aRadioType were updated successfully.
+     * @retval kErrorAbort  Abort the data poll transmission (not currently attached to any parent).
      *
      */
-    otError GetPollDestinationAddress(Mac::Address &aDest, Mac::RadioType &aRadioType) const;
+    Error GetPollDestinationAddress(Mac::Address &aDest, Mac::RadioType &aRadioType) const;
 #else
     /**
      * This method gets the destination MAC address for a data poll frame.
      *
      * @param[out] aDest       Reference to a `MAC::Address` to output the poll destination address (on success).
      *
-     * @retval OT_ERROR_NONE   @p aDest was updated successfully.
-     * @retval OT_ERROR_ABORT  Abort the data poll transmission (not currently attached to any parent).
+     * @retval kErrorNone   @p aDest was updated successfully.
+     * @retval kErrorAbort  Abort the data poll transmission (not currently attached to any parent).
      *
      */
-    otError GetPollDestinationAddress(Mac::Address &aDest) const;
+    Error GetPollDestinationAddress(Mac::Address &aDest) const;
 #endif // #if OPENTHREAD_CONFIG_MULTI_RADIO
 
     /**
@@ -166,7 +166,7 @@
      * @param[in] aError     Error status of a data poll message transmission.
      *
      */
-    void HandlePollSent(Mac::TxFrame &aFrame, otError aError);
+    void HandlePollSent(Mac::TxFrame &aFrame, Error aError);
 
     /**
      * This method informs the data poll sender that a data poll timeout happened, i.e., when the ack in response to
diff --git a/src/core/mac/link_raw.cpp b/src/core/mac/link_raw.cpp
index 5f8766c..4abd372 100644
--- a/src/core/mac/link_raw.cpp
+++ b/src/core/mac/link_raw.cpp
@@ -64,23 +64,32 @@
 {
 }
 
-otError LinkRaw::SetReceiveDone(otLinkRawReceiveDone aCallback)
+Error LinkRaw::SetReceiveDone(otLinkRawReceiveDone aCallback)
 {
-    otError error = OT_ERROR_NONE;
+    Error error  = kErrorNone;
+    bool  enable = aCallback != nullptr;
 
-    otLogDebgMac("LinkRaw::Enabled(%s)", (aCallback != nullptr ? "true" : "false"));
+    otLogDebgMac("LinkRaw::Enabled(%s)", (enable ? "true" : "false"));
 
 #if OPENTHREAD_MTD || OPENTHREAD_FTD
-    VerifyOrExit(!Get<ThreadNetif>().IsUp(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(!Get<ThreadNetif>().IsUp(), error = kErrorInvalidState);
 
     // In MTD/FTD build, `Mac` has already enabled sub-mac. We ensure to
     // disable/enable MAC layer when link-raw is being enabled/disabled to
     // avoid any conflict in control of radio and sub-mac between `Mac` and
     // `LinkRaw`. in RADIO build, we directly enable/disable sub-mac.
 
-    Get<Mac>().SetEnabled(aCallback == nullptr);
+    if (!enable)
+    {
+        // When disabling link-raw, make sure there is no ongoing
+        // transmit or scan operation. Otherwise Mac will attempt to
+        // handle an unexpected "done" callback.
+        VerifyOrExit(!mSubMac.IsTransmittingOrScanning(), error = kErrorBusy);
+    }
+
+    Get<Mac>().SetEnabled(!enable);
 #else
-    if (aCallback)
+    if (enable)
     {
         SuccessOrExit(error = mSubMac.Enable());
     }
@@ -96,11 +105,11 @@
     return error;
 }
 
-otError LinkRaw::SetPanId(uint16_t aPanId)
+Error LinkRaw::SetPanId(uint16_t aPanId)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
     mSubMac.SetPanId(aPanId);
     mPanId = aPanId;
 
@@ -108,44 +117,44 @@
     return error;
 }
 
-otError LinkRaw::SetChannel(uint8_t aChannel)
+Error LinkRaw::SetChannel(uint8_t aChannel)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
     mReceiveChannel = aChannel;
 
 exit:
     return error;
 }
 
-otError LinkRaw::SetExtAddress(const ExtAddress &aExtAddress)
+Error LinkRaw::SetExtAddress(const ExtAddress &aExtAddress)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
     mSubMac.SetExtAddress(aExtAddress);
 
 exit:
     return error;
 }
 
-otError LinkRaw::SetShortAddress(ShortAddress aShortAddress)
+Error LinkRaw::SetShortAddress(ShortAddress aShortAddress)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
     mSubMac.SetShortAddress(aShortAddress);
 
 exit:
     return error;
 }
 
-otError LinkRaw::Receive(void)
+Error LinkRaw::Receive(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
 
     SuccessOrExit(error = mSubMac.Receive(mReceiveChannel));
 
@@ -153,22 +162,22 @@
     return error;
 }
 
-void LinkRaw::InvokeReceiveDone(RxFrame *aFrame, otError aError)
+void LinkRaw::InvokeReceiveDone(RxFrame *aFrame, Error aError)
 {
     otLogDebgMac("LinkRaw::ReceiveDone(%d bytes), error:%s", (aFrame != nullptr) ? aFrame->mLength : 0,
-                 otThreadErrorToString(aError));
+                 ErrorToString(aError));
 
-    if (mReceiveDoneCallback && (aError == OT_ERROR_NONE))
+    if (mReceiveDoneCallback && (aError == kErrorNone))
     {
         mReceiveDoneCallback(&GetInstance(), aFrame, aError);
     }
 }
 
-otError LinkRaw::Transmit(otLinkRawTransmitDone aCallback)
+Error LinkRaw::Transmit(otLinkRawTransmitDone aCallback)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
 
     SuccessOrExit(error = mSubMac.Send());
     mTransmitDoneCallback = aCallback;
@@ -177,9 +186,9 @@
     return error;
 }
 
-void LinkRaw::InvokeTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, otError aError)
+void LinkRaw::InvokeTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aError)
 {
-    otLogDebgMac("LinkRaw::TransmitDone(%d bytes), error:%s", aFrame.mLength, otThreadErrorToString(aError));
+    otLogDebgMac("LinkRaw::TransmitDone(%d bytes), error:%s", aFrame.mLength, ErrorToString(aError));
 
     if (mTransmitDoneCallback)
     {
@@ -188,11 +197,11 @@
     }
 }
 
-otError LinkRaw::EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration, otLinkRawEnergyScanDone aCallback)
+Error LinkRaw::EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration, otLinkRawEnergyScanDone aCallback)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
 
     SuccessOrExit(error = mSubMac.EnergyScan(aScanChannel, aScanDuration));
     mEnergyScanDoneCallback = aCallback;
@@ -210,26 +219,26 @@
     }
 }
 
-otError LinkRaw::SetMacKey(uint8_t    aKeyIdMode,
-                           uint8_t    aKeyId,
-                           const Key &aPrevKey,
-                           const Key &aCurrKey,
-                           const Key &aNextKey)
+Error LinkRaw::SetMacKey(uint8_t    aKeyIdMode,
+                         uint8_t    aKeyId,
+                         const Key &aPrevKey,
+                         const Key &aCurrKey,
+                         const Key &aNextKey)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
     mSubMac.SetMacKey(aKeyIdMode, aKeyId, aPrevKey, aCurrKey, aNextKey);
 
 exit:
     return error;
 }
 
-otError LinkRaw::SetMacFrameCounter(uint32_t aMacFrameCounter)
+Error LinkRaw::SetMacFrameCounter(uint32_t aMacFrameCounter)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
     mSubMac.SetFrameCounter(aMacFrameCounter);
 
 exit:
@@ -242,16 +251,16 @@
 
 void LinkRaw::RecordFrameTransmitStatus(const TxFrame &aFrame,
                                         const RxFrame *aAckFrame,
-                                        otError        aError,
+                                        Error          aError,
                                         uint8_t        aRetryCount,
                                         bool           aWillRetx)
 {
     OT_UNUSED_VARIABLE(aAckFrame);
     OT_UNUSED_VARIABLE(aWillRetx);
 
-    if (aError != OT_ERROR_NONE)
+    if (aError != kErrorNone)
     {
-        otLogInfoMac("Frame tx failed, error:%s, retries:%d/%d, %s", otThreadErrorToString(aError), aRetryCount,
+        otLogInfoMac("Frame tx failed, error:%s, retries:%d/%d, %s", ErrorToString(aError), aRetryCount,
                      aFrame.GetMaxFrameRetries(), aFrame.ToInfoString().AsCString());
     }
 }
diff --git a/src/core/mac/link_raw.hpp b/src/core/mac/link_raw.hpp
index 0f0c819..ed0fecf 100644
--- a/src/core/mac/link_raw.hpp
+++ b/src/core/mac/link_raw.hpp
@@ -80,12 +80,12 @@
      *                        raw link-layer.
      *
      *
-     * @retval OT_ERROR_INVALID_STATE   Thread stack is enabled.
-     * @retval OT_ERROR_FAILED          The radio could not be enabled/disabled.
-     * @retval OT_ERROR_NONE            Successfully enabled/disabled raw link.
+     * @retval kErrorInvalidState    Thread stack is enabled.
+     * @retval kErrorFailed          The radio could not be enabled/disabled.
+     * @retval kErrorNone            Successfully enabled/disabled raw link.
      *
      */
-    otError SetReceiveDone(otLinkRawReceiveDone aCallback);
+    Error SetReceiveDone(otLinkRawReceiveDone aCallback);
 
     /**
      * This method returns the capabilities of the raw link-layer.
@@ -98,22 +98,22 @@
     /**
      * This method starts a (recurring) Receive on the link-layer.
      *
-     * @retval OT_ERROR_NONE             Successfully transitioned to Receive.
-     * @retval OT_ERROR_INVALID_STATE    The radio was disabled or transmitting.
+     * @retval kErrorNone            Successfully transitioned to Receive.
+     * @retval kErrorInvalidState    The radio was disabled or transmitting.
      *
      */
-    otError Receive(void);
+    Error Receive(void);
 
     /**
      * This method invokes the mReceiveDoneCallback, if set.
      *
      * @param[in]  aFrame    A pointer to the received frame or nullptr if the receive operation failed.
-     * @param[in]  aError    OT_ERROR_NONE when successfully received a frame,
-     *                       OT_ERROR_ABORT when reception was aborted and a frame was not received,
-     *                       OT_ERROR_NO_BUFS when a frame could not be received due to lack of rx buffer space.
+     * @param[in]  aError    kErrorNone when successfully received a frame,
+     *                       kErrorAbort when reception was aborted and a frame was not received,
+     *                       kErrorNoBufs when a frame could not be received due to lack of rx buffer space.
      *
      */
-    void InvokeReceiveDone(RxFrame *aFrame, otError aError);
+    void InvokeReceiveDone(RxFrame *aFrame, Error aError);
 
     /**
      * This method gets the radio transmit frame.
@@ -126,28 +126,28 @@
     /**
      * This method starts a (single) Transmit on the link-layer.
      *
-     * @note The callback @p aCallback will not be called if this call does not return OT_ERROR_NONE.
+     * @note The callback @p aCallback will not be called if this call does not return kErrorNone.
      *
      * @param[in]  aCallback            A pointer to a function called on completion of the transmission.
      *
-     * @retval OT_ERROR_NONE            Successfully transitioned to Transmit.
-     * @retval OT_ERROR_INVALID_STATE   The radio was not in the Receive state.
+     * @retval kErrorNone           Successfully transitioned to Transmit.
+     * @retval kErrorInvalidState   The radio was not in the Receive state.
      *
      */
-    otError Transmit(otLinkRawTransmitDone aCallback);
+    Error Transmit(otLinkRawTransmitDone aCallback);
 
     /**
      * This method invokes the mTransmitDoneCallback, if set.
      *
      * @param[in]  aFrame     The transmitted frame.
      * @param[in]  aAckFrame  A pointer to the ACK frame, nullptr if no ACK was received.
-     * @param[in]  aError     OT_ERROR_NONE when the frame was transmitted,
-     *                        OT_ERROR_NO_ACK when the frame was transmitted but no ACK was received,
-     *                        OT_ERROR_CHANNEL_ACCESS_FAILURE tx failed due to activity on the channel,
-     *                        OT_ERROR_ABORT when transmission was aborted for other reasons.
+     * @param[in]  aError     kErrorNone when the frame was transmitted,
+     *                        kErrorNoAck when the frame was transmitted but no ACK was received,
+     *                        kErrorChannelAccessFailure tx failed due to activity on the channel,
+     *                        kErrorAbort when transmission was aborted for other reasons.
      *
      */
-    void InvokeTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, otError aError);
+    void InvokeTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aError);
 
     /**
      * This method starts a (single) Energy Scan on the link-layer.
@@ -156,12 +156,12 @@
      * @param[in]  aScanDuration    The duration, in milliseconds, for the channel to be scanned.
      * @param[in]  aCallback        A pointer to a function called on completion of a scanned channel.
      *
-     * @retval OT_ERROR_NONE             Successfully started scanning the channel.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  The radio doesn't support energy scanning.
-     * @retval OT_ERROR_INVALID_STATE    If the raw link-layer isn't enabled.
+     * @retval kErrorNone            Successfully started scanning the channel.
+     * @retval kErrorNotImplemented  The radio doesn't support energy scanning.
+     * @retval kErrorInvalidState    If the raw link-layer isn't enabled.
      *
      */
-    otError EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration, otLinkRawEnergyScanDone aCallback);
+    Error EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration, otLinkRawEnergyScanDone aCallback);
 
     /**
      * This method invokes the mEnergyScanDoneCallback, if set.
@@ -184,11 +184,11 @@
      *
      * @param[in]   aShortAddress   The short address.
      *
-     * @retval OT_ERROR_NONE             If successful.
-     * @retval OT_ERROR_INVALID_STATE    If the raw link-layer isn't enabled.
+     * @retval kErrorNone            If successful.
+     * @retval kErrorInvalidState    If the raw link-layer isn't enabled.
      *
      */
-    otError SetShortAddress(ShortAddress aShortAddress);
+    Error SetShortAddress(ShortAddress aShortAddress);
 
     /**
      * This function returns PANID.
@@ -203,11 +203,11 @@
      *
      * @param[in]   aPanId          The PANID.
      *
-     * @retval OT_ERROR_NONE             If successful.
-     * @retval OT_ERROR_INVALID_STATE    If the raw link-layer isn't enabled.
+     * @retval kErrorNone            If successful.
+     * @retval kErrorInvalidState    If the raw link-layer isn't enabled.
      *
      */
-    otError SetPanId(PanId aPanId);
+    Error SetPanId(PanId aPanId);
 
     /**
      * This method gets the current receiving channel.
@@ -223,7 +223,7 @@
      * @param[in]  aChannel     The channel to use for receiving.
      *
      */
-    otError SetChannel(uint8_t aChannel);
+    Error SetChannel(uint8_t aChannel);
 
     /**
      * This function returns the extended address.
@@ -238,11 +238,11 @@
      *
      * @param[in]   aExtAddress     The extended address.
      *
-     * @retval OT_ERROR_NONE             If successful.
-     * @retval OT_ERROR_INVALID_STATE    If the raw link-layer isn't enabled.
+     * @retval kErrorNone            If successful.
+     * @retval kErrorInvalidState    If the raw link-layer isn't enabled.
      *
      */
-    otError SetExtAddress(const ExtAddress &aExtAddress);
+    Error SetExtAddress(const ExtAddress &aExtAddress);
 
     /**
      * This method updates MAC keys and key index.
@@ -253,26 +253,22 @@
      * @param[in]   aCurrKey          The current MAC key.
      * @param[in]   aNextKey          The next MAC key.
      *
-     * @retval OT_ERROR_NONE             If successful.
-     * @retval OT_ERROR_INVALID_STATE    If the raw link-layer isn't enabled.
+     * @retval kErrorNone            If successful.
+     * @retval kErrorInvalidState    If the raw link-layer isn't enabled.
      *
      */
-    otError SetMacKey(uint8_t    aKeyIdMode,
-                      uint8_t    aKeyId,
-                      const Key &aPrevKey,
-                      const Key &aCurrKey,
-                      const Key &aNextKey);
+    Error SetMacKey(uint8_t aKeyIdMode, uint8_t aKeyId, const Key &aPrevKey, const Key &aCurrKey, const Key &aNextKey);
 
     /**
      * This method sets the current MAC frame counter value.
      *
      * @param[in] aMacFrameCounter  The MAC frame counter value.
      *
-     * @retval OT_ERROR_NONE             If successful.
-     * @retval OT_ERROR_INVALID_STATE    If the raw link-layer isn't enabled.
+     * @retval kErrorNone            If successful.
+     * @retval kErrorInvalidState    If the raw link-layer isn't enabled.
      *
      */
-    otError SetMacFrameCounter(uint32_t aMacFrameCounter);
+    Error SetMacFrameCounter(uint32_t aMacFrameCounter);
 
     /**
      * This method records the status of a frame transmission attempt and is mainly used for logging failures.
@@ -282,10 +278,10 @@
      *
      * @param[in] aFrame      The transmitted frame.
      * @param[in] aAckFrame   A pointer to the ACK frame, or nullptr if no ACK was received.
-     * @param[in] aError      OT_ERROR_NONE when the frame was transmitted successfully,
-     *                        OT_ERROR_NO_ACK when the frame was transmitted but no ACK was received,
-     *                        OT_ERROR_CHANNEL_ACCESS_FAILURE tx failed due to activity on the channel,
-     *                        OT_ERROR_ABORT when transmission was aborted for other reasons.
+     * @param[in] aError      kErrorNone when the frame was transmitted successfully,
+     *                        kErrorNoAck when the frame was transmitted but no ACK was received,
+     *                        kErrorChannelAccessFailure tx failed due to activity on the channel,
+     *                        kErrorAbort when transmission was aborted for other reasons.
      * @param[in] aRetryCount Indicates number of transmission retries for this frame.
      * @param[in] aWillRetx   Indicates whether frame will be retransmitted or not. This is applicable only
      *                        when there was an error in transmission (i.e., `aError` is not NONE).
@@ -294,11 +290,11 @@
 #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_MAC == 1)
     void RecordFrameTransmitStatus(const TxFrame &aFrame,
                                    const RxFrame *aAckFrame,
-                                   otError        aError,
+                                   Error          aError,
                                    uint8_t        aRetryCount,
                                    bool           aWillRetx);
 #else
-    void    RecordFrameTransmitStatus(const TxFrame &, const RxFrame *, otError, uint8_t, bool) {}
+    void    RecordFrameTransmitStatus(const TxFrame &, const RxFrame *, Error, uint8_t, bool) {}
 #endif
 
 private:
diff --git a/src/core/mac/mac.cpp b/src/core/mac/mac.cpp
index de0a50a..6a1d6d4 100644
--- a/src/core/mac/mac.cpp
+++ b/src/core/mac/mac.cpp
@@ -70,7 +70,7 @@
 const char Mac::sNetworkNameInit[] = "OpenThread";
 
 #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
-const char Mac::sDomainNameInit[] = "Thread";
+const char Mac::sDomainNameInit[] = "DefaultDomain";
 #endif
 
 Mac::Mac(Instance &aInstance)
@@ -82,6 +82,9 @@
     , mPendingTransmitDataDirect(false)
 #if OPENTHREAD_FTD
     , mPendingTransmitDataIndirect(false)
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+    , mPendingTransmitDataCsl(false)
+#endif
 #endif
     , mPendingTransmitPoll(false)
     , mPendingTransmitOobFrame(false)
@@ -108,20 +111,20 @@
     , mMaxFrameRetriesDirect(kDefaultMaxFrameRetriesDirect)
 #if OPENTHREAD_FTD
     , mMaxFrameRetriesIndirect(kDefaultMaxFrameRetriesIndirect)
-#endif
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     , mCslTxFireTime(TimeMilli::kMaxDuration)
 #endif
+#endif
     , mActiveScanHandler(nullptr) // Initialize `mActiveScanHandler` and `mEnergyScanHandler` union
     , mScanHandlerContext(nullptr)
     , mLinks(aInstance)
-    , mOperationTask(aInstance, Mac::HandleOperationTask, this)
-    , mTimer(aInstance, Mac::HandleTimer, this)
+    , mOperationTask(aInstance, Mac::HandleOperationTask)
+    , mTimer(aInstance, Mac::HandleTimer)
     , mOobFrame(nullptr)
     , mKeyIdMode2FrameCounter(0)
     , mCcaSampleCount(0)
 #if OPENTHREAD_CONFIG_MULTI_RADIO
-    , mTxError(OT_ERROR_NONE)
+    , mTxError(kErrorNone)
 #endif
 {
     ExtAddress randomExtAddress;
@@ -146,12 +149,12 @@
     SetShortAddress(GetShortAddress());
 }
 
-otError Mac::ActiveScan(uint32_t aScanChannels, uint16_t aScanDuration, ActiveScanHandler aHandler, void *aContext)
+Error Mac::ActiveScan(uint32_t aScanChannels, uint16_t aScanDuration, ActiveScanHandler aHandler, void *aContext)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(!IsActiveScanInProgress() && !IsEnergyScanInProgress(), error = OT_ERROR_BUSY);
+    VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
+    VerifyOrExit(!IsActiveScanInProgress() && !IsEnergyScanInProgress(), error = kErrorBusy);
 
     mActiveScanHandler  = aHandler;
     mScanHandlerContext = aContext;
@@ -167,12 +170,12 @@
     return error;
 }
 
-otError Mac::EnergyScan(uint32_t aScanChannels, uint16_t aScanDuration, EnergyScanHandler aHandler, void *aContext)
+Error Mac::EnergyScan(uint32_t aScanChannels, uint16_t aScanDuration, EnergyScanHandler aHandler, void *aContext)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(!IsActiveScanInProgress() && !IsEnergyScanInProgress(), error = OT_ERROR_BUSY);
+    VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
+    VerifyOrExit(!IsActiveScanInProgress() && !IsEnergyScanInProgress(), error = kErrorBusy);
 
     mEnergyScanHandler  = aHandler;
     mScanHandlerContext = aContext;
@@ -207,7 +210,7 @@
     case kOperationTransmitDataDirect:
 #if OPENTHREAD_FTD
     case kOperationTransmitDataIndirect:
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     case kOperationTransmitDataCsl:
 #endif
 #endif
@@ -228,9 +231,9 @@
     return retval;
 }
 
-otError Mac::ConvertBeaconToActiveScanResult(const RxFrame *aBeaconFrame, ActiveScanResult &aResult)
+Error Mac::ConvertBeaconToActiveScanResult(const RxFrame *aBeaconFrame, ActiveScanResult &aResult)
 {
-    otError              error = OT_ERROR_NONE;
+    Error                error = kErrorNone;
     Address              address;
     const Beacon *       beacon        = nullptr;
     const BeaconPayload *beaconPayload = nullptr;
@@ -238,14 +241,14 @@
 
     memset(&aResult, 0, sizeof(ActiveScanResult));
 
-    VerifyOrExit(aBeaconFrame != nullptr, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aBeaconFrame != nullptr, error = kErrorInvalidArgs);
 
-    VerifyOrExit(aBeaconFrame->GetType() == Frame::kFcfFrameBeacon, error = OT_ERROR_PARSE);
+    VerifyOrExit(aBeaconFrame->GetType() == Frame::kFcfFrameBeacon, error = kErrorParse);
     SuccessOrExit(error = aBeaconFrame->GetSrcAddr(address));
-    VerifyOrExit(address.IsExtended(), error = OT_ERROR_PARSE);
+    VerifyOrExit(address.IsExtended(), error = kErrorParse);
     aResult.mExtAddress = address.GetExtended();
 
-    if (OT_ERROR_NONE != aBeaconFrame->GetSrcPanId(aResult.mPanId))
+    if (kErrorNone != aBeaconFrame->GetSrcPanId(aResult.mPanId))
     {
         IgnoreError(aBeaconFrame->GetDstPanId(aResult.mPanId));
     }
@@ -265,7 +268,7 @@
         aResult.mIsJoinable = beaconPayload->IsJoiningPermitted();
         aResult.mIsNative   = beaconPayload->IsNative();
         IgnoreError(static_cast<NetworkName &>(aResult.mNetworkName).Set(beaconPayload->GetNetworkName()));
-        VerifyOrExit(IsValidUtf8String(aResult.mNetworkName.m8), error = OT_ERROR_PARSE);
+        VerifyOrExit(IsValidUtf8String(aResult.mNetworkName.m8), error = kErrorParse);
         aResult.mExtendedPanId = beaconPayload->GetExtendedPanId();
     }
 
@@ -275,11 +278,11 @@
     return error;
 }
 
-otError Mac::UpdateScanChannel(void)
+Error Mac::UpdateScanChannel(void)
 {
-    otError error;
+    Error error;
 
-    VerifyOrExit(IsEnabled(), error = OT_ERROR_ABORT);
+    VerifyOrExit(IsEnabled(), error = kErrorAbort);
 
     error = mScanChannelMask.GetNextChannel(mScanChannel);
 
@@ -289,7 +292,7 @@
 
 void Mac::PerformActiveScan(void)
 {
-    if (UpdateScanChannel() == OT_ERROR_NONE)
+    if (UpdateScanChannel() == kErrorNone)
     {
         // If there are more channels to scan, send the beacon request.
         BeginTransmit();
@@ -325,7 +328,7 @@
 
 void Mac::PerformEnergyScan(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     SuccessOrExit(error = UpdateScanChannel());
 
@@ -345,7 +348,7 @@
 
 exit:
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         FinishOperation();
 
@@ -417,11 +420,11 @@
     return;
 }
 
-otError Mac::SetPanChannel(uint8_t aChannel)
+Error Mac::SetPanChannel(uint8_t aChannel)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(mSupportedChannelMask.ContainsChannel(aChannel), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(mSupportedChannelMask.ContainsChannel(aChannel), error = kErrorInvalidArgs);
 
     SuccessOrExit(Get<Notifier>().Update(mPanChannel, aChannel, kEventThreadChannelChanged));
 
@@ -437,11 +440,11 @@
     return error;
 }
 
-otError Mac::SetTemporaryChannel(uint8_t aChannel)
+Error Mac::SetTemporaryChannel(uint8_t aChannel)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(mSupportedChannelMask.ContainsChannel(aChannel), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(mSupportedChannelMask.ContainsChannel(aChannel), error = kErrorInvalidArgs);
 
     mUsingTemporaryChannel = true;
     mRadioChannel          = aChannel;
@@ -470,7 +473,7 @@
     IgnoreError(Get<Notifier>().Update(mSupportedChannelMask, newMask, kEventSupportedChannelMaskChanged));
 }
 
-otError Mac::SetNetworkName(const char *aNameString)
+Error Mac::SetNetworkName(const char *aNameString)
 {
     // When setting Network Name from a string, we treat it as `NameData`
     // with `kMaxSize + 1` chars. `NetworkName::Set(data)` will look
@@ -478,12 +481,12 @@
     // the name's length and ensure that the name fits in `kMaxSize`
     // chars. The `+ 1` ensures that a `aNameString` with length
     // longer than `kMaxSize` is correctly rejected (returning error
-    // `OT_ERROR_INVALID_ARGS`).
+    // `kErrorInvalidArgs`).
 
-    otError  error;
+    Error    error;
     NameData data(aNameString, NetworkName::kMaxSize + 1);
 
-    VerifyOrExit(IsValidUtf8String(aNameString), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(IsValidUtf8String(aNameString), error = kErrorInvalidArgs);
 
     error = SetNetworkName(data);
 
@@ -491,14 +494,14 @@
     return error;
 }
 
-otError Mac::SetNetworkName(const NameData &aNameData)
+Error Mac::SetNetworkName(const NameData &aNameData)
 {
-    otError error = mNetworkName.Set(aNameData);
+    Error error = mNetworkName.Set(aNameData);
 
-    if (error == OT_ERROR_ALREADY)
+    if (error == kErrorAlready)
     {
         Get<Notifier>().SignalIfFirst(kEventThreadNetworkNameChanged);
-        error = OT_ERROR_NONE;
+        error = kErrorNone;
         ExitNow();
     }
 
@@ -510,7 +513,7 @@
 }
 
 #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
-otError Mac::SetDomainName(const char *aNameString)
+Error Mac::SetDomainName(const char *aNameString)
 {
     // When setting Domain Name from a string, we treat it as `NameData`
     // with `kMaxSize + 1` chars. `DomainName::Set(data)` will look
@@ -518,12 +521,12 @@
     // the name's length and ensure that the name fits in `kMaxSize`
     // chars. The `+ 1` ensures that a `aNameString` with length
     // longer than `kMaxSize` is correctly rejected (returning error
-    // `OT_ERROR_INVALID_ARGS`).
+    // `kErrorInvalidArgs`).
 
-    otError  error;
+    Error    error;
     NameData data(aNameString, DomainName::kMaxSize + 1);
 
-    VerifyOrExit(IsValidUtf8String(aNameString), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(IsValidUtf8String(aNameString), error = kErrorInvalidArgs);
 
     error = SetDomainName(data);
 
@@ -531,13 +534,13 @@
     return error;
 }
 
-otError Mac::SetDomainName(const NameData &aNameData)
+Error Mac::SetDomainName(const NameData &aNameData)
 {
-    otError error = mDomainName.Set(aNameData);
+    Error error = mDomainName.Set(aNameData);
 
-    if (error == OT_ERROR_ALREADY)
+    if (error == kErrorAlready)
     {
-        error = OT_ERROR_NONE;
+        error = kErrorNone;
     }
 
     return error;
@@ -581,7 +584,7 @@
     return;
 }
 
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
 void Mac::RequestCslFrameTransmission(uint32_t aDelay)
 {
     VerifyOrExit(mEnabled);
@@ -596,14 +599,13 @@
 #endif
 #endif // OPENTHREAD_FTD
 
-otError Mac::RequestOutOfBandFrameTransmission(otRadioFrame *aOobFrame)
+Error Mac::RequestOutOfBandFrameTransmission(otRadioFrame *aOobFrame)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(aOobFrame != nullptr, error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(!mPendingTransmitOobFrame && (mOperation != kOperationTransmitOutOfBandFrame),
-                 error = OT_ERROR_ALREADY);
+    VerifyOrExit(aOobFrame != nullptr, error = kErrorInvalidArgs);
+    VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
+    VerifyOrExit(!mPendingTransmitOobFrame && (mOperation != kOperationTransmitOutOfBandFrame), error = kErrorAlready);
 
     mOobFrame = static_cast<TxFrame *>(aOobFrame);
 
@@ -613,12 +615,12 @@
     return error;
 }
 
-otError Mac::RequestDataPollTransmission(void)
+Error Mac::RequestDataPollTransmission(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(!mPendingTransmitPoll && (mOperation != kOperationTransmitPoll), error = OT_ERROR_ALREADY);
+    VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
+    VerifyOrExit(!mPendingTransmitPoll && (mOperation != kOperationTransmitPoll), error = kErrorAlready);
 
     // We ensure data frame and data poll tx requests are handled in the
     // order they are requested. So if we have a pending direct data frame
@@ -655,7 +657,7 @@
         }
 #endif
     }
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     else if (mPendingTransmitDataCsl)
     {
         mTimer.FireAt(mCslTxFireTime);
@@ -727,7 +729,7 @@
         mPendingTransmitDataIndirect = true;
         break;
 
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     case kOperationTransmitDataCsl:
         mPendingTransmitDataCsl = true;
         break;
@@ -755,7 +757,7 @@
 
 void Mac::HandleOperationTask(Tasklet &aTasklet)
 {
-    aTasklet.GetOwner<Mac>().PerformNextOperation();
+    aTasklet.Get<Mac>().PerformNextOperation();
 }
 
 void Mac::PerformNextOperation(void)
@@ -772,7 +774,7 @@
         mPendingTransmitDataDirect = false;
 #if OPENTHREAD_FTD
         mPendingTransmitDataIndirect = false;
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
         mPendingTransmitDataCsl = false;
 #endif
 #endif
@@ -793,7 +795,7 @@
         mPendingWaitingForData = false;
         mOperation             = kOperationWaitingForData;
     }
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     else if (mPendingTransmitDataCsl && TimerMilli::GetNow() >= mCslTxFireTime)
     {
         mPendingTransmitDataCsl = false;
@@ -870,7 +872,7 @@
     case kOperationTransmitDataDirect:
 #if OPENTHREAD_FTD
     case kOperationTransmitDataIndirect:
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     case kOperationTransmitDataCsl:
 #endif
 #endif
@@ -900,6 +902,7 @@
     TxFrame *frame = nullptr;
     Address  src, dst;
     uint16_t fcf;
+    bool     iePresent = Get<MeshForwarder>().CalcIePresent(nullptr);
 
 #if OPENTHREAD_CONFIG_MULTI_RADIO
     RadioType radio;
@@ -912,7 +915,13 @@
 #endif
 
     fcf = Frame::kFcfFrameMacCmd | Frame::kFcfPanidCompression | Frame::kFcfAckRequest | Frame::kFcfSecurityEnabled;
-    UpdateFrameControlField(nullptr, /* aIsTimeSync */ false, fcf);
+
+    if (iePresent)
+    {
+        fcf |= Frame::kFcfIePresent;
+    }
+
+    fcf |= Get<MeshForwarder>().CalcFrameVersion(Get<NeighborTable>().FindNeighbor(dst), iePresent);
 
     if (dst.IsExtended())
     {
@@ -935,7 +944,10 @@
     frame->SetSrcAddr(src);
     frame->SetDstAddr(dst);
 #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
-    IgnoreError(AppendHeaderIe(false, *frame));
+    if (iePresent)
+    {
+        Get<MeshForwarder>().AppendHeaderIe(nullptr, *frame);
+    }
 #endif
 
     IgnoreError(frame->SetCommandId(Frame::kMacCmdDataRequest));
@@ -1136,7 +1148,7 @@
 
 #if OPENTHREAD_CONFIG_MULTI_RADIO
     mTxPendingRadioLinks.Clear();
-    mTxError = OT_ERROR_ABORT;
+    mTxError = kErrorAbort;
 #endif
 
     VerifyOrExit(IsEnabled());
@@ -1198,7 +1210,7 @@
         }
         break;
 
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     case kOperationTransmitDataCsl:
         txFrames.SetMaxCsmaBackoffs(kMaxCsmaBackoffsCsl);
         txFrames.SetMaxFrameRetries(kMaxFrameRetriesCsl);
@@ -1287,15 +1299,15 @@
     mTxPendingRadioLinks = txFrames.GetSelectedRadioTypes();
 
     // If the "required radio type set" is empty,`mTxError` starts as
-    // `OT_ERROR_ABORT`. In this case, successful tx over any radio
+    // `kErrorAbort`. In this case, successful tx over any radio
     // link is sufficient for overall tx to be considered successful.
     // When the "required radio type set" is not empty, `mTxError`
-    // starts as `OT_ERROR_NONE` and we update it if tx over any link
+    // starts as `kErrorNone` and we update it if tx over any link
     // in the required set fails.
 
     if (!txFrames.GetRequiredRadioTypes().IsEmpty())
     {
-        mTxError = OT_ERROR_NONE;
+        mTxError = kErrorNone;
     }
 #endif
 
@@ -1325,7 +1337,7 @@
 
         frame = &txFrames.GetBroadcastTxFrame();
         frame->SetLength(0);
-        HandleTransmitDone(*frame, nullptr, OT_ERROR_ABORT);
+        HandleTransmitDone(*frame, nullptr, kErrorAbort);
     }
 }
 
@@ -1352,7 +1364,7 @@
 
 void Mac::RecordFrameTransmitStatus(const TxFrame &aFrame,
                                     const RxFrame *aAckFrame,
-                                    otError        aError,
+                                    Error          aError,
                                     uint8_t        aRetryCount,
                                     bool           aWillRetx)
 {
@@ -1377,12 +1389,12 @@
 
         switch (aError)
         {
-        case OT_ERROR_NO_ACK:
+        case kErrorNoAck:
             frameTxSuccess = false;
 
-            // Fall through
+            OT_FALL_THROUGH;
 
-        case OT_ERROR_NONE:
+        case kErrorNone:
             neighbor->GetLinkInfo().AddFrameTxStatus(frameTxSuccess);
             break;
 
@@ -1393,7 +1405,7 @@
 
     // Log frame transmission failure.
 
-    if (aError != OT_ERROR_NONE)
+    if (aError != kErrorNone)
     {
         LogFrameTxFailure(aFrame, aError, aRetryCount, aWillRetx);
         otDumpDebgMac("TX ERR", aFrame.GetHeader(), 16);
@@ -1412,7 +1424,7 @@
 
     // Update neighbor's RSSI link info from the received Ack.
 
-    if ((aError == OT_ERROR_NONE) && ackRequested && (aAckFrame != nullptr) && (neighbor != nullptr))
+    if ((aError == kErrorNone) && ackRequested && (aAckFrame != nullptr) && (neighbor != nullptr))
     {
         neighbor->GetLinkInfo().AddRss(aAckFrame->GetRssi());
 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
@@ -1423,7 +1435,7 @@
 #if OPENTHREAD_FTD
         if (aAckFrame->GetVersion() == Frame::kFcfFrameVersion2015)
         {
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
             ProcessCsl(*aAckFrame, dstAddr);
 #endif
         }
@@ -1434,12 +1446,12 @@
 
     mCounters.mTxTotal++;
 
-    if (aError == OT_ERROR_ABORT)
+    if (aError == kErrorAbort)
     {
         mCounters.mTxErrAbort++;
     }
 
-    if (aError == OT_ERROR_CHANNEL_ACCESS_FAILURE)
+    if (aError == kErrorChannelAccessFailure)
     {
         mCounters.mTxErrBusyChannel++;
     }
@@ -1448,7 +1460,7 @@
     {
         mCounters.mTxAckRequested++;
 
-        if (aError == OT_ERROR_NONE)
+        if (aError == kErrorNone)
         {
             mCounters.mTxAcked++;
         }
@@ -1471,7 +1483,7 @@
     return;
 }
 
-void Mac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, otError aError)
+void Mac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aError)
 {
 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
     if (!aFrame.IsEmpty()
@@ -1509,10 +1521,10 @@
 
 #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
         // Verify Enh-ACK integrity by checking its MIC
-        if ((aError == OT_ERROR_NONE) && (aAckFrame != nullptr) &&
-            (ProcessEnhAckSecurity(aFrame, *aAckFrame) != OT_ERROR_NONE))
+        if ((aError == kErrorNone) && (aAckFrame != nullptr) &&
+            (ProcessEnhAckSecurity(aFrame, *aAckFrame) != kErrorNone))
         {
-            aError = OT_ERROR_NO_ACK;
+            aError = kErrorNoAck;
         }
 #endif
     }
@@ -1531,10 +1543,10 @@
             // If the "required radio type set" is empty, successful
             // tx over any radio link is sufficient for overall tx to
             // be considered successful. In this case `mTxError`
-            // starts as `OT_ERROR_ABORT` and we update it only when
-            // it is not already `OT_ERROR_NONE`.
+            // starts as `kErrorAbort` and we update it only when
+            // it is not already `kErrorNone`.
 
-            if (mTxError != OT_ERROR_NONE)
+            if (mTxError != kErrorNone)
             {
                 mTxError = aError;
             }
@@ -1544,13 +1556,13 @@
             // When the "required radio type set" is not empty we
             // expect the successful frame tx on all links in this set
             // to consider the overall tx successful. In this case,
-            // `mTxError` starts as `OT_ERROR_NONE` and we update it
+            // `mTxError` starts as `kErrorNone` and we update it
             // if tx over any link in the set fails.
 
-            if (requriedRadios.Contains(radio) && (aError != OT_ERROR_NONE))
+            if (requriedRadios.Contains(radio) && (aError != kErrorNone))
             {
                 otLogDebgMac("Frame tx failed on required radio link %s with error %s", RadioTypeToString(radio),
-                             otThreadErrorToString(aError));
+                             ErrorToString(aError));
 
                 mTxError = aError;
             }
@@ -1584,7 +1596,7 @@
     case kOperationTransmitPoll:
         OT_ASSERT(aFrame.IsEmpty() || aFrame.GetAckRequest());
 
-        if ((aError == OT_ERROR_NONE) && (aAckFrame != nullptr))
+        if ((aError == kErrorNone) && (aAckFrame != nullptr))
         {
             bool framePending = aAckFrame->GetFramePending();
 
@@ -1605,7 +1617,7 @@
     case kOperationTransmitDataDirect:
         mCounters.mTxData++;
 
-        if (aError != OT_ERROR_NONE)
+        if (aError != kErrorNone)
         {
             mCounters.mTxDirectMaxRetryExpiry++;
         }
@@ -1620,7 +1632,7 @@
         FinishOperation();
         Get<MeshForwarder>().HandleSentFrame(aFrame, aError);
 #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
-        if (aError == OT_ERROR_NONE && Get<Mle::Mle>().GetParent().IsEnhancedKeepAliveSupported() &&
+        if (aError == kErrorNone && Get<Mle::Mle>().GetParent().IsEnhancedKeepAliveSupported() &&
             aFrame.GetSecurityEnabled() && aAckFrame != nullptr)
         {
             Get<DataPollSender>().ProcessFrame(*aAckFrame);
@@ -1630,7 +1642,7 @@
         break;
 
 #if OPENTHREAD_FTD
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     case kOperationTransmitDataCsl:
         mCounters.mTxData++;
 
@@ -1644,7 +1656,7 @@
     case kOperationTransmitDataIndirect:
         mCounters.mTxData++;
 
-        if (aError != OT_ERROR_NONE)
+        if (aError != kErrorNone)
         {
             mCounters.mTxIndirectMaxRetryExpiry++;
         }
@@ -1681,7 +1693,7 @@
 
 void Mac::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<Mac>().HandleTimer();
+    aTimer.Get<Mac>().HandleTimer();
 }
 
 void Mac::HandleTimer(void)
@@ -1711,7 +1723,7 @@
             }
 #endif
         }
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
         else if (mPendingTransmitDataCsl)
         {
             PerformNextOperation();
@@ -1725,10 +1737,10 @@
     }
 }
 
-otError Mac::ProcessReceiveSecurity(RxFrame &aFrame, const Address &aSrcAddr, Neighbor *aNeighbor)
+Error Mac::ProcessReceiveSecurity(RxFrame &aFrame, const Address &aSrcAddr, Neighbor *aNeighbor)
 {
     KeyManager &      keyManager = Get<KeyManager>();
-    otError           error      = OT_ERROR_SECURITY;
+    Error             error      = kErrorSecurity;
     uint8_t           securityLevel;
     uint8_t           keyIdMode;
     uint32_t          frameCounter;
@@ -1737,7 +1749,7 @@
     const Key *       macKey;
     const ExtAddress *extAddress;
 
-    VerifyOrExit(aFrame.GetSecurityEnabled(), error = OT_ERROR_NONE);
+    VerifyOrExit(aFrame.GetSecurityEnabled(), error = kErrorNone);
 
     IgnoreError(aFrame.GetSecurityLevel(securityLevel));
     VerifyOrExit(securityLevel == Frame::kSecEncMic32);
@@ -1800,7 +1812,7 @@
 #endif
 
                 // If frame counter is one off, then frame is a duplicate.
-                VerifyOrExit((frameCounter + 1) != neighborFrameCounter, error = OT_ERROR_DUPLICATED);
+                VerifyOrExit((frameCounter + 1) != neighborFrameCounter, error = kErrorDuplicated);
 
                 VerifyOrExit(frameCounter >= neighborFrameCounter);
             }
@@ -1854,16 +1866,16 @@
         }
     }
 
-    error = OT_ERROR_NONE;
+    error = kErrorNone;
 
 exit:
     return error;
 }
 
 #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
-otError Mac::ProcessEnhAckSecurity(TxFrame &aTxFrame, RxFrame &aAckFrame)
+Error Mac::ProcessEnhAckSecurity(TxFrame &aTxFrame, RxFrame &aAckFrame)
 {
-    otError     error = OT_ERROR_SECURITY;
+    Error       error = kErrorSecurity;
     uint8_t     securityLevel;
     uint8_t     txKeyId;
     uint8_t     ackKeyId;
@@ -1875,14 +1887,14 @@
     KeyManager &keyManager = Get<KeyManager>();
     const Key * macKey;
 
-    VerifyOrExit(aAckFrame.GetSecurityEnabled(), error = OT_ERROR_NONE);
+    VerifyOrExit(aAckFrame.GetSecurityEnabled(), error = kErrorNone);
     VerifyOrExit(aAckFrame.IsVersion2015());
 
     IgnoreError(aAckFrame.GetSecurityLevel(securityLevel));
     VerifyOrExit(securityLevel == Frame::kSecEncMic32);
 
     IgnoreError(aAckFrame.GetKeyIdMode(keyIdMode));
-    VerifyOrExit(keyIdMode == Frame::kKeyIdMode1, error = OT_ERROR_NONE);
+    VerifyOrExit(keyIdMode == Frame::kKeyIdMode1, error = kErrorNone);
 
     IgnoreError(aTxFrame.GetKeyId(txKeyId));
     IgnoreError(aAckFrame.GetKeyId(ackKeyId));
@@ -1949,7 +1961,7 @@
     }
 
 exit:
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         otLogInfoMac("Frame tx attempt failed, error: Enh-ACK security check fail");
     }
@@ -1958,19 +1970,19 @@
 }
 #endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
 
-void Mac::HandleReceivedFrame(RxFrame *aFrame, otError aError)
+void Mac::HandleReceivedFrame(RxFrame *aFrame, Error aError)
 {
     Address   srcaddr;
     Address   dstaddr;
     PanId     panid;
     Neighbor *neighbor;
-    otError   error = aError;
+    Error     error = aError;
 
     mCounters.mRxTotal++;
 
     SuccessOrExit(error);
-    VerifyOrExit(aFrame != nullptr, error = OT_ERROR_NO_FRAME_RECEIVED);
-    VerifyOrExit(IsEnabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(aFrame != nullptr, error = kErrorNoFrameReceived);
+    VerifyOrExit(IsEnabled(), error = kErrorInvalidState);
 
     // Ensure we have a valid frame before attempting to read any contents of
     // the buffer received from the radio.
@@ -1988,7 +2000,7 @@
 
     case Address::kTypeShort:
         VerifyOrExit((mRxOnWhenIdle && dstaddr.IsBroadcast()) || dstaddr.GetShort() == GetShortAddress(),
-                     error = OT_ERROR_DESTINATION_ADDRESS_FILTERED);
+                     error = kErrorDestinationAddressFiltered);
 
 #if OPENTHREAD_FTD
         // Allow multicasts from neighbor routers if FTD
@@ -2001,14 +2013,14 @@
         break;
 
     case Address::kTypeExtended:
-        VerifyOrExit(dstaddr.GetExtended() == GetExtAddress(), error = OT_ERROR_DESTINATION_ADDRESS_FILTERED);
+        VerifyOrExit(dstaddr.GetExtended() == GetExtAddress(), error = kErrorDestinationAddressFiltered);
         break;
     }
 
     // Verify destination PAN ID if present
-    if (OT_ERROR_NONE == aFrame->GetDstPanId(panid))
+    if (kErrorNone == aFrame->GetDstPanId(panid))
     {
-        VerifyOrExit(panid == kShortAddrBroadcast || panid == mPanId, error = OT_ERROR_DESTINATION_ADDRESS_FILTERED);
+        VerifyOrExit(panid == kShortAddrBroadcast || panid == mPanId, error = kErrorDestinationAddressFiltered);
     }
 
     // Source Address Filtering
@@ -2020,16 +2032,16 @@
     case Address::kTypeShort:
         otLogDebgMac("Received frame from short address 0x%04x", srcaddr.GetShort());
 
-        VerifyOrExit(neighbor != nullptr, error = OT_ERROR_UNKNOWN_NEIGHBOR);
+        VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor);
 
         srcaddr.SetExtended(neighbor->GetExtAddress());
 
-        // Fall through
+        OT_FALL_THROUGH;
 
     case Address::kTypeExtended:
 
         // Duplicate Address Protection
-        VerifyOrExit(srcaddr.GetExtended() != GetExtAddress(), error = OT_ERROR_INVALID_SOURCE_ADDRESS);
+        VerifyOrExit(srcaddr.GetExtended() != GetExtAddress(), error = kErrorInvalidSourceAddress);
 
 #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
         {
@@ -2067,7 +2079,7 @@
 
     switch (error)
     {
-    case OT_ERROR_DUPLICATED:
+    case kErrorDuplicated:
 
         // Allow a duplicate received frame pass, only if the
         // current operation is `kOperationWaitingForData` (i.e.,
@@ -2084,16 +2096,16 @@
 
         VerifyOrExit(mOperation == kOperationWaitingForData);
 
-        // Fall through
+        OT_FALL_THROUGH;
 
-    case OT_ERROR_NONE:
+    case kErrorNone:
         break;
 
     default:
         ExitNow();
     }
 
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     if (aFrame->GetVersion() == Frame::kFcfFrameVersion2015)
     {
         ProcessCsl(*aFrame, srcaddr);
@@ -2126,11 +2138,11 @@
                 case Neighbor::kStateChildUpdateRequest:
 
                     // Only accept a "MAC Data Request" frame from a child being restored.
-                    VerifyOrExit(aFrame->IsDataRequestCommand(), error = OT_ERROR_DROP);
+                    VerifyOrExit(aFrame->IsDataRequestCommand(), error = kErrorDrop);
                     break;
 
                 default:
-                    ExitNow(error = OT_ERROR_UNKNOWN_NEIGHBOR);
+                    ExitNow(error = kErrorUnknownNeighbor);
                 }
 
 #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 && OPENTHREAD_FTD
@@ -2160,7 +2172,7 @@
             ExitNow();
         }
 
-        // Fall through
+        OT_FALL_THROUGH;
 
     case kOperationEnergyScan:
 
@@ -2201,7 +2213,7 @@
     case Frame::kFcfFrameMacCmd:
         if (HandleMacCommand(*aFrame)) // returns `true` when handled
         {
-            ExitNow(error = OT_ERROR_NONE);
+            ExitNow(error = kErrorNone);
         }
 
         break;
@@ -2226,41 +2238,41 @@
 
 exit:
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         LogFrameRxFailure(aFrame, error);
 
         switch (error)
         {
-        case OT_ERROR_SECURITY:
+        case kErrorSecurity:
             mCounters.mRxErrSec++;
             break;
 
-        case OT_ERROR_FCS:
+        case kErrorFcs:
             mCounters.mRxErrFcs++;
             break;
 
-        case OT_ERROR_NO_FRAME_RECEIVED:
+        case kErrorNoFrameReceived:
             mCounters.mRxErrNoFrame++;
             break;
 
-        case OT_ERROR_UNKNOWN_NEIGHBOR:
+        case kErrorUnknownNeighbor:
             mCounters.mRxErrUnknownNeighbor++;
             break;
 
-        case OT_ERROR_INVALID_SOURCE_ADDRESS:
+        case kErrorInvalidSourceAddress:
             mCounters.mRxErrInvalidSrcAddr++;
             break;
 
-        case OT_ERROR_ADDRESS_FILTERED:
+        case kErrorAddressFiltered:
             mCounters.mRxAddressFiltered++;
             break;
 
-        case OT_ERROR_DESTINATION_ADDRESS_FILTERED:
+        case kErrorDestinationAddressFiltered:
             mCounters.mRxDestAddrFiltered++;
             break;
 
-        case OT_ERROR_DUPLICATED:
+        case kErrorDuplicated:
             mCounters.mRxDuplicated++;
             break;
 
@@ -2364,67 +2376,50 @@
 
 const char *Mac::OperationToString(Operation aOperation)
 {
-    const char *retval = "";
-
-    switch (aOperation)
-    {
-    case kOperationIdle:
-        retval = "Idle";
-        break;
-
-    case kOperationActiveScan:
-        retval = "ActiveScan";
-        break;
-
-    case kOperationEnergyScan:
-        retval = "EnergyScan";
-        break;
-
-    case kOperationTransmitBeacon:
-        retval = "TransmitBeacon";
-        break;
-
-    case kOperationTransmitDataDirect:
-        retval = "TransmitDataDirect";
-        break;
-
+    static const char *const kOperationStrings[] = {
+        "Idle",               // (0) kOperationIdle
+        "ActiveScan",         // (1) kOperationActiveScan
+        "EnergyScan",         // (2) kOperationEnergyScan
+        "TransmitBeacon",     // (3) kOperationTransmitBeacon
+        "TransmitDataDirect", // (4) kOperationTransmitDataDirect
+        "TransmitPoll",       // (5) kOperationTransmitPoll
+        "WaitingForData",     // (6) kOperationWaitingForData
+        "TransmitOobFrame",   // (7) kOperationTransmitOutOfBandFrame
 #if OPENTHREAD_FTD
-    case kOperationTransmitDataIndirect:
-        retval = "TransmitDataIndirect";
-        break;
+        "TransmitDataIndirect", // (8) kOperationTransmitDataIndirect
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+        "TransmitDataCsl", // (9) kOperationTransmitDataCsl
+#endif
+#endif
+    };
 
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
-    case kOperationTransmitDataCsl:
-        retval = "TransmitDataCsl";
-        break;
+    static_assert(kOperationIdle == 0, "kOperationIdle value is incorrect");
+    static_assert(kOperationActiveScan == 1, "kOperationActiveScan value is incorrect");
+    static_assert(kOperationEnergyScan == 2, "kOperationEnergyScan value is incorrect");
+    static_assert(kOperationTransmitBeacon == 3, "kOperationTransmitBeacon value is incorrect");
+    static_assert(kOperationTransmitDataDirect == 4, "kOperationTransmitDataDirect value is incorrect");
+    static_assert(kOperationTransmitPoll == 5, "kOperationTransmitPoll value is incorrect");
+    static_assert(kOperationWaitingForData == 6, "kOperationWaitingForData value is incorrect");
+    static_assert(kOperationTransmitOutOfBandFrame == 7, "kOperationTransmitOutOfBandFrame value is incorrect");
+#if OPENTHREAD_FTD
+    static_assert(kOperationTransmitDataIndirect == 8, "kOperationTransmitDataIndirect value is incorrect");
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+    static_assert(kOperationTransmitDataCsl == 9, "TransmitDataCsl value is incorrect");
 #endif
 #endif
 
-    case kOperationTransmitPoll:
-        retval = "TransmitPoll";
-        break;
-
-    case kOperationWaitingForData:
-        retval = "WaitingForData";
-        break;
-
-    case kOperationTransmitOutOfBandFrame:
-        retval = "TransmitOobFrame";
-        break;
-    }
-
-    return retval;
+    return kOperationStrings[aOperation];
 }
 
-void Mac::LogFrameRxFailure(const RxFrame *aFrame, otError aError) const
+void Mac::LogFrameRxFailure(const RxFrame *aFrame, Error aError) const
 {
     otLogLevel logLevel;
 
     switch (aError)
     {
-    case OT_ERROR_ABORT:
-    case OT_ERROR_NO_FRAME_RECEIVED:
-    case OT_ERROR_DESTINATION_ADDRESS_FILTERED:
+    case kErrorAbort:
+    case kErrorNoFrameReceived:
+    case kErrorDestinationAddressFiltered:
         logLevel = OT_LOG_LEVEL_DEBG;
         break;
 
@@ -2435,16 +2430,15 @@
 
     if (aFrame == nullptr)
     {
-        otLogMac(logLevel, "Frame rx failed, error:%s", otThreadErrorToString(aError));
+        otLogMac(logLevel, "Frame rx failed, error:%s", ErrorToString(aError));
     }
     else
     {
-        otLogMac(logLevel, "Frame rx failed, error:%s, %s", otThreadErrorToString(aError),
-                 aFrame->ToInfoString().AsCString());
+        otLogMac(logLevel, "Frame rx failed, error:%s, %s", ErrorToString(aError), aFrame->ToInfoString().AsCString());
     }
 }
 
-void Mac::LogFrameTxFailure(const TxFrame &aFrame, otError aError, uint8_t aRetryCount, bool aWillRetx) const
+void Mac::LogFrameTxFailure(const TxFrame &aFrame, Error aError, uint8_t aRetryCount, bool aWillRetx) const
 {
 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE && OPENTHREAD_CONFIG_MULTI_RADIO
     if (aFrame.GetRadioType() == kRadioTypeIeee802154)
@@ -2457,12 +2451,12 @@
         uint8_t maxAttempts = aFrame.GetMaxFrameRetries() + 1;
         uint8_t curAttempt  = aWillRetx ? (aRetryCount + 1) : maxAttempts;
 
-        otLogInfoMac("Frame tx attempt %d/%d failed, error:%s, %s", curAttempt, maxAttempts,
-                     otThreadErrorToString(aError), aFrame.ToInfoString().AsCString());
+        otLogInfoMac("Frame tx attempt %d/%d failed, error:%s, %s", curAttempt, maxAttempts, ErrorToString(aError),
+                     aFrame.ToInfoString().AsCString());
     }
     else
     {
-        otLogInfoMac("Frame tx failed, error:%s, %s", otThreadErrorToString(aError), aFrame.ToInfoString().AsCString());
+        otLogInfoMac("Frame tx failed, error:%s, %s", ErrorToString(aError), aFrame.ToInfoString().AsCString());
     }
 }
 
@@ -2473,7 +2467,7 @@
 
 #else // #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_MAC == 1)
 
-void Mac::LogFrameRxFailure(const RxFrame *, otError) const
+void Mac::LogFrameRxFailure(const RxFrame *, Error) const
 {
 }
 
@@ -2481,7 +2475,7 @@
 {
 }
 
-void Mac::LogFrameTxFailure(const TxFrame &, otError, uint8_t, bool) const
+void Mac::LogFrameTxFailure(const TxFrame &, Error, uint8_t, bool) const
 {
 }
 
@@ -2559,10 +2553,10 @@
 
 #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
 
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
 void Mac::ProcessCsl(const RxFrame &aFrame, const Address &aSrcAddr)
 {
-    const uint8_t *cur   = aFrame.GetHeaderIe(Frame::kHeaderIeCsl);
+    const uint8_t *cur   = aFrame.GetHeaderIe(CslIe::kHeaderIeId);
     Child *        child = Get<ChildTable>().FindChild(aSrcAddr, Child::kInStateAnyExceptInvalid);
     const CslIe *  csl;
 
@@ -2585,7 +2579,7 @@
 exit:
     return;
 }
-#endif // !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
 
 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
 void Mac::ProcessEnhAckProbing(const RxFrame &aFrame, const Neighbor &aNeighbor)
@@ -2612,109 +2606,5 @@
 }
 #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
 
-#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
-otError Mac::AppendHeaderIe(bool aIsTimeSync, TxFrame &aFrame) const
-{
-    OT_UNUSED_VARIABLE(aIsTimeSync);
-
-    const size_t kMaxNumHeaderIe = 3; // TimeSync + CSL + Termination2
-    HeaderIe     ieList[kMaxNumHeaderIe];
-    otError      error   = OT_ERROR_NONE;
-    uint8_t      ieCount = 0;
-
-    VerifyOrExit(aFrame.IsVersion2015() && aFrame.IsIePresent());
-
-#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
-    if (aIsTimeSync)
-    {
-        ieList[ieCount].Init();
-        ieList[ieCount].SetId(Frame::kHeaderIeVendor);
-        ieList[ieCount].SetLength(sizeof(TimeIe));
-        ieCount++;
-    }
-#endif
-
-#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
-    if (IsCslEnabled())
-    {
-        OT_ASSERT(aFrame.GetSecurityEnabled());
-
-        aFrame.mInfo.mTxInfo.mCslPresent = true;
-        ieList[ieCount].Init();
-        ieList[ieCount].SetId(Frame::kHeaderIeCsl);
-        ieList[ieCount].SetLength(sizeof(CslIe));
-        ieCount++;
-    }
-    else
-#endif
-    {
-        aFrame.mInfo.mTxInfo.mCslPresent = false;
-    }
-
-    if (ieCount > 0)
-    {
-        ieList[ieCount].Init();
-        ieList[ieCount].SetId(Frame::kHeaderIeTermination2);
-        ieList[ieCount].SetLength(0);
-
-        SuccessOrExit(error = aFrame.AppendHeaderIe(ieList, ++ieCount));
-    }
-
-#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
-    if (aIsTimeSync)
-    {
-        uint8_t *cur = aFrame.GetHeaderIe(Frame::kHeaderIeVendor);
-        TimeIe * ie  = reinterpret_cast<TimeIe *>(cur + sizeof(HeaderIe));
-
-        ie->Init();
-    }
-#endif
-
-exit:
-    return error;
-}
-#endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
-
-void Mac::UpdateFrameControlField(const Neighbor *aNeighbor, bool aIsTimeSync, uint16_t &aFcf) const
-{
-    OT_UNUSED_VARIABLE(aIsTimeSync);
-    OT_UNUSED_VARIABLE(aNeighbor);
-
-#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
-#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
-    if (aIsTimeSync)
-    {
-        aFcf |= Frame::kFcfFrameVersion2015 | Frame::kFcfIePresent;
-    }
-    else
-#endif
-#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
-        if (IsCslEnabled())
-    {
-        aFcf |= Frame::kFcfFrameVersion2015 | Frame::kFcfIePresent;
-    }
-    else
-#endif
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
-        if (aNeighbor != nullptr && !Mle::MleRouter::IsActiveRouter(aNeighbor->GetRloc16()) &&
-            static_cast<const Child *>(aNeighbor)->IsCslSynchronized())
-    {
-        aFcf |= Frame::kFcfFrameVersion2015;
-    }
-    else
-#endif
-#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
-        if (aNeighbor != nullptr && aNeighbor->IsEnhAckProbingActive())
-    {
-        aFcf |= Frame::kFcfFrameVersion2015; ///< Set version to 2015 to fetch Link Metrics data in Enh-ACK.
-    }
-    else
-#endif
-#endif
-    {
-        aFcf |= Frame::kFcfFrameVersion2006;
-    }
-}
-
 } // namespace Mac
 } // namespace ot
diff --git a/src/core/mac/mac.hpp b/src/core/mac/mac.hpp
index caa413b..b5a6207 100644
--- a/src/core/mac/mac.hpp
+++ b/src/core/mac/mac.hpp
@@ -79,7 +79,7 @@
     kDataPollTimeout = 100, ///< Timeout for receiving Data Frame (milliseconds).
     kSleepDelay      = 300, ///< Max sleep delay when frame is pending (milliseconds).
 
-    kScanDurationDefault = 300, ///< Default interval between channels (milliseconds).
+    kScanDurationDefault = OPENTHREAD_CONFIG_MAC_SCAN_DURATION, ///< Default interval between channels (milliseconds).
 
     kMaxCsmaBackoffsDirect =
         OPENTHREAD_CONFIG_MAC_MAX_CSMA_BACKOFFS_DIRECT, ///< macMaxCsmaBackoffs for direct transmissions
@@ -147,11 +147,11 @@
      * @param[in]  aHandler       A pointer to a function that is called on receiving an IEEE 802.15.4 Beacon.
      * @param[in]  aContext       A pointer to an arbitrary context (used when invoking `aHandler` callback).
      *
-     * @retval OT_ERROR_NONE  Successfully scheduled the Active Scan request.
-     * @retval OT_ERROR_BUSY  Could not schedule the scan (a scan is ongoing or scheduled).
+     * @retval kErrorNone  Successfully scheduled the Active Scan request.
+     * @retval kErrorBusy  Could not schedule the scan (a scan is ongoing or scheduled).
      *
      */
-    otError ActiveScan(uint32_t aScanChannels, uint16_t aScanDuration, ActiveScanHandler aHandler, void *aContext);
+    Error ActiveScan(uint32_t aScanChannels, uint16_t aScanDuration, ActiveScanHandler aHandler, void *aContext);
 
     /**
      * This method starts an IEEE 802.15.4 Energy Scan.
@@ -162,11 +162,11 @@
      * @param[in]  aHandler          A pointer to a function called to pass on scan result or indicate scan completion.
      * @param[in]  aContext          A pointer to an arbitrary context (used when invoking @p aHandler callback).
      *
-     * @retval OT_ERROR_NONE  Accepted the Energy Scan request.
-     * @retval OT_ERROR_BUSY  Could not start the energy scan.
+     * @retval kErrorNone  Accepted the Energy Scan request.
+     * @retval kErrorBusy  Could not start the energy scan.
      *
      */
-    otError EnergyScan(uint32_t aScanChannels, uint16_t aScanDuration, EnergyScanHandler aHandler, void *aContext);
+    Error EnergyScan(uint32_t aScanChannels, uint16_t aScanDuration, EnergyScanHandler aHandler, void *aContext);
 
     /**
      * This method indicates the energy scan for the current channel is complete.
@@ -221,7 +221,7 @@
      */
     void RequestIndirectFrameTransmission(void);
 
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     /**
      * This method requests `Mac` to start a CSL tx operation after a delay of @p aDelay time.
      *
@@ -240,23 +240,23 @@
      *
      * @param[in]  aOobFrame  A pointer to the frame.
      *
-     * @retval OT_ERROR_NONE           Successfully scheduled the frame transmission.
-     * @retval OT_ERROR_ALREADY        MAC layer is busy sending a previously requested frame.
-     * @retval OT_ERROR_INVALID_STATE  The MAC layer is not enabled.
-     * @retval OT_ERROR_INVALID_ARGS   The argument @p aOobFrame is nullptr.
+     * @retval kErrorNone          Successfully scheduled the frame transmission.
+     * @retval kErrorAlready       MAC layer is busy sending a previously requested frame.
+     * @retval kErrorInvalidState  The MAC layer is not enabled.
+     * @retval kErrorInvalidArgs   The argument @p aOobFrame is nullptr.
      *
      */
-    otError RequestOutOfBandFrameTransmission(otRadioFrame *aOobFrame);
+    Error RequestOutOfBandFrameTransmission(otRadioFrame *aOobFrame);
 
     /**
      * This method requests transmission of a data poll (MAC Data Request) frame.
      *
-     * @retval OT_ERROR_NONE           Data poll transmission request is scheduled successfully.
-     * @retval OT_ERROR_ALREADY        MAC is busy sending earlier poll transmission request.
-     * @retval OT_ERROR_INVALID_STATE  The MAC layer is not enabled.
+     * @retval kErrorNone          Data poll transmission request is scheduled successfully.
+     * @retval kErrorAlready       MAC is busy sending earlier poll transmission request.
+     * @retval kErrorInvalidState  The MAC layer is not enabled.
      *
      */
-    otError RequestDataPollTransmission(void);
+    Error RequestDataPollTransmission(void);
 
     /**
      * This method returns a reference to the IEEE 802.15.4 Extended Address.
@@ -303,11 +303,11 @@
      *
      * @param[in]  aChannel  The IEEE 802.15.4 PAN Channel.
      *
-     * @retval OT_ERROR_NONE           Successfully set the IEEE 802.15.4 PAN Channel.
-     * @retval OT_ERROR_INVALID_ARGS   The @p aChannel is not in the supported channel mask.
+     * @retval kErrorNone          Successfully set the IEEE 802.15.4 PAN Channel.
+     * @retval kErrorInvalidArgs   The @p aChannel is not in the supported channel mask.
      *
      */
-    otError SetPanChannel(uint8_t aChannel);
+    Error SetPanChannel(uint8_t aChannel);
 
     /**
      * This method sets the temporary IEEE 802.15.4 radio channel.
@@ -319,11 +319,11 @@
      *
      * @param[in]  aChannel            A IEEE 802.15.4 channel.
      *
-     * @retval OT_ERROR_NONE           Successfully set the temporary channel
-     * @retval OT_ERROR_INVALID_ARGS   The @p aChannel is not in the supported channel mask.
+     * @retval kErrorNone          Successfully set the temporary channel
+     * @retval kErrorInvalidArgs   The @p aChannel is not in the supported channel mask.
      *
      */
-    otError SetTemporaryChannel(uint8_t aChannel);
+    Error SetTemporaryChannel(uint8_t aChannel);
 
     /**
      * This method clears the use of a previously set temporary channel and adopts the PAN channel.
@@ -360,22 +360,22 @@
      *
      * @param[in]  aNameString   A pointer to a string character array. Must be null terminated.
      *
-     * @retval OT_ERROR_NONE           Successfully set the IEEE 802.15.4 Network Name.
-     * @retval OT_ERROR_INVALID_ARGS   Given name is too long.
+     * @retval kErrorNone          Successfully set the IEEE 802.15.4 Network Name.
+     * @retval kErrorInvalidArgs   Given name is too long.
      *
      */
-    otError SetNetworkName(const char *aNameString);
+    Error SetNetworkName(const char *aNameString);
 
     /**
      * This method sets the IEEE 802.15.4 Network Name.
      *
      * @param[in]  aNameData     A name data (pointer to char buffer and length).
      *
-     * @retval OT_ERROR_NONE           Successfully set the IEEE 802.15.4 Network Name.
-     * @retval OT_ERROR_INVALID_ARGS   Given name is too long.
+     * @retval kErrorNone          Successfully set the IEEE 802.15.4 Network Name.
+     * @retval kErrorInvalidArgs   Given name is too long.
      *
      */
-    otError SetNetworkName(const NameData &aNameData);
+    Error SetNetworkName(const NameData &aNameData);
 
 #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
     /**
@@ -391,22 +391,22 @@
      *
      * @param[in]  aNameString   A pointer to a string character array. Must be null terminated.
      *
-     * @retval OT_ERROR_NONE           Successfully set the Thread Domain Name.
-     * @retval OT_ERROR_INVALID_ARGS   Given name is too long.
+     * @retval kErrorNone          Successfully set the Thread Domain Name.
+     * @retval kErrorInvalidArgs   Given name is too long.
      *
      */
-    otError SetDomainName(const char *aNameString);
+    Error SetDomainName(const char *aNameString);
 
     /**
      * This method sets the Thread Domain Name.
      *
      * @param[in]  aNameData     A name data (pointer to char buffer and length).
      *
-     * @retval OT_ERROR_NONE           Successfully set the Thread Domain Name.
-     * @retval OT_ERROR_INVALID_ARGS   Given name is too long.
+     * @retval kErrorNone          Successfully set the Thread Domain Name.
+     * @retval kErrorInvalidArgs   Given name is too long.
      *
      */
-    otError SetDomainName(const NameData &aNameData);
+    Error SetDomainName(const NameData &aNameData);
 #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
 
     /**
@@ -482,11 +482,11 @@
      * This method is called to handle a received frame.
      *
      * @param[in]  aFrame  A pointer to the received frame, or nullptr if the receive operation was aborted.
-     * @param[in]  aError  OT_ERROR_NONE when successfully received a frame,
-     *                     OT_ERROR_ABORT when reception was aborted and a frame was not received.
+     * @param[in]  aError  kErrorNone when successfully received a frame,
+     *                     kErrorAbort when reception was aborted and a frame was not received.
      *
      */
-    void HandleReceivedFrame(RxFrame *aFrame, otError aError);
+    void HandleReceivedFrame(RxFrame *aFrame, Error aError);
 
     /**
      * This method records CCA status (success/failure) for a frame transmission attempt.
@@ -505,10 +505,10 @@
      *
      * @param[in] aFrame      The transmitted frame.
      * @param[in] aAckFrame   A pointer to the ACK frame, or nullptr if no ACK was received.
-     * @param[in] aError      OT_ERROR_NONE when the frame was transmitted successfully,
-     *                        OT_ERROR_NO_ACK when the frame was transmitted but no ACK was received,
-     *                        OT_ERROR_CHANNEL_ACCESS_FAILURE tx failed due to activity on the channel,
-     *                        OT_ERROR_ABORT when transmission was aborted for other reasons.
+     * @param[in] aError      kErrorNone when the frame was transmitted successfully,
+     *                        kErrorNoAck when the frame was transmitted but no ACK was received,
+     *                        kErrorChannelAccessFailure tx failed due to activity on the channel,
+     *                        kErrorAbort when transmission was aborted for other reasons.
      * @param[in] aRetryCount Indicates number of transmission retries for this frame.
      * @param[in] aWillRetx   Indicates whether frame will be retransmitted or not. This is applicable only
      *                        when there was an error in transmission (i.e., `aError` is not NONE).
@@ -516,7 +516,7 @@
      */
     void RecordFrameTransmitStatus(const TxFrame &aFrame,
                                    const RxFrame *aAckFrame,
-                                   otError        aError,
+                                   Error          aError,
                                    uint8_t        aRetryCount,
                                    bool           aWillRetx);
 
@@ -525,13 +525,13 @@
      *
      * @param[in]  aFrame      The frame that was transmitted.
      * @param[in]  aAckFrame   A pointer to the ACK frame, nullptr if no ACK was received.
-     * @param[in]  aError      OT_ERROR_NONE when the frame was transmitted successfully,
-     *                         OT_ERROR_NO_ACK when the frame was transmitted but no ACK was received,
-     *                         OT_ERROR_CHANNEL_ACCESS_FAILURE when the tx failed due to activity on the channel,
-     *                         OT_ERROR_ABORT when transmission was aborted for other reasons.
+     * @param[in]  aError      kErrorNone when the frame was transmitted successfully,
+     *                         kErrorNoAck when the frame was transmitted but no ACK was received,
+     *                         kErrorChannelAccessFailure when the tx failed due to activity on the channel,
+     *                         kErrorAbort when transmission was aborted for other reasons.
      *
      */
-    void HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, otError aError);
+    void HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aError);
 
     /**
      * This method returns if an active scan is in progress.
@@ -746,35 +746,6 @@
 
 #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
 
-#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
-    /**
-     * This method appends header IEs to a TX-frame according to its
-     * frame control field and if time sync is enabled.
-     *
-     * @param[in]      aIsTimeSync  A boolean indicates if time sync is being used.
-     * @param[in,out]  aFrame       A reference to the TX-frame to which the IEs will be appended.
-     *
-     * @retval OT_ERROR_NONE       If append header IEs successfully.
-     * @retval OT_ERROR_NOT_FOUND  If cannot find header IE position in the frame.
-     *
-     */
-    otError AppendHeaderIe(bool aIsTimeSync, TxFrame &aFrame) const;
-#endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
-
-    /**
-     * This method updates frame control field.
-     *
-     * If the frame would contain header IEs, IE present field would be set.
-     * If this is a CSL transmission frame or header IE is present in this frame,
-     * the version should be set to 2015. Otherwise, the version would be set to 2006.
-     *
-     * @param[in]   aNeighbor    A pointer to the destination device, could be `nullptr`.
-     * @param[in]   aIsTimeSync  A boolean indicates if time sync is being used.
-     * @param[out]  aFcf         A reference to the frame control field to set.
-     *
-     */
-    void UpdateFrameControlField(const Neighbor *aNeighbor, bool aIsTimeSync, uint16_t &aFcf) const;
-
 private:
     enum
     {
@@ -783,22 +754,22 @@
         kMaxAcquisitionId  = 0xffff,
     };
 
-    enum Operation
+    enum Operation : uint8_t
     {
         kOperationIdle = 0,
         kOperationActiveScan,
         kOperationEnergyScan,
         kOperationTransmitBeacon,
         kOperationTransmitDataDirect,
-#if OPENTHREAD_FTD
-        kOperationTransmitDataIndirect,
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
-        kOperationTransmitDataCsl,
-#endif
-#endif
         kOperationTransmitPoll,
         kOperationWaitingForData,
         kOperationTransmitOutOfBandFrame,
+#if OPENTHREAD_FTD
+        kOperationTransmitDataIndirect,
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+        kOperationTransmitDataCsl,
+#endif
+#endif
     };
 
 #if OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_ENABLE
@@ -824,10 +795,10 @@
     };
 #endif // OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_ENABLE
 
-    otError ProcessReceiveSecurity(RxFrame &aFrame, const Address &aSrcAddr, Neighbor *aNeighbor);
-    void    ProcessTransmitSecurity(TxFrame &aFrame);
+    Error ProcessReceiveSecurity(RxFrame &aFrame, const Address &aSrcAddr, Neighbor *aNeighbor);
+    void  ProcessTransmitSecurity(TxFrame &aFrame);
 #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
-    otError ProcessEnhAckSecurity(TxFrame &aTxFrame, RxFrame &aAckFrame);
+    Error ProcessEnhAckSecurity(TxFrame &aTxFrame, RxFrame &aAckFrame);
 #endif
 
     void     UpdateIdleMode(void);
@@ -846,23 +817,23 @@
     void        HandleTimer(void);
     static void HandleOperationTask(Tasklet &aTasklet);
 
-    void    Scan(Operation aScanOperation, uint32_t aScanChannels, uint16_t aScanDuration);
-    otError UpdateScanChannel(void);
-    void    PerformActiveScan(void);
-    void    ReportActiveScanResult(const RxFrame *aBeaconFrame);
-    otError ConvertBeaconToActiveScanResult(const RxFrame *aBeaconFrame, ActiveScanResult &aResult);
-    void    PerformEnergyScan(void);
-    void    ReportEnergyScanResult(int8_t aRssi);
+    void  Scan(Operation aScanOperation, uint32_t aScanChannels, uint16_t aScanDuration);
+    Error UpdateScanChannel(void);
+    void  PerformActiveScan(void);
+    void  ReportActiveScanResult(const RxFrame *aBeaconFrame);
+    Error ConvertBeaconToActiveScanResult(const RxFrame *aBeaconFrame, ActiveScanResult &aResult);
+    void  PerformEnergyScan(void);
+    void  ReportEnergyScanResult(int8_t aRssi);
 
-    void LogFrameRxFailure(const RxFrame *aFrame, otError aError) const;
-    void LogFrameTxFailure(const TxFrame &aFrame, otError aError, uint8_t aRetryCount, bool aWillRetx) const;
+    void LogFrameRxFailure(const RxFrame *aFrame, Error aError) const;
+    void LogFrameTxFailure(const TxFrame &aFrame, Error aError, uint8_t aRetryCount, bool aWillRetx) const;
     void LogBeacon(const char *aActionText, const BeaconPayload &aBeaconPayload) const;
 
 #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
     uint8_t GetTimeIeOffset(const Frame &aFrame);
 #endif
 
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     void ProcessCsl(const RxFrame &aFrame, const Address &aSrcAddr);
 #endif
 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
@@ -883,10 +854,10 @@
     bool mPendingTransmitDataDirect : 1;
 #if OPENTHREAD_FTD
     bool mPendingTransmitDataIndirect : 1;
-#endif
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     bool mPendingTransmitDataCsl : 1;
 #endif
+#endif
     bool mPendingTransmitPoll : 1;
     bool mPendingTransmitOobFrame : 1;
     bool mPendingWaitingForData : 1;
@@ -919,10 +890,10 @@
     uint8_t     mMaxFrameRetriesDirect;
 #if OPENTHREAD_FTD
     uint8_t mMaxFrameRetriesIndirect;
-#endif
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     TimeMilli mCslTxFireTime;
 #endif
+#endif
 
     union
     {
@@ -946,7 +917,7 @@
 
 #if OPENTHREAD_CONFIG_MULTI_RADIO
     RadioTypes mTxPendingRadioLinks;
-    otError    mTxError;
+    Error      mTxError;
 #endif
 
 #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
diff --git a/src/core/mac/mac_filter.cpp b/src/core/mac/mac_filter.cpp
index f4f6f9b..568c947 100644
--- a/src/core/mac/mac_filter.cpp
+++ b/src/core/mac/mac_filter.cpp
@@ -83,14 +83,14 @@
     return rval;
 }
 
-otError Filter::AddAddress(const ExtAddress &aExtAddress)
+Error Filter::AddAddress(const ExtAddress &aExtAddress)
 {
-    otError      error = OT_ERROR_NONE;
+    Error        error = kErrorNone;
     FilterEntry *entry = FindEntry(aExtAddress);
 
     if (entry == nullptr)
     {
-        VerifyOrExit((entry = FindAvailableEntry()) != nullptr, error = OT_ERROR_NO_BUFS);
+        VerifyOrExit((entry = FindAvailableEntry()) != nullptr, error = kErrorNoBufs);
         entry->mExtAddress = aExtAddress;
     }
 
@@ -118,9 +118,9 @@
     }
 }
 
-otError Filter::GetNextAddress(Iterator &aIterator, Entry &aEntry) const
+Error Filter::GetNextAddress(Iterator &aIterator, Entry &aEntry) const
 {
-    otError error = OT_ERROR_NOT_FOUND;
+    Error error = kErrorNotFound;
 
     for (; aIterator < OT_ARRAY_LENGTH(mFilterEntries); aIterator++)
     {
@@ -130,7 +130,7 @@
         {
             aEntry.mExtAddress = entry.mExtAddress;
             aEntry.mRssIn      = entry.mRssIn;
-            error              = OT_ERROR_NONE;
+            error              = kErrorNone;
             aIterator++;
             break;
         }
@@ -139,15 +139,15 @@
     return error;
 }
 
-otError Filter::AddRssIn(const ExtAddress &aExtAddress, int8_t aRss)
+Error Filter::AddRssIn(const ExtAddress &aExtAddress, int8_t aRss)
 {
-    otError      error = OT_ERROR_NONE;
+    Error        error = kErrorNone;
     FilterEntry *entry = FindEntry(aExtAddress);
 
     if (entry == nullptr)
     {
         entry = FindAvailableEntry();
-        VerifyOrExit(entry != nullptr, error = OT_ERROR_NO_BUFS);
+        VerifyOrExit(entry != nullptr, error = kErrorNoBufs);
 
         entry->mExtAddress = aExtAddress;
     }
@@ -180,9 +180,9 @@
     mDefaultRssIn = kFixedRssDisabled;
 }
 
-otError Filter::GetNextRssIn(Iterator &aIterator, Entry &aEntry)
+Error Filter::GetNextRssIn(Iterator &aIterator, Entry &aEntry)
 {
-    otError error = OT_ERROR_NOT_FOUND;
+    Error error = kErrorNotFound;
 
     for (; aIterator < OT_ARRAY_LENGTH(mFilterEntries); aIterator++)
     {
@@ -192,7 +192,7 @@
         {
             aEntry.mExtAddress = entry.mExtAddress;
             aEntry.mRssIn      = entry.mRssIn;
-            error              = OT_ERROR_NONE;
+            error              = kErrorNone;
             aIterator++;
             ExitNow();
         }
@@ -203,7 +203,7 @@
     {
         static_cast<ExtAddress &>(aEntry.mExtAddress).Fill(0xff);
         aEntry.mRssIn = mDefaultRssIn;
-        error         = OT_ERROR_NONE;
+        error         = kErrorNone;
         aIterator++;
     }
 
@@ -211,9 +211,9 @@
     return error;
 }
 
-otError Filter::Apply(const ExtAddress &aExtAddress, int8_t &aRss)
+Error Filter::Apply(const ExtAddress &aExtAddress, int8_t &aRss)
 {
-    otError      error = OT_ERROR_NONE;
+    Error        error = kErrorNone;
     FilterEntry *entry = FindEntry(aExtAddress);
     bool         isInFilterList;
 
@@ -231,11 +231,11 @@
         break;
 
     case kModeAllowlist:
-        VerifyOrExit(isInFilterList, error = OT_ERROR_ADDRESS_FILTERED);
+        VerifyOrExit(isInFilterList, error = kErrorAddressFiltered);
         break;
 
     case kModeDenylist:
-        VerifyOrExit(!isInFilterList, error = OT_ERROR_ADDRESS_FILTERED);
+        VerifyOrExit(!isInFilterList, error = kErrorAddressFiltered);
         break;
     }
 
diff --git a/src/core/mac/mac_filter.hpp b/src/core/mac/mac_filter.hpp
index 4a68523..31d2e45 100644
--- a/src/core/mac/mac_filter.hpp
+++ b/src/core/mac/mac_filter.hpp
@@ -117,11 +117,11 @@
      *
      * @param[in]  aExtAddress  A reference to the Extended Address.
      *
-     * @retval OT_ERROR_NONE           Successfully added @p aExtAddress to the filter.
-     * @retval OT_ERROR_NO_BUFS        No available entry exists.
+     * @retval kErrorNone          Successfully added @p aExtAddress to the filter.
+     * @retval kErrorNoBufs        No available entry exists.
      *
      */
-    otError AddAddress(const ExtAddress &aExtAddress);
+    Error AddAddress(const ExtAddress &aExtAddress);
 
     /**
      * This method removes an Extended Address from the filter.
@@ -146,11 +146,11 @@
      *                           To get the first in-use address filter, set it to OT_MAC_FILTER_ITERATOR_INIT.
      * @param[out]     aEntry    A reference to where the information is placed.
      *
-     * @retval OT_ERROR_NONE       Successfully retrieved the next address filter entry.
-     * @retval OT_ERROR_NOT_FOUND  No subsequent entry exists.
+     * @retval kErrorNone      Successfully retrieved the next address filter entry.
+     * @retval kErrorNotFound  No subsequent entry exists.
      *
      */
-    otError GetNextAddress(Iterator &aIterator, Entry &aEntry) const;
+    Error GetNextAddress(Iterator &aIterator, Entry &aEntry) const;
 
     /**
      * This method adds a fixed received signal strength entry for the messages from a given Extended Address.
@@ -158,11 +158,11 @@
      * @param[in]  aExtAddress  An Extended Address
      * @param[in]  aRss         The received signal strength to set.
      *
-     * @retval OT_ERROR_NONE     Successfully set @p aRss for @p aExtAddress.
-     * @retval OT_ERROR_NO_BUFS  No available entry exists.
+     * @retval kErrorNone    Successfully set @p aRss for @p aExtAddress.
+     * @retval kErrorNoBufs  No available entry exists.
      *
      */
-    otError AddRssIn(const ExtAddress &aExtAddress, int8_t aRss);
+    Error AddRssIn(const ExtAddress &aExtAddress, int8_t aRss);
 
     /**
      * This method removes a fixed received signal strength entry for a given Extended Address.
@@ -206,11 +206,11 @@
      *                           Extended Address as all 0xff to indicate the default received signal strength
      *                           if it was set.
      *
-     * @retval OT_ERROR_NONE       Successfully retrieved the next RssIn filter entry.
-     * @retval OT_ERROR_NOT_FOUND  No subsequent entry exists.
+     * @retval kErrorNone      Successfully retrieved the next RssIn filter entry.
+     * @retval kErrorNotFound  No subsequent entry exists.
      *
      */
-    otError GetNextRssIn(Iterator &aIterator, Entry &aEntry);
+    Error GetNextRssIn(Iterator &aIterator, Entry &aEntry);
 
     /**
      * This method applies the filter rules on a given Extended Address.
@@ -218,12 +218,11 @@
      * @param[in]  aExtAddress  A reference to the Extended Address.
      * @param[out] aRss         A reference to where the received signal strength to be placed.
      *
-     * @retval OT_ERROR_NONE                Successfully applied the filter rules on @p aExtAddress.
-     * @retval OT_ERROR_ADDRESS_FILTERED    Address filter (allowlist or denylist) is enabled and @p aExtAddress is
-     *                                      filtered.
+     * @retval kErrorNone             Successfully applied the filter rules on @p aExtAddress.
+     * @retval kErrorAddressFiltered  Address filter (allowlist or denylist) is enabled and @p aExtAddress is filtered.
      *
      */
-    otError Apply(const ExtAddress &aExtAddress, int8_t &aRss);
+    Error Apply(const ExtAddress &aExtAddress, int8_t &aRss);
 
 private:
     enum
diff --git a/src/core/mac/mac_frame.cpp b/src/core/mac/mac_frame.cpp
index bdbb030..134d8e1 100644
--- a/src/core/mac/mac_frame.cpp
+++ b/src/core/mac/mac_frame.cpp
@@ -50,6 +50,13 @@
 using ot::Encoding::LittleEndian::WriteUint16;
 using ot::Encoding::LittleEndian::WriteUint32;
 
+void HeaderIe::Init(uint16_t aId, uint8_t aLen)
+{
+    Init();
+    SetId(aId);
+    SetLength(aLen);
+}
+
 void Frame::InitMacHeader(uint16_t aFcf, uint8_t aSecurityControl)
 {
     mLength = CalculateAddrFieldSize(aFcf);
@@ -79,13 +86,13 @@
     return ReadUint16(mPsdu);
 }
 
-otError Frame::ValidatePsdu(void) const
+Error Frame::ValidatePsdu(void) const
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     uint8_t index = FindPayloadIndex();
 
-    VerifyOrExit(index != kInvalidIndex, error = OT_ERROR_PARSE);
-    VerifyOrExit((index + GetFooterLength()) <= mLength, error = OT_ERROR_PARSE);
+    VerifyOrExit(index != kInvalidIndex, error = kErrorParse);
+    VerifyOrExit((index + GetFooterLength()) <= mLength, error = kErrorParse);
 
 exit:
     return error;
@@ -159,12 +166,12 @@
     return present;
 }
 
-otError Frame::GetDstPanId(PanId &aPanId) const
+Error Frame::GetDstPanId(PanId &aPanId) const
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     uint8_t index = FindDstPanIdIndex();
 
-    VerifyOrExit(index != kInvalidIndex, error = OT_ERROR_PARSE);
+    VerifyOrExit(index != kInvalidIndex, error = kErrorParse);
     aPanId = ReadUint16(&mPsdu[index]);
 
 exit:
@@ -184,12 +191,12 @@
     return kFcfSize + kDsnSize + (IsDstPanIdPresent() ? sizeof(PanId) : 0);
 }
 
-otError Frame::GetDstAddr(Address &aAddress) const
+Error Frame::GetDstAddr(Address &aAddress) const
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     uint8_t index = FindDstAddrIndex();
 
-    VerifyOrExit(index != kInvalidIndex, error = OT_ERROR_PARSE);
+    VerifyOrExit(index != kInvalidIndex, error = kErrorParse);
 
     switch (GetFrameControlField() & kFcfDstAddrMask)
     {
@@ -297,24 +304,24 @@
     return srcPanIdPresent;
 }
 
-otError Frame::GetSrcPanId(PanId &aPanId) const
+Error Frame::GetSrcPanId(PanId &aPanId) const
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     uint8_t index = FindSrcPanIdIndex();
 
-    VerifyOrExit(index != kInvalidIndex, error = OT_ERROR_PARSE);
+    VerifyOrExit(index != kInvalidIndex, error = kErrorParse);
     aPanId = ReadUint16(&mPsdu[index]);
 
 exit:
     return error;
 }
 
-otError Frame::SetSrcPanId(PanId aPanId)
+Error Frame::SetSrcPanId(PanId aPanId)
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     uint8_t index = FindSrcPanIdIndex();
 
-    VerifyOrExit(index != kInvalidIndex, error = OT_ERROR_PARSE);
+    VerifyOrExit(index != kInvalidIndex, error = kErrorParse);
     WriteUint16(aPanId, &mPsdu[index]);
 
 exit:
@@ -352,13 +359,13 @@
     return index;
 }
 
-otError Frame::GetSrcAddr(Address &aAddress) const
+Error Frame::GetSrcAddr(Address &aAddress) const
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     uint8_t  index = FindSrcAddrIndex();
     uint16_t fcf   = GetFrameControlField();
 
-    VerifyOrExit(index != kInvalidIndex, error = OT_ERROR_PARSE);
+    VerifyOrExit(index != kInvalidIndex, error = kErrorParse);
 
     switch (fcf & kFcfSrcAddrMask)
     {
@@ -416,12 +423,12 @@
     }
 }
 
-otError Frame::GetSecurityControlField(uint8_t &aSecurityControlField) const
+Error Frame::GetSecurityControlField(uint8_t &aSecurityControlField) const
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     uint8_t index = FindSecurityHeaderIndex();
 
-    VerifyOrExit(index != kInvalidIndex, error = OT_ERROR_PARSE);
+    VerifyOrExit(index != kInvalidIndex, error = kErrorParse);
 
     aSecurityControlField = mPsdu[index];
 
@@ -450,12 +457,12 @@
     return index;
 }
 
-otError Frame::GetSecurityLevel(uint8_t &aSecurityLevel) const
+Error Frame::GetSecurityLevel(uint8_t &aSecurityLevel) const
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     uint8_t index = FindSecurityHeaderIndex();
 
-    VerifyOrExit(index != kInvalidIndex, error = OT_ERROR_PARSE);
+    VerifyOrExit(index != kInvalidIndex, error = kErrorParse);
 
     aSecurityLevel = mPsdu[index] & kSecLevelMask;
 
@@ -463,12 +470,12 @@
     return error;
 }
 
-otError Frame::GetKeyIdMode(uint8_t &aKeyIdMode) const
+Error Frame::GetKeyIdMode(uint8_t &aKeyIdMode) const
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     uint8_t index = FindSecurityHeaderIndex();
 
-    VerifyOrExit(index != kInvalidIndex, error = OT_ERROR_PARSE);
+    VerifyOrExit(index != kInvalidIndex, error = kErrorParse);
 
     aKeyIdMode = mPsdu[index] & kKeyIdModeMask;
 
@@ -476,12 +483,12 @@
     return error;
 }
 
-otError Frame::GetFrameCounter(uint32_t &aFrameCounter) const
+Error Frame::GetFrameCounter(uint32_t &aFrameCounter) const
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     uint8_t index = FindSecurityHeaderIndex();
 
-    VerifyOrExit(index != kInvalidIndex, error = OT_ERROR_PARSE);
+    VerifyOrExit(index != kInvalidIndex, error = kErrorParse);
 
     // Security Control
     index += kSecurityControlSize;
@@ -551,13 +558,13 @@
     memcpy(&mPsdu[index + kSecurityControlSize + kFrameCounterSize], aKeySource, keySourceLength);
 }
 
-otError Frame::GetKeyId(uint8_t &aKeyId) const
+Error Frame::GetKeyId(uint8_t &aKeyId) const
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     uint8_t keySourceLength;
     uint8_t index = FindSecurityHeaderIndex();
 
-    VerifyOrExit(index != kInvalidIndex);
+    VerifyOrExit(index != kInvalidIndex, error = kErrorParse);
 
     keySourceLength = GetKeySourceLength(mPsdu[index] & kKeyIdModeMask);
 
@@ -579,11 +586,12 @@
     mPsdu[index + kSecurityControlSize + kFrameCounterSize + keySourceLength] = aKeyId;
 }
 
-otError Frame::GetCommandId(uint8_t &aCommandId) const
+Error Frame::GetCommandId(uint8_t &aCommandId) const
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     uint8_t index = FindPayloadIndex();
-    VerifyOrExit(index != kInvalidIndex, error = OT_ERROR_PARSE);
+
+    VerifyOrExit(index != kInvalidIndex, error = kErrorParse);
 
     aCommandId = mPsdu[IsVersion2015() ? index : (index - 1)];
 
@@ -591,12 +599,12 @@
     return error;
 }
 
-otError Frame::SetCommandId(uint8_t aCommandId)
+Error Frame::SetCommandId(uint8_t aCommandId)
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     uint8_t index = FindPayloadIndex();
 
-    VerifyOrExit(index != kInvalidIndex, error = OT_ERROR_PARSE);
+    VerifyOrExit(index != kInvalidIndex, error = kErrorParse);
 
     mPsdu[IsVersion2015() ? index : (index - 1)] = aCommandId;
 
@@ -829,7 +837,7 @@
             index += ie->GetLength();
             VerifyOrExit(index + footerLength <= mLength, index = kInvalidIndex);
 
-            if (ie->GetId() == kHeaderIeTermination2)
+            if (ie->GetId() == Termination2Ie::kHeaderIeId)
             {
                 break;
             }
@@ -886,25 +894,59 @@
     return index;
 }
 
-otError Frame::AppendHeaderIe(HeaderIe *aIeList, uint8_t aIeCount)
+#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
+template <typename IeType> Error Frame::AppendHeaderIeAt(uint8_t &aIndex)
 {
-    otError  error = OT_ERROR_NONE;
-    uint16_t index = FindHeaderIeIndex();
+    Error error = kErrorNone;
 
-    VerifyOrExit(index != kInvalidIndex, error = OT_ERROR_NOT_FOUND);
+    SuccessOrExit(error = InitIeHeaderAt(aIndex, IeType::kHeaderIeId, IeType::kIeContentSize));
 
-    for (uint8_t i = 0; i < aIeCount; i++)
-    {
-        memcpy(&mPsdu[index], &aIeList[i], sizeof(HeaderIe));
-
-        index += sizeof(HeaderIe) + aIeList[i].GetLength();
-        mLength += sizeof(HeaderIe) + aIeList[i].GetLength();
-    }
+    InitIeContentAt<IeType>(aIndex);
 
 exit:
     return error;
 }
 
+Error Frame::InitIeHeaderAt(uint8_t &aIndex, uint8_t ieId, uint8_t ieContentSize)
+{
+    Error error = kErrorNone;
+
+    if (aIndex == 0)
+    {
+        aIndex = FindHeaderIeIndex();
+    }
+
+    VerifyOrExit(aIndex != kInvalidIndex, error = kErrorNotFound);
+
+    reinterpret_cast<HeaderIe *>(mPsdu + aIndex)->Init(ieId, ieContentSize);
+    aIndex += sizeof(HeaderIe);
+
+    mLength += sizeof(HeaderIe) + ieContentSize;
+exit:
+    return error;
+}
+
+#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
+template <> void Frame::InitIeContentAt<TimeIe>(uint8_t &aIndex)
+{
+    reinterpret_cast<TimeIe *>(mPsdu + aIndex)->Init();
+    aIndex += sizeof(TimeIe);
+}
+#endif
+
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+template <> void Frame::InitIeContentAt<CslIe>(uint8_t &aIndex)
+{
+    aIndex += sizeof(CslIe);
+}
+#endif
+
+template <> void Frame::InitIeContentAt<Termination2Ie>(uint8_t &aIndex)
+{
+    OT_UNUSED_VARIABLE(aIndex);
+}
+#endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
+
 const uint8_t *Frame::GetHeaderIe(uint8_t aIeId) const
 {
     uint8_t        index        = FindHeaderIeIndex();
@@ -948,7 +990,7 @@
     {
         const HeaderIe *ie = reinterpret_cast<const HeaderIe *>(&mPsdu[index]);
 
-        if (ie->GetId() == kHeaderIeVendor)
+        if (ie->GetId() == VendorIeHeader::kHeaderIeId)
         {
             const VendorIeHeader *vendorIe =
                 reinterpret_cast<const VendorIeHeader *>(reinterpret_cast<const uint8_t *>(ie) + sizeof(HeaderIe));
@@ -972,7 +1014,7 @@
 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
 void Frame::SetCslIe(uint16_t aCslPeriod, uint16_t aCslPhase)
 {
-    uint8_t *cur = GetHeaderIe(Frame::kHeaderIeCsl);
+    uint8_t *cur = GetHeaderIe(CslIe::kHeaderIeId);
     CslIe *  csl;
 
     VerifyOrExit(cur != nullptr);
@@ -1002,7 +1044,7 @@
     const TimeIe * timeIe = nullptr;
     const uint8_t *cur    = nullptr;
 
-    cur = GetHeaderIe(kHeaderIeVendor);
+    cur = GetHeaderIe(VendorIeHeader::kHeaderIeId);
     VerifyOrExit(cur != nullptr);
 
     cur += sizeof(HeaderIe);
@@ -1079,6 +1121,17 @@
 }
 #endif
 
+// Explicit instantiation
+#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
+#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
+template Error Frame::AppendHeaderIeAt<TimeIe>(uint8_t &aIndex);
+#endif
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+template Error Frame::AppendHeaderIeAt<CslIe>(uint8_t &aIndex);
+#endif
+template Error Frame::AppendHeaderIeAt<Termination2Ie>(uint8_t &aIndex);
+#endif
+
 void TxFrame::CopyFrom(const TxFrame &aFromFrame)
 {
     uint8_t *      psduBuffer   = mPsdu;
@@ -1170,9 +1223,9 @@
 }
 
 #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
-otError TxFrame::GenerateEnhAck(const RxFrame &aFrame, bool aIsFramePending, const uint8_t *aIeData, uint8_t aIeLength)
+Error TxFrame::GenerateEnhAck(const RxFrame &aFrame, bool aIsFramePending, const uint8_t *aIeData, uint8_t aIeLength)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     uint16_t fcf = kFcfFrameAck | kFcfFrameVersion2015 | kFcfSrcAddrNone;
     Address  address;
@@ -1237,7 +1290,7 @@
         }
         else
         {
-            ExitNow(error = OT_ERROR_PARSE);
+            ExitNow(error = kErrorParse);
         }
 
         SetDstPanId(panId);
@@ -1280,15 +1333,15 @@
 }
 #endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
 
-otError RxFrame::ProcessReceiveAesCcm(const ExtAddress &aExtAddress, const Key &aMacKey)
+Error RxFrame::ProcessReceiveAesCcm(const ExtAddress &aExtAddress, const Key &aMacKey)
 {
 #if OPENTHREAD_RADIO
     OT_UNUSED_VARIABLE(aExtAddress);
     OT_UNUSED_VARIABLE(aMacKey);
 
-    return OT_ERROR_NONE;
+    return kErrorNone;
 #else
-    otError        error        = OT_ERROR_SECURITY;
+    Error          error        = kErrorSecurity;
     uint32_t       frameCounter = 0;
     uint8_t        securityLevel;
     uint8_t        nonce[Crypto::AesCcm::kNonceSize];
@@ -1296,7 +1349,7 @@
     uint8_t        tagLength;
     Crypto::AesCcm aesCcm;
 
-    VerifyOrExit(GetSecurityEnabled(), error = OT_ERROR_NONE);
+    VerifyOrExit(GetSecurityEnabled(), error = kErrorNone);
 
     SuccessOrExit(GetSecurityLevel(securityLevel));
     SuccessOrExit(GetFrameCounter(frameCounter));
@@ -1321,7 +1374,7 @@
     VerifyOrExit(memcmp(tag, GetFooter(), tagLength) == 0);
 #endif
 
-    error = OT_ERROR_NONE;
+    error = kErrorNone;
 
 exit:
     return error;
@@ -1357,7 +1410,7 @@
         break;
 
     case kFcfFrameMacCmd:
-        if (GetCommandId(commandId) != OT_ERROR_NONE)
+        if (GetCommandId(commandId) != kErrorNone)
         {
             commandId = 0xff;
         }
diff --git a/src/core/mac/mac_frame.hpp b/src/core/mac/mac_frame.hpp
index c8e0672..43e2047 100644
--- a/src/core/mac/mac_frame.hpp
+++ b/src/core/mac/mac_frame.hpp
@@ -72,6 +72,15 @@
     void Init(void) { mFields.m16 = 0; }
 
     /**
+     * This method initializes the Header IE with Id and Length.
+     *
+     * @param[in]  aId   The IE Element Id.
+     * @param[in]  aLen  The IE content length.
+     *
+     */
+    void Init(uint16_t aId, uint8_t aLen);
+
+    /**
      * This method returns the IE Element Id.
      *
      * @returns the IE Element Id.
@@ -144,6 +153,12 @@
 class VendorIeHeader
 {
 public:
+    enum : uint8_t
+    {
+        kHeaderIeId    = 0x00,
+        kIeContentSize = sizeof(uint8_t) * 4,
+    };
+
     /**
      * This method returns the Vendor OUI.
      *
@@ -205,6 +220,12 @@
         kVendorIeTime = 0x01,
     };
 
+    enum
+    {
+        kHeaderIeId    = VendorIeHeader::kHeaderIeId,
+        kIeContentSize = VendorIeHeader::kIeContentSize + sizeof(uint8_t) + sizeof(uint64_t),
+    };
+
     /**
      * This method initializes the time IE.
      *
@@ -257,6 +278,12 @@
 class ThreadIe
 {
 public:
+    enum : uint8_t
+    {
+        kHeaderIeId    = VendorIeHeader::kHeaderIeId,
+        kIeContentSize = VendorIeHeader::kIeContentSize,
+    };
+
     enum : uint32_t
     {
         kVendorOuiThreadCompanyId = 0xeab89b,
@@ -348,10 +375,6 @@
         kMacCmdCoordinatorRealignment     = 8,
         kMacCmdGtsRequest                 = 9,
 
-        kHeaderIeVendor       = 0x00,
-        kHeaderIeCsl          = 0x1a,
-        kHeaderIeTermination2 = 0x7f,
-
         kImmAckLength = kFcfSize + kDsnSize + k154FcsSize,
 
         kInfoStringSize = 128, ///< Max chars needed for the info string representation (@sa ToInfoString()).
@@ -384,11 +407,11 @@
     /**
      * This method validates the frame.
      *
-     * @retval OT_ERROR_NONE    Successfully parsed the MAC header.
-     * @retval OT_ERROR_PARSE   Failed to parse through the MAC header.
+     * @retval kErrorNone    Successfully parsed the MAC header.
+     * @retval kErrorParse   Failed to parse through the MAC header.
      *
      */
-    otError ValidatePsdu(void) const;
+    Error ValidatePsdu(void) const;
 
     /**
      * This method returns the IEEE 802.15.4 Frame Type.
@@ -513,11 +536,11 @@
      *
      * @param[out]  aPanId  The Destination PAN Identifier.
      *
-     * @retval OT_ERROR_NONE   Successfully retrieved the Destination PAN Identifier.
-     * @retval OT_ERROR_PARSE  Failed to parse the PAN Identifier.
+     * @retval kErrorNone   Successfully retrieved the Destination PAN Identifier.
+     * @retval kErrorParse  Failed to parse the PAN Identifier.
      *
      */
-    otError GetDstPanId(PanId &aPanId) const;
+    Error GetDstPanId(PanId &aPanId) const;
 
     /**
      * This method sets the Destination PAN Identifier.
@@ -540,10 +563,10 @@
      *
      * @param[out]  aAddress  The Destination Address.
      *
-     * @retval OT_ERROR_NONE  Successfully retrieved the Destination Address.
+     * @retval kErrorNone  Successfully retrieved the Destination Address.
      *
      */
-    otError GetDstAddr(Address &aAddress) const;
+    Error GetDstAddr(Address &aAddress) const;
 
     /**
      * This method sets the Destination Address.
@@ -582,20 +605,20 @@
      *
      * @param[out]  aPanId  The Source PAN Identifier.
      *
-     * @retval OT_ERROR_NONE   Successfully retrieved the Source PAN Identifier.
+     * @retval kErrorNone   Successfully retrieved the Source PAN Identifier.
      *
      */
-    otError GetSrcPanId(PanId &aPanId) const;
+    Error GetSrcPanId(PanId &aPanId) const;
 
     /**
      * This method sets the Source PAN Identifier.
      *
      * @param[in]  aPanId  The Source PAN Identifier.
      *
-     * @retval OT_ERROR_NONE   Successfully set the Source PAN Identifier.
+     * @retval kErrorNone   Successfully set the Source PAN Identifier.
      *
      */
-    otError SetSrcPanId(PanId aPanId);
+    Error SetSrcPanId(PanId aPanId);
 
     /**
      * This method indicates whether or not the Source Address is present for this object.
@@ -610,10 +633,10 @@
      *
      * @param[out]  aAddress  The Source Address.
      *
-     * @retval OT_ERROR_NONE  Successfully retrieved the Source Address.
+     * @retval kErrorNone  Successfully retrieved the Source Address.
      *
      */
-    otError GetSrcAddr(Address &aAddress) const;
+    Error GetSrcAddr(Address &aAddress) const;
 
     /**
      * This method sets the Source Address.
@@ -644,11 +667,11 @@
      *
      * @param[out]  aSecurityControlField  The Security Control Field.
      *
-     * @retval OT_ERROR_NONE   Successfully retrieved the Security Level Identifier.
-     * @retval OT_ERROR_PARSE  Failed to find the security control field in the frame.
+     * @retval kErrorNone   Successfully retrieved the Security Level Identifier.
+     * @retval kErrorParse  Failed to find the security control field in the frame.
      *
      */
-    otError GetSecurityControlField(uint8_t &aSecurityControlField) const;
+    Error GetSecurityControlField(uint8_t &aSecurityControlField) const;
 
     /**
      * This method sets the Security Control Field.
@@ -663,30 +686,30 @@
      *
      * @param[out]  aSecurityLevel  The Security Level Identifier.
      *
-     * @retval OT_ERROR_NONE  Successfully retrieved the Security Level Identifier.
+     * @retval kErrorNone  Successfully retrieved the Security Level Identifier.
      *
      */
-    otError GetSecurityLevel(uint8_t &aSecurityLevel) const;
+    Error GetSecurityLevel(uint8_t &aSecurityLevel) const;
 
     /**
      * This method gets the Key Identifier Mode.
      *
      * @param[out]  aSecurityLevel  The Key Identifier Mode.
      *
-     * @retval OT_ERROR_NONE  Successfully retrieved the Key Identifier Mode.
+     * @retval kErrorNone  Successfully retrieved the Key Identifier Mode.
      *
      */
-    otError GetKeyIdMode(uint8_t &aKeyIdMode) const;
+    Error GetKeyIdMode(uint8_t &aKeyIdMode) const;
 
     /**
      * This method gets the Frame Counter.
      *
      * @param[out]  aFrameCounter  The Frame Counter.
      *
-     * @retval OT_ERROR_NONE  Successfully retrieved the Frame Counter.
+     * @retval kErrorNone  Successfully retrieved the Frame Counter.
      *
      */
-    otError GetFrameCounter(uint32_t &aFrameCounter) const;
+    Error GetFrameCounter(uint32_t &aFrameCounter) const;
 
     /**
      * This method sets the Frame Counter.
@@ -717,10 +740,10 @@
      *
      * @param[out]  aKeyId  The Key Identifier.
      *
-     * @retval OT_ERROR_NONE  Successfully retrieved the Key Identifier.
+     * @retval kErrorNone  Successfully retrieved the Key Identifier.
      *
      */
-    otError GetKeyId(uint8_t &aKeyId) const;
+    Error GetKeyId(uint8_t &aKeyId) const;
 
     /**
      * This method sets the Key Identifier.
@@ -735,20 +758,20 @@
      *
      * @param[out]  aCommandId  The Command ID.
      *
-     * @retval OT_ERROR_NONE  Successfully retrieved the Command ID.
+     * @retval kErrorNone  Successfully retrieved the Command ID.
      *
      */
-    otError GetCommandId(uint8_t &aCommandId) const;
+    Error GetCommandId(uint8_t &aCommandId) const;
 
     /**
      * This method sets the Command ID.
      *
      * @param[in]  aCommandId  The Command ID.
      *
-     * @retval OT_ERROR_NONE  Successfully set the Command ID.
+     * @retval kErrorNone  Successfully set the Command ID.
      *
      */
-    otError SetCommandId(uint8_t aCommandId);
+    Error SetCommandId(uint8_t aCommandId);
 
     /**
      * This method indicates whether the frame is a MAC Data Request command (data poll).
@@ -923,16 +946,22 @@
 
 #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
     /**
-     * This method appends Header IEs to MAC header.
+     * This template method appends an Header IE at specified index in this frame.
      *
-     * @param[in]   aIeList  The pointer to the Header IEs array.
-     * @param[in]   aIeCount The number of Header IEs in the array.
+     * @param[in,out]   aIndex  The index to append IE. If `aIndex` is `0` on input, this method finds the index
+     *                          for the first IE and appends the IE at that position. If the position is not found
+     *                          successfully, `aIndex` will be set to `kInvalidIndex`. Otherwise the IE will be
+     *                          appended at `aIndex` on input. And on output, `aIndex` will be set to the end of the
+     *                          IE just appended.
      *
-     * @retval OT_ERROR_NONE    Successfully appended the Header IEs.
-     * @retval OT_ERROR_FAILED  If IE Present bit is not set.
+     * @tparam  IeType  The Header IE type, it MUST contain an enum `kHeaderIeId` equal to the IE's Id
+     *                  and an enum `kIeContentSize` indicating the IE body's size.
+     *
+     * @retval kErrorNone      Successfully appended the Header IE.
+     * @retval kErrorNotFound  The position for first IE is not found.
      *
      */
-    otError AppendHeaderIe(HeaderIe *aIeList, uint8_t aIeCount);
+    template <typename IeType> Error AppendHeaderIeAt(uint8_t &aIndex);
 
     /**
      * This method returns a pointer to the Header IE.
@@ -1091,6 +1120,9 @@
     uint8_t FindPayloadIndex(void) const;
 #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
     uint8_t FindHeaderIeIndex(void) const;
+
+    Error                           InitIeHeaderAt(uint8_t &aIndex, uint8_t ieId, uint8_t ieContentSize);
+    template <typename IeType> void InitIeContentAt(uint8_t &aIndex);
 #endif
 
     static uint8_t GetKeySourceLength(uint8_t aKeyIdMode);
@@ -1171,11 +1203,11 @@
      *                          for AES CCM computation.
      * @param[in]  aMacKey      A reference to the MAC key to decrypt the received frame.
      *
-     * @retval OT_ERROR_NONE      Process of received frame AES CCM succeeded.
-     * @retval OT_ERROR_SECURITY  Received frame MIC check failed.
+     * @retval kErrorNone      Process of received frame AES CCM succeeded.
+     * @retval kErrorSecurity  Received frame MIC check failed.
      *
      */
-    otError ProcessReceiveAesCcm(const ExtAddress &aExtAddress, const Key &aMacKey);
+    Error ProcessReceiveAesCcm(const ExtAddress &aExtAddress, const Key &aMacKey);
 
 #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
     /**
@@ -1394,11 +1426,11 @@
      * @param[in]    aIeData            A pointer to the IE data portion of the ACK to be sent.
      * @param[in]    aIeLength          The length of IE data portion of the ACK to be sent.
      *
-     * @retval  OT_ERROR_NONE           Successfully generated Enh Ack.
-     * @retval  OT_ERROR_PARSE          @p aFrame has incorrect format.
+     * @retval  kErrorNone           Successfully generated Enh Ack.
+     * @retval  kErrorParse          @p aFrame has incorrect format.
      *
      */
-    otError GenerateEnhAck(const RxFrame &aFrame, bool aIsFramePending, const uint8_t *aIeData, uint8_t aIeLength);
+    Error GenerateEnhAck(const RxFrame &aFrame, bool aIsFramePending, const uint8_t *aIeData, uint8_t aIeLength);
 
 #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
     /**
@@ -1642,6 +1674,12 @@
 class CslIe
 {
 public:
+    enum : uint8_t
+    {
+        kHeaderIeId    = 0x1a,
+        kIeContentSize = sizeof(uint16_t) * 2,
+    };
+
     /**
      * This method returns the CSL Period.
      *
@@ -1680,6 +1718,22 @@
 } OT_TOOL_PACKED_END;
 
 /**
+ * This class implements Termination2 IE.
+ *
+ * This class is empty for template specialization.
+ *
+ */
+class Termination2Ie
+{
+public:
+    enum : uint8_t
+    {
+        kHeaderIeId    = 0x7f,
+        kIeContentSize = 0,
+    };
+};
+
+/**
  * @}
  *
  */
diff --git a/src/core/mac/mac_links.cpp b/src/core/mac/mac_links.cpp
index 5b41262..156d48d 100644
--- a/src/core/mac/mac_links.cpp
+++ b/src/core/mac/mac_links.cpp
@@ -159,9 +159,9 @@
 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
     if (aRadioTypes.Contains(kRadioTypeIeee802154))
     {
-        otError error = mSubMac.Send();
+        Error error = mSubMac.Send();
 
-        OT_ASSERT(error == OT_ERROR_NONE);
+        OT_ASSERT(error == kErrorNone);
         OT_UNUSED_VARIABLE(error);
     }
 #endif
diff --git a/src/core/mac/mac_links.hpp b/src/core/mac/mac_links.hpp
index 4ae1fd2..0f6d835 100644
--- a/src/core/mac/mac_links.hpp
+++ b/src/core/mac/mac_links.hpp
@@ -180,7 +180,7 @@
         mTxFrame802154.SetIsARetransmission(false);
         mTxFrame802154.SetIsSecurityProcessed(false);
         mTxFrame802154.SetCsmaCaEnabled(true); // Set to true by default, only set to `false` for CSL transmission
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
         mTxFrame802154.SetTxDelay(0);
         mTxFrame802154.SetTxDelayBaseTime(0);
 #endif
@@ -513,8 +513,8 @@
     {
 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
         {
-            otError error = mSubMac.Send();
-            OT_ASSERT(error == OT_ERROR_NONE);
+            Error error = mSubMac.Send();
+            OT_ASSERT(error == kErrorNone);
             OT_UNUSED_VARIABLE(error);
         }
 #endif
@@ -576,12 +576,12 @@
      * @param[in] aScanChannel   The channel to perform the energy scan on.
      * @param[in] aScanDuration  The duration, in milliseconds, for the channel to be scanned.
      *
-     * @retval OT_ERROR_NONE             Successfully started scanning the channel.
-     * @retval OT_ERROR_INVALID_STATE    The radio was disabled or transmitting.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  Energy scan is not supported by radio link.
+     * @retval kErrorNone            Successfully started scanning the channel.
+     * @retval kErrorInvalidState    The radio was disabled or transmitting.
+     * @retval kErrorNotImplemented  Energy scan is not supported by radio link.
      *
      */
-    otError EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration)
+    Error EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration)
     {
         OT_UNUSED_VARIABLE(aScanChannel);
         OT_UNUSED_VARIABLE(aScanDuration);
@@ -590,7 +590,7 @@
 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
             mSubMac.EnergyScan(aScanChannel, aScanDuration);
 #else
-            OT_ERROR_NOT_IMPLEMENTED;
+            kErrorNotImplemented;
 #endif
     }
 
@@ -654,8 +654,8 @@
      *
      * @param[in] TxFrame  The `TxFrame` from which to get the counter value.
      *
-     * @retval OT_ERROR_NONE             If successful.
-     * @retval OT_ERROR_INVALID_STATE    If the raw link-layer isn't enabled.
+     * @retval kErrorNone            If successful.
+     * @retval kErrorInvalidState    If the raw link-layer isn't enabled.
      *
      */
     void SetMacFrameCounter(TxFrame &aFrame);
diff --git a/src/core/mac/mac_types.cpp b/src/core/mac/mac_types.cpp
index 3ed7150..6761d35 100644
--- a/src/core/mac/mac_types.cpp
+++ b/src/core/mac/mac_types.cpp
@@ -128,15 +128,15 @@
     return NameData(m8, len);
 }
 
-otError NetworkName::Set(const NameData &aNameData)
+Error NetworkName::Set(const NameData &aNameData)
 {
-    otError error  = OT_ERROR_NONE;
+    Error   error  = kErrorNone;
     uint8_t newLen = static_cast<uint8_t>(StringLength(aNameData.GetBuffer(), aNameData.GetLength()));
 
-    VerifyOrExit(newLen <= kMaxSize, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(newLen <= kMaxSize, error = kErrorInvalidArgs);
 
     // Ensure the new name does not match the current one.
-    VerifyOrExit(memcmp(m8, aNameData.GetBuffer(), newLen) || (m8[newLen] != '\0'), error = OT_ERROR_ALREADY);
+    VerifyOrExit(memcmp(m8, aNameData.GetBuffer(), newLen) || (m8[newLen] != '\0'), error = kErrorAlready);
 
     memcpy(m8, aNameData.GetBuffer(), newLen);
     m8[newLen] = '\0';
@@ -162,15 +162,15 @@
     return NameData(m8, len);
 }
 
-otError DomainName::Set(const NameData &aNameData)
+Error DomainName::Set(const NameData &aNameData)
 {
-    otError error  = OT_ERROR_NONE;
+    Error   error  = kErrorNone;
     uint8_t newLen = static_cast<uint8_t>(StringLength(aNameData.GetBuffer(), aNameData.GetLength()));
 
-    VerifyOrExit(newLen <= kMaxSize, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(newLen <= kMaxSize, error = kErrorInvalidArgs);
 
     // Ensure the new name does not match the current one.
-    VerifyOrExit(memcmp(m8, aNameData.GetBuffer(), newLen) || (m8[newLen] != '\0'), error = OT_ERROR_ALREADY);
+    VerifyOrExit(memcmp(m8, aNameData.GetBuffer(), newLen) || (m8[newLen] != '\0'), error = kErrorAlready);
 
     memcpy(m8, aNameData.GetBuffer(), newLen);
     m8[newLen] = '\0';
diff --git a/src/core/mac/mac_types.hpp b/src/core/mac/mac_types.hpp
index 5cbdc63..8dac5e0 100644
--- a/src/core/mac/mac_types.hpp
+++ b/src/core/mac/mac_types.hpp
@@ -564,12 +564,12 @@
      *
      * @param[in]  aNameData           A reference to name data.
      *
-     * @retval OT_ERROR_NONE           Successfully set the IEEE 802.15.4 Network Name.
-     * @retval OT_ERROR_ALREADY        The name is already set to the same string.
-     * @retval OT_ERROR_INVALID_ARGS   Given name is too long.
+     * @retval kErrorNone          Successfully set the IEEE 802.15.4 Network Name.
+     * @retval kErrorAlready       The name is already set to the same string.
+     * @retval kErrorInvalidArgs   Given name is too long.
      *
      */
-    otError Set(const NameData &aNameData);
+    Error Set(const NameData &aNameData);
 
     /**
      * This method overloads operator `==` to evaluate whether or not two given `NetworkName` objects are equal.
@@ -623,12 +623,12 @@
      *
      * @param[in]  aNameData           A reference to name data.
      *
-     * @retval OT_ERROR_NONE           Successfully set the Thread Domain Name.
-     * @retval OT_ERROR_ALREADY        The name is already set to the same string.
-     * @retval OT_ERROR_INVALID_ARGS   Given name is too long.
+     * @retval kErrorNone          Successfully set the Thread Domain Name.
+     * @retval kErrorAlready       The name is already set to the same string.
+     * @retval kErrorInvalidArgs   Given name is too long.
      *
      */
-    otError Set(const NameData &aNameData);
+    Error Set(const NameData &aNameData);
 
 private:
     char m8[kMaxSize + 1]; ///< Byte values.
diff --git a/src/core/mac/sub_mac.cpp b/src/core/mac/sub_mac.cpp
index dd27e71..dcbe4ab 100644
--- a/src/core/mac/sub_mac.cpp
+++ b/src/core/mac/sub_mac.cpp
@@ -65,14 +65,14 @@
     , mPcapCallbackContext(nullptr)
     , mFrameCounter(0)
     , mKeyId(0)
-    , mTimer(aInstance, SubMac::HandleTimer, this)
+    , mTimer(aInstance, SubMac::HandleTimer)
 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
     , mCslTimeout(OPENTHREAD_CONFIG_CSL_TIMEOUT)
     , mCslPeriod(0)
     , mCslChannel(0)
     , mIsCslChannelSpecified(false)
     , mCslState(kCslIdle)
-    , mCslTimer(aInstance, SubMac::HandleCslTimer, this)
+    , mCslTimer(aInstance, SubMac::HandleCslTimer)
 #endif
 {
     mExtAddress.Clear();
@@ -151,9 +151,9 @@
     mPcapCallbackContext = aCallbackContext;
 }
 
-otError SubMac::Enable(void)
+Error SubMac::Enable(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     VerifyOrExit(mState == kStateDisabled);
 
@@ -163,13 +163,13 @@
     SetState(kStateSleep);
 
 exit:
-    OT_ASSERT(error == OT_ERROR_NONE);
+    OT_ASSERT(error == kErrorNone);
     return error;
 }
 
-otError SubMac::Disable(void)
+Error SubMac::Disable(void)
 {
-    otError error;
+    Error error;
 
     mTimer.Stop();
     SuccessOrExit(error = Get<Radio>().Sleep());
@@ -180,13 +180,13 @@
     return error;
 }
 
-otError SubMac::Sleep(void)
+Error SubMac::Sleep(void)
 {
-    otError error = Get<Radio>().Sleep();
+    Error error = Get<Radio>().Sleep();
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogWarnMac("RadioSleep() failed, error: %s", otThreadErrorToString(error));
+        otLogWarnMac("RadioSleep() failed, error: %s", ErrorToString(error));
         ExitNow();
     }
 
@@ -196,13 +196,13 @@
     return error;
 }
 
-otError SubMac::Receive(uint8_t aChannel)
+Error SubMac::Receive(uint8_t aChannel)
 {
-    otError error = Get<Radio>().Receive(aChannel);
+    Error error = Get<Radio>().Receive(aChannel);
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogWarnMac("RadioReceive() failed, error: %s", otThreadErrorToString(error));
+        otLogWarnMac("RadioReceive() failed, error: %s", ErrorToString(error));
         ExitNow();
     }
 
@@ -213,9 +213,9 @@
 }
 
 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
-otError SubMac::CslSample(uint8_t aPanChannel)
+Error SubMac::CslSample(uint8_t aPanChannel)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     if (!IsCslChannelSpecified())
     {
@@ -233,7 +233,7 @@
 #endif
         break;
     case kCslIdle:
-        ExitNow(error = OT_ERROR_INVALID_STATE);
+        ExitNow(error = kErrorInvalidState);
     default:
         OT_ASSERT(false);
     }
@@ -241,17 +241,17 @@
     SetState(kStateCslSample);
 
 exit:
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogWarnMac("CslSample() failed, error: %s", otThreadErrorToString(error));
+        otLogWarnMac("CslSample() failed, error: %s", ErrorToString(error));
     }
     return error;
 }
 #endif
 
-void SubMac::HandleReceiveDone(RxFrame *aFrame, otError aError)
+void SubMac::HandleReceiveDone(RxFrame *aFrame, Error aError)
 {
-    if (mPcapCallback && (aFrame != nullptr) && (aError == OT_ERROR_NONE))
+    if (mPcapCallback && (aFrame != nullptr) && (aError == kErrorNone))
     {
         mPcapCallback(aFrame, false, mPcapCallbackContext);
     }
@@ -262,7 +262,7 @@
     }
 
 #if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
-    if (aFrame != nullptr && aError == OT_ERROR_NONE)
+    if (aFrame != nullptr && aError == kErrorNone)
     {
         // Split the log into two lines for RTT to output
         otLogDebgMac("Received frame in state (SubMac %s, CSL %s), timestamp %u", StateToString(mState),
@@ -275,9 +275,9 @@
     mCallbacks.ReceiveDone(aFrame, aError);
 }
 
-otError SubMac::Send(void)
+Error SubMac::Send(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     switch (mState)
     {
@@ -288,7 +288,7 @@
 #endif
     case kStateTransmit:
     case kStateEnergyScan:
-        ExitNow(error = OT_ERROR_INVALID_STATE);
+        ExitNow(error = kErrorInvalidState);
         OT_UNREACHABLE_CODE(break);
 
     case kStateSleep:
@@ -409,7 +409,7 @@
 
 void SubMac::BeginTransmit(void)
 {
-    otError error;
+    Error error;
 
     OT_UNUSED_VARIABLE(error);
 
@@ -422,7 +422,7 @@
     if ((mRadioCaps & OT_RADIO_CAPS_SLEEP_TO_TX) == 0)
     {
         error = Get<Radio>().Receive(mTransmitFrame.GetChannel());
-        OT_ASSERT(error == OT_ERROR_NONE);
+        OT_ASSERT(error == kErrorNone);
     }
 
     SetState(kStateTransmit);
@@ -433,14 +433,14 @@
     }
 
     error = Get<Radio>().Transmit(mTransmitFrame);
-    if (error == OT_ERROR_INVALID_STATE && mTransmitFrame.mInfo.mTxInfo.mTxDelay > 0)
+    if (error == kErrorInvalidState && mTransmitFrame.mInfo.mTxInfo.mTxDelay > 0)
     {
         // Platform `transmit_at` fails and we send the frame directly.
         mTransmitFrame.mInfo.mTxInfo.mTxDelay         = 0;
         mTransmitFrame.mInfo.mTxInfo.mTxDelayBaseTime = 0;
         error                                         = Get<Radio>().Transmit(mTransmitFrame);
     }
-    OT_ASSERT(error == OT_ERROR_NONE);
+    OT_ASSERT(error == kErrorNone);
 
 exit:
     return;
@@ -458,7 +458,7 @@
     }
 }
 
-void SubMac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, otError aError)
+void SubMac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aError)
 {
     bool ccaSuccess = true;
     bool shouldRetx;
@@ -471,18 +471,18 @@
 
     switch (aError)
     {
-    case OT_ERROR_ABORT:
+    case kErrorAbort:
         // Do not record CCA status in case of `ABORT` error
         // since there may be no CCA check performed by radio.
         break;
 
-    case OT_ERROR_CHANNEL_ACCESS_FAILURE:
+    case kErrorChannelAccessFailure:
         ccaSuccess = false;
 
-        // fall through
+        OT_FALL_THROUGH;
 
-    case OT_ERROR_NONE:
-    case OT_ERROR_NO_ACK:
+    case kErrorNone:
+    case kErrorNoAck:
         if (aFrame.IsCsmaCaEnabled())
         {
             mCallbacks.RecordCcaStatus(ccaSuccess, aFrame.GetChannel());
@@ -510,8 +510,7 @@
 
     // Determine whether to re-transmit the frame.
 
-    shouldRetx =
-        ((aError != OT_ERROR_NONE) && ShouldHandleRetries() && (mTransmitRetries < aFrame.GetMaxFrameRetries()));
+    shouldRetx = ((aError != kErrorNone) && ShouldHandleRetries() && (mTransmitRetries < aFrame.GetMaxFrameRetries()));
 
     mCallbacks.RecordFrameTransmitStatus(aFrame, aAckFrame, aError, mTransmitRetries, shouldRetx);
 
@@ -554,10 +553,10 @@
     allowError = Get<LinkRaw>().IsEnabled();
 #endif
 
-    VerifyOrExit(aFrame.GetKeyIdMode(keyIdMode) == OT_ERROR_NONE, OT_ASSERT(allowError));
+    VerifyOrExit(aFrame.GetKeyIdMode(keyIdMode) == kErrorNone, OT_ASSERT(allowError));
     VerifyOrExit(keyIdMode == Frame::kKeyIdMode1);
 
-    VerifyOrExit(aFrame.GetFrameCounter(frameCounter) == OT_ERROR_NONE, OT_ASSERT(allowError));
+    VerifyOrExit(aFrame.GetFrameCounter(frameCounter) == kErrorNone, OT_ASSERT(allowError));
     UpdateFrameCounter(frameCounter);
 
 exit:
@@ -574,9 +573,9 @@
     return Get<Radio>().GetReceiveSensitivity();
 }
 
-otError SubMac::EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration)
+Error SubMac::EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     switch (mState)
     {
@@ -587,7 +586,7 @@
     case kStateCslTransmit:
 #endif
     case kStateEnergyScan:
-        ExitNow(error = OT_ERROR_INVALID_STATE);
+        ExitNow(error = kErrorInvalidState);
 
     case kStateReceive:
     case kStateSleep:
@@ -605,7 +604,7 @@
     else if (ShouldHandleEnergyScan())
     {
         error = Get<Radio>().Receive(aScanChannel);
-        OT_ASSERT(error == OT_ERROR_NONE);
+        OT_ASSERT(error == kErrorNone);
 
         SetState(kStateEnergyScan);
         mEnergyScanMaxRssi = kInvalidRssiValue;
@@ -614,7 +613,7 @@
     }
     else
     {
-        error = OT_ERROR_NOT_IMPLEMENTED;
+        error = kErrorNotImplemented;
     }
 
 exit:
@@ -657,7 +656,7 @@
 
 void SubMac::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<SubMac>().HandleTimer();
+    aTimer.Get<SubMac>().HandleTimer();
 }
 
 void SubMac::HandleTimer(void)
@@ -676,7 +675,7 @@
     case kStateTransmit:
         otLogDebgMac("Ack timer timed out");
         IgnoreError(Get<Radio>().Receive(mTransmitFrame.GetChannel()));
-        HandleTransmitDone(mTransmitFrame, nullptr, OT_ERROR_NO_ACK);
+        HandleTransmitDone(mTransmitFrame, nullptr, kErrorNoAck);
         break;
 
     case kStateEnergyScan:
@@ -859,66 +858,57 @@
 
 const char *SubMac::StateToString(State aState)
 {
-    const char *str = "Unknown";
-
-    switch (aState)
-    {
-    case kStateDisabled:
-        str = "Disabled";
-        break;
-    case kStateSleep:
-        str = "Sleep";
-        break;
-    case kStateReceive:
-        str = "Receive";
-        break;
-    case kStateCsmaBackoff:
-        str = "CsmaBackoff";
-        break;
-    case kStateTransmit:
-        str = "Transmit";
-        break;
-    case kStateEnergyScan:
-        str = "EnergyScan";
-        break;
+    static const char *const kStateStrings[] = {
+        "Disabled",    // (0) kStateDisabled
+        "Sleep",       // (1) kStateSleep
+        "Receive",     // (2) kStateReceive
+        "CsmaBackoff", // (3) kStateCsmaBackoff
+        "Transmit",    // (4) kStateTransmit
+        "EnergyScan",  // (5) kStateEnergyScan
 #if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
-    case kStateCslTransmit:
-        str = "CslTransmit";
-        break;
+        "CslTransmit", // (6) kStateCslTransmit
 #endif
 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
-    case kStateCslSample:
-        str = "CslSample";
-        break;
+        "CslSample", // (7) kStateCslSample
 #endif
-    }
+    };
 
-    return str;
+    static_assert(kStateDisabled == 0, "kStateDisabled value is not correct");
+    static_assert(kStateSleep == 1, "kStateSleep value is not correct");
+    static_assert(kStateReceive == 2, "kStateReceive value is not correct");
+    static_assert(kStateCsmaBackoff == 3, "kStateCsmaBackoff value is not correct");
+    static_assert(kStateTransmit == 4, "kStateTransmit value is not correct");
+    static_assert(kStateEnergyScan == 5, "kStateEnergyScan value is not correct");
+#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+    static_assert(kStateCslTransmit == 6, "kStateCslTransmit value is not correct");
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+    static_assert(kStateCslSample == 7, "kStateCslSample value is not correct");
+#endif
+#else
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+    static_assert(kStateCslSample == 6, "kStateCslSample value is not correct");
+#endif
+#endif
+
+    return kStateStrings[aState];
 }
 
 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
 const char *SubMac::CslStateToString(CslState aCslState)
 {
-    const char *str = "Unknown";
+    static const char *const kCslStateStrings[] = {
+        "CslIdle",   // (0) kCslIdle
+        "CslSample", // (1) kCslSample
+        "CslSleep",  // (2) kCslSleep
+    };
 
-    switch (aCslState)
-    {
-    case kCslIdle:
-        str = "CslIdle";
-        break;
-    case kCslSample:
-        str = "CslSample";
-        break;
-    case kCslSleep:
-        str = "kCslSleep";
-        break;
-    default:
-        break;
-    }
+    static_assert(kCslIdle == 0, "kCslIdle value is incorrect");
+    static_assert(kCslSample == 1, "kCslSample value is incorrect");
+    static_assert(kCslSleep == 2, "kCslSleep value is incorrect");
 
-    return str;
+    return kCslStateStrings[aCslState];
 }
-#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+#endif
 
 // LCOV_EXCL_STOP
 
@@ -971,7 +961,7 @@
 
 void SubMac::HandleCslTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<SubMac>().HandleCslTimer();
+    aTimer.Get<SubMac>().HandleCslTimer();
 }
 
 void SubMac::HandleCslTimer(void)
diff --git a/src/core/mac/sub_mac.hpp b/src/core/mac/sub_mac.hpp
index b2036e3..4b79ca2 100644
--- a/src/core/mac/sub_mac.hpp
+++ b/src/core/mac/sub_mac.hpp
@@ -58,6 +58,18 @@
 
 namespace Mac {
 
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE && (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2)
+#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE."
+#endif
+
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE && (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2)
+#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE."
+#endif
+
+#if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE && !OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+#error "OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE is required for OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE."
+#endif
+
 /**
  * This class implements the IEEE 802.15.4 MAC (sub-MAC).
  *
@@ -104,12 +116,12 @@
          * This method notifies user of `SubMac` of a received frame.
          *
          * @param[in]  aFrame    A pointer to the received frame or nullptr if the receive operation failed.
-         * @param[in]  aError    OT_ERROR_NONE when successfully received a frame,
-         *                       OT_ERROR_ABORT when reception was aborted and a frame was not received,
-         *                       OT_ERROR_NO_BUFS when a frame could not be received due to lack of rx buffer space.
+         * @param[in]  aError    kErrorNone when successfully received a frame,
+         *                       kErrorAbort when reception was aborted and a frame was not received,
+         *                       kErrorNoBufs when a frame could not be received due to lack of rx buffer space.
          *
          */
-        void ReceiveDone(RxFrame *aFrame, otError aError);
+        void ReceiveDone(RxFrame *aFrame, Error aError);
 
         /**
          * This method notifies user of `SubMac` of CCA status (success/failure) for a frame transmission attempt.
@@ -132,10 +144,10 @@
          *
          * @param[in] aFrame      The transmitted frame.
          * @param[in] aAckFrame   A pointer to the ACK frame, or nullptr if no ACK was received.
-         * @param[in] aError      OT_ERROR_NONE when the frame was transmitted successfully,
-         *                        OT_ERROR_NO_ACK when the frame was transmitted but no ACK was received,
-         *                        OT_ERROR_CHANNEL_ACCESS_FAILURE tx failed due to activity on the channel,
-         *                        OT_ERROR_ABORT when transmission was aborted for other reasons.
+         * @param[in] aError      kErrorNone when the frame was transmitted successfully,
+         *                        kErrorNoAck when the frame was transmitted but no ACK was received,
+         *                        kErrorChannelAccessFailure tx failed due to activity on the channel,
+         *                        kErrorAbort when transmission was aborted for other reasons.
          * @param[in] aRetryCount Current retry count. This is valid only when sub-mac handles frame re-transmissions.
          * @param[in] aWillRetx   Indicates whether frame will be retransmitted or not. This is applicable only
          *                        when there was an error in current transmission attempt.
@@ -143,7 +155,7 @@
          */
         void RecordFrameTransmitStatus(const TxFrame &aFrame,
                                        const RxFrame *aAckFrame,
-                                       otError        aError,
+                                       Error          aError,
                                        uint8_t        aRetryCount,
                                        bool           aWillRetx);
 
@@ -153,13 +165,13 @@
          *
          * @param[in]  aFrame     The transmitted frame.
          * @param[in]  aAckFrame  A pointer to the ACK frame, nullptr if no ACK was received.
-         * @param[in]  aError     OT_ERROR_NONE when the frame was transmitted,
-         *                        OT_ERROR_NO_ACK when the frame was transmitted but no ACK was received,
-         *                        OT_ERROR_CHANNEL_ACCESS_FAILURE tx failed due to activity on the channel,
-         *                        OT_ERROR_ABORT when transmission was aborted for other reasons.
+         * @param[in]  aError     kErrorNone when the frame was transmitted,
+         *                        kErrorNoAck when the frame was transmitted but no ACK was received,
+         *                        kErrorChannelAccessFailure tx failed due to activity on the channel,
+         *                        kErrorAbort when transmission was aborted for other reasons.
          *
          */
-        void TransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, otError aError);
+        void TransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aError);
 
         /**
          * This method notifies user of `SubMac` that energy scan is complete.
@@ -263,40 +275,49 @@
     /**
      * This method enables the radio.
      *
-     * @retval OT_ERROR_NONE     Successfully enabled.
-     * @retval OT_ERROR_FAILED   The radio could not be enabled.
+     * @retval kErrorNone     Successfully enabled.
+     * @retval kErrorFailed   The radio could not be enabled.
      *
      */
-    otError Enable(void);
+    Error Enable(void);
 
     /**
      * This method disables the radio.
      *
-     * @retval OT_ERROR_NONE     Successfully disabled the radio.
+     * @retval kErrorNone     Successfully disabled the radio.
      *
      */
-    otError Disable(void);
+    Error Disable(void);
 
     /**
      * This method transitions the radio to Sleep.
      *
-     * @retval OT_ERROR_NONE          Successfully transitioned to Sleep.
-     * @retval OT_ERROR_BUSY          The radio was transmitting.
-     * @retval OT_ERROR_INVALID_STATE The radio was disabled.
+     * @retval kErrorNone          Successfully transitioned to Sleep.
+     * @retval kErrorBusy          The radio was transmitting.
+     * @retval kErrorInvalidState  The radio was disabled.
      *
      */
-    otError Sleep(void);
+    Error Sleep(void);
+
+    /**
+     * This method indicates whether the sub-mac is busy transmitting or scanning.
+     *
+     * @retval TRUE if the sub-mac is busy transmitting or scanning.
+     * @retval FALSE if the sub-mac is not busy transmitting or scanning.
+     *
+     */
+    bool IsTransmittingOrScanning(void) const { return (mState == kStateTransmit) || (mState == kStateEnergyScan); }
 
     /**
      * This method transitions the radio to Receive.
      *
      * @param[in]  aChannel   The channel to use for receiving.
      *
-     * @retval OT_ERROR_NONE          Successfully transitioned to Receive.
-     * @retval OT_ERROR_INVALID_STATE The radio was disabled or transmitting.
+     * @retval kErrorNone          Successfully transitioned to Receive.
+     * @retval kErrorInvalidState  The radio was disabled or transmitting.
      *
      */
-    otError Receive(uint8_t aChannel);
+    Error Receive(uint8_t aChannel);
 
 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
     /**
@@ -309,12 +330,12 @@
      * @param[in]  aPanChannel  The current phy channel used by the device. This param will only take effect when CSL
      *                          channel hasn't been explicitly specified.
      *
-     * @retval OT_ERROR_NONE          Successfully entered CSL operation (sleep or receive according to CSL timer).
-     * @retval OT_ERROR_BUSY          The radio was transmitting.
-     * @retval OT_ERROR_INVALID_STATE The radio was disabled.
+     * @retval kErrorNone          Successfully entered CSL operation (sleep or receive according to CSL timer).
+     * @retval kErrorBusy          The radio was transmitting.
+     * @retval kErrorInvalidState  The radio was disabled.
      *
      */
-    otError CslSample(uint8_t aPanChannel);
+    Error CslSample(uint8_t aPanChannel);
 #endif
 
     /**
@@ -332,11 +353,11 @@
      *
      * The `SubMac` layer handles Ack timeout, CSMA backoff, and frame retransmission.
      *
-     * @retval OT_ERROR_NONE          Successfully started the frame transmission
-     * @retval OT_ERROR_INVALID_STATE The radio was disabled or transmitting.
+     * @retval kErrorNone          Successfully started the frame transmission
+     * @retval kErrorInvalidState  The radio was disabled or transmitting.
      *
      */
-    otError Send(void);
+    Error Send(void);
 
     /**
      * This method gets the number of transmit retries of last transmitted frame.
@@ -360,12 +381,12 @@
      * @param[in] aScanChannel   The channel to perform the energy scan on.
      * @param[in] aScanDuration  The duration, in milliseconds, for the channel to be scanned.
      *
-     * @retval OT_ERROR_NONE             Successfully started scanning the channel.
-     * @retval OT_ERROR_INVALID_STATE    The radio was disabled or transmitting.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  Energy scan is not supported (applicable in link-raw/radio mode only).
+     * @retval kErrorNone            Successfully started scanning the channel.
+     * @retval kErrorInvalidState    The radio was disabled or transmitting.
+     * @retval kErrorNotImplemented  Energy scan is not supported (applicable in link-raw/radio mode only).
      *
      */
-    otError EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration);
+    Error EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration);
 
     /**
      * This method returns the noise floor value (currently use the radio receive sensitivity value).
@@ -514,7 +535,7 @@
 #endif
     };
 
-    enum State
+    enum State : uint8_t
     {
         kStateDisabled,    ///< Radio is disabled.
         kStateSleep,       ///< Radio is in sleep.
@@ -575,9 +596,9 @@
     void BeginTransmit(void);
     void SampleRssi(void);
 
-    void HandleReceiveDone(RxFrame *aFrame, otError aError);
+    void HandleReceiveDone(RxFrame *aFrame, Error aError);
     void HandleTransmitStarted(TxFrame &aFrame);
-    void HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, otError aError);
+    void HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aError);
     void UpdateFrameCounterOnTxDone(const TxFrame &aFrame);
     void HandleEnergyScanDone(int8_t aMaxRssi);
 
diff --git a/src/core/mac/sub_mac_callbacks.cpp b/src/core/mac/sub_mac_callbacks.cpp
index 6fa84b7..6f070ec 100644
--- a/src/core/mac/sub_mac_callbacks.cpp
+++ b/src/core/mac/sub_mac_callbacks.cpp
@@ -51,7 +51,7 @@
 
 #if OPENTHREAD_FTD || OPENTHREAD_MTD
 
-void SubMac::Callbacks::ReceiveDone(RxFrame *aFrame, otError aError)
+void SubMac::Callbacks::ReceiveDone(RxFrame *aFrame, Error aError)
 {
 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE
     if (Get<LinkRaw>().IsEnabled())
@@ -72,14 +72,14 @@
 
 void SubMac::Callbacks::RecordFrameTransmitStatus(const TxFrame &aFrame,
                                                   const RxFrame *aAckFrame,
-                                                  otError        aError,
+                                                  Error          aError,
                                                   uint8_t        aRetryCount,
                                                   bool           aWillRetx)
 {
     Get<Mac>().RecordFrameTransmitStatus(aFrame, aAckFrame, aError, aRetryCount, aWillRetx);
 }
 
-void SubMac::Callbacks::TransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, otError aError)
+void SubMac::Callbacks::TransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aError)
 {
 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE
     if (Get<LinkRaw>().IsEnabled())
@@ -114,7 +114,7 @@
 
 #elif OPENTHREAD_RADIO
 
-void SubMac::Callbacks::ReceiveDone(RxFrame *aFrame, otError aError)
+void SubMac::Callbacks::ReceiveDone(RxFrame *aFrame, Error aError)
 {
     Get<LinkRaw>().InvokeReceiveDone(aFrame, aError);
 }
@@ -125,14 +125,14 @@
 
 void SubMac::Callbacks::RecordFrameTransmitStatus(const TxFrame &aFrame,
                                                   const RxFrame *aAckFrame,
-                                                  otError        aError,
+                                                  Error          aError,
                                                   uint8_t        aRetryCount,
                                                   bool           aWillRetx)
 {
     Get<LinkRaw>().RecordFrameTransmitStatus(aFrame, aAckFrame, aError, aRetryCount, aWillRetx);
 }
 
-void SubMac::Callbacks::TransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, otError aError)
+void SubMac::Callbacks::TransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aError)
 {
     Get<LinkRaw>().InvokeTransmitDone(aFrame, aAckFrame, aError);
 }
diff --git a/src/core/meshcop/announce_begin_client.cpp b/src/core/meshcop/announce_begin_client.cpp
index 23160f5..84ff93b 100644
--- a/src/core/meshcop/announce_begin_client.cpp
+++ b/src/core/meshcop/announce_begin_client.cpp
@@ -53,18 +53,18 @@
 {
 }
 
-otError AnnounceBeginClient::SendRequest(uint32_t            aChannelMask,
-                                         uint8_t             aCount,
-                                         uint16_t            aPeriod,
-                                         const Ip6::Address &aAddress)
+Error AnnounceBeginClient::SendRequest(uint32_t            aChannelMask,
+                                       uint8_t             aCount,
+                                       uint16_t            aPeriod,
+                                       const Ip6::Address &aAddress)
 {
-    otError                 error = OT_ERROR_NONE;
+    Error                   error = kErrorNone;
     MeshCoP::ChannelMaskTlv channelMask;
     Ip6::MessageInfo        messageInfo;
     Coap::Message *         message = nullptr;
 
-    VerifyOrExit(Get<MeshCoP::Commissioner>().IsActive(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit((message = MeshCoP::NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(Get<MeshCoP::Commissioner>().IsActive(), error = kErrorInvalidState);
+    VerifyOrExit((message = MeshCoP::NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsPost(aAddress, UriPath::kAnnounceBegin));
     SuccessOrExit(error = message->SetPayloadMarker());
diff --git a/src/core/meshcop/announce_begin_client.hpp b/src/core/meshcop/announce_begin_client.hpp
index 7702e05..a351a2f 100644
--- a/src/core/meshcop/announce_begin_client.hpp
+++ b/src/core/meshcop/announce_begin_client.hpp
@@ -64,11 +64,11 @@
      * @param[in]  aPeriod        The time between two successive MLE Announce transmissions (in milliseconds).
      * @param[in]  aAddress       The destination address.
      *
-     * @retval OT_ERROR_NONE     Successfully enqueued the Announce Begin message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers to generate a Announce Begin message.
+     * @retval kErrorNone    Successfully enqueued the Announce Begin message.
+     * @retval kErrorNoBufs  Insufficient buffers to generate a Announce Begin message.
      *
      */
-    otError SendRequest(uint32_t aChannelMask, uint8_t aCount, uint16_t aPeriod, const Ip6::Address &aAddress);
+    Error SendRequest(uint32_t aChannelMask, uint8_t aCount, uint16_t aPeriod, const Ip6::Address &aAddress);
 };
 
 /**
diff --git a/src/core/meshcop/border_agent.cpp b/src/core/meshcop/border_agent.cpp
index a3970f6..fb33647 100644
--- a/src/core/meshcop/border_agent.cpp
+++ b/src/core/meshcop/border_agent.cpp
@@ -62,7 +62,7 @@
     memcpy(mToken, aMessage.GetToken(), mTokenLength);
 }
 
-otError BorderAgent::ForwardContext::ToHeader(Coap::Message &aMessage, uint8_t aCode)
+Error BorderAgent::ForwardContext::ToHeader(Coap::Message &aMessage, uint8_t aCode)
 {
     if ((mType == Coap::kTypeNonConfirmable) || mSeparate)
     {
@@ -81,17 +81,17 @@
     return aMessage.SetToken(mToken, mTokenLength);
 }
 
-Coap::Message::Code BorderAgent::CoapCodeFromError(otError aError)
+Coap::Message::Code BorderAgent::CoapCodeFromError(Error aError)
 {
     Coap::Message::Code code;
 
     switch (aError)
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         code = Coap::kCodeChanged;
         break;
 
-    case OT_ERROR_PARSE:
+    case kErrorParse:
         code = Coap::kCodeBadRequest;
         break;
 
@@ -103,13 +103,13 @@
     return code;
 }
 
-void BorderAgent::SendErrorMessage(ForwardContext &aForwardContext, otError aError)
+void BorderAgent::SendErrorMessage(ForwardContext &aForwardContext, Error aError)
 {
-    otError           error   = OT_ERROR_NONE;
+    Error             error   = kErrorNone;
     Coap::CoapSecure &coaps   = Get<Coap::CoapSecure>();
     Coap::Message *   message = nullptr;
 
-    VerifyOrExit((message = NewMeshCoPMessage(coaps)) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(coaps)) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = aForwardContext.ToHeader(*message, CoapCodeFromError(aError)));
     SuccessOrExit(error = coaps.SendMessage(*message, coaps.GetMessageInfo()));
 
@@ -118,13 +118,13 @@
     LogError("send error CoAP message", error);
 }
 
-void BorderAgent::SendErrorMessage(const Coap::Message &aRequest, bool aSeparate, otError aError)
+void BorderAgent::SendErrorMessage(const Coap::Message &aRequest, bool aSeparate, Error aError)
 {
-    otError           error   = OT_ERROR_NONE;
+    Error             error   = kErrorNone;
     Coap::CoapSecure &coaps   = Get<Coap::CoapSecure>();
     Coap::Message *   message = nullptr;
 
-    VerifyOrExit((message = NewMeshCoPMessage(coaps)) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(coaps)) != nullptr, error = kErrorNoBufs);
 
     if (aRequest.IsNonConfirmable() || aSeparate)
     {
@@ -152,7 +152,7 @@
 void BorderAgent::HandleCoapResponse(void *               aContext,
                                      otMessage *          aMessage,
                                      const otMessageInfo *aMessageInfo,
-                                     otError              aResult)
+                                     Error                aResult)
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
 
@@ -162,13 +162,13 @@
                                                          aResult);
 }
 
-void BorderAgent::HandleCoapResponse(ForwardContext &aForwardContext, const Coap::Message *aResponse, otError aResult)
+void BorderAgent::HandleCoapResponse(ForwardContext &aForwardContext, const Coap::Message *aResponse, Error aResult)
 {
     Coap::Message *message = nullptr;
-    otError        error;
+    Error          error;
 
     SuccessOrExit(error = aResult);
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Coap::CoapSecure>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Coap::CoapSecure>())) != nullptr, error = kErrorNoBufs);
 
     if (aForwardContext.IsPetition() && aResponse->GetCode() == Coap::kCodeChanged)
     {
@@ -199,17 +199,16 @@
 
 exit:
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         FreeMessage(message);
 
-        otLogWarnMeshCoP("Commissioner request[%hu] failed: %s", aForwardContext.GetMessageId(),
-                         otThreadErrorToString(error));
+        otLogWarnMeshCoP("Commissioner request[%hu] failed: %s", aForwardContext.GetMessageId(), ErrorToString(error));
 
         SendErrorMessage(aForwardContext, error);
     }
 
-    GetInstance().HeapFree(&aForwardContext);
+    Instance::HeapFree(&aForwardContext);
 }
 
 template <Coap::Resource BorderAgent::*aResource>
@@ -284,7 +283,7 @@
     , mPendingSet(UriPath::kPendingSet, BorderAgent::HandleRequest<&BorderAgent::mPendingSet>, this)
     , mProxyTransmit(UriPath::kProxyTx, BorderAgent::HandleRequest<&BorderAgent::mProxyTransmit>, this)
     , mUdpReceiver(BorderAgent::HandleUdpReceive, this)
-    , mTimer(aInstance, HandleTimeout, this)
+    , mTimer(aInstance, HandleTimeout)
     , mState(kStateStopped)
 {
     mCommissionerAloc.InitAsThreadOriginRealmLocalScope();
@@ -316,13 +315,13 @@
     Message *           message = nullptr;
     Ip6::MessageInfo    messageInfo;
     uint16_t            offset;
-    otError             error;
+    Error               error;
     UdpEncapsulationTlv tlv;
 
     SuccessOrExit(error = Tlv::FindTlvOffset(aMessage, Tlv::kUdpEncapsulation, offset));
     SuccessOrExit(error = aMessage.Read(offset, tlv));
 
-    VerifyOrExit((message = Get<Ip6::Udp>().NewMessage(0)) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Ip6::Udp>().NewMessage(0)) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = message->SetLength(tlv.GetUdpLength()));
     aMessage.CopyTo(offset + sizeof(tlv), 0, tlv.GetUdpLength(), *message);
 
@@ -342,15 +341,15 @@
 
 bool BorderAgent::HandleUdpReceive(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError        error;
+    Error          error;
     Coap::Message *message = nullptr;
 
     VerifyOrExit(aMessageInfo.GetSockAddr() == mCommissionerAloc.GetAddress(),
-                 error = OT_ERROR_DESTINATION_ADDRESS_FILTERED);
+                 error = kErrorDestinationAddressFiltered);
 
-    VerifyOrExit(aMessage.GetLength() > 0, error = OT_ERROR_NONE);
+    VerifyOrExit(aMessage.GetLength() > 0, error = kErrorNone);
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Coap::CoapSecure>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Coap::CoapSecure>())) != nullptr, error = kErrorNoBufs);
 
     message->InitAsNonConfirmablePost();
     SuccessOrExit(error = message->AppendUriPathOptions(UriPath::kProxyRx));
@@ -382,16 +381,16 @@
     FreeMessageOnError(message, error);
     LogError("notify commissioner on ProxyRx (c/ur)", error);
 
-    return error != OT_ERROR_DESTINATION_ADDRESS_FILTERED;
+    return error != kErrorDestinationAddressFiltered;
 }
 
 void BorderAgent::HandleRelayReceive(const Coap::Message &aMessage)
 {
     Coap::Message *message = nullptr;
-    otError        error;
+    Error          error;
 
-    VerifyOrExit(aMessage.IsNonConfirmablePostRequest(), error = OT_ERROR_DROP);
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Coap::CoapSecure>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(aMessage.IsNonConfirmablePostRequest(), error = kErrorDrop);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Coap::CoapSecure>())) != nullptr, error = kErrorNoBufs);
 
     message->InitAsNonConfirmablePost();
     SuccessOrExit(error = message->AppendUriPathOptions(UriPath::kRelayRx));
@@ -408,9 +407,9 @@
     FreeMessageOnError(message, error);
 }
 
-otError BorderAgent::ForwardToCommissioner(Coap::Message &aForwardMessage, const Message &aMessage)
+Error BorderAgent::ForwardToCommissioner(Coap::Message &aForwardMessage, const Message &aMessage)
 {
-    otError  error  = OT_ERROR_NONE;
+    Error    error  = kErrorNone;
     uint16_t offset = 0;
 
     offset = aForwardMessage.GetLength();
@@ -429,11 +428,11 @@
 
 void BorderAgent::HandleKeepAlive(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError error;
+    Error error;
 
     error = ForwardToLeader(aMessage, aMessageInfo, UriPath::kLeaderKeepAlive, false, true);
 
-    if (error == OT_ERROR_NONE)
+    if (error == kErrorNone)
     {
         mTimer.Start(kKeepAliveTimeout);
     }
@@ -441,7 +440,7 @@
 
 void BorderAgent::HandleRelayTransmit(const Coap::Message &aMessage)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     uint16_t         joinerRouterRloc;
     Coap::Message *  message = nullptr;
     Ip6::MessageInfo messageInfo;
@@ -451,7 +450,7 @@
 
     SuccessOrExit(error = Tlv::Find<JoinerRouterLocatorTlv>(aMessage, joinerRouterRloc));
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsNonConfirmablePost(UriPath::kRelayTx));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -475,27 +474,27 @@
     LogError("send to joiner router request RelayTx (c/tx)", error);
 }
 
-otError BorderAgent::ForwardToLeader(const Coap::Message &   aMessage,
-                                     const Ip6::MessageInfo &aMessageInfo,
-                                     const char *            aPath,
-                                     bool                    aPetition,
-                                     bool                    aSeparate)
+Error BorderAgent::ForwardToLeader(const Coap::Message &   aMessage,
+                                   const Ip6::MessageInfo &aMessageInfo,
+                                   const char *            aPath,
+                                   bool                    aPetition,
+                                   bool                    aSeparate)
 {
-    otError          error          = OT_ERROR_NONE;
+    Error            error          = kErrorNone;
     ForwardContext * forwardContext = nullptr;
     Ip6::MessageInfo messageInfo;
     Coap::Message *  message = nullptr;
     uint16_t         offset  = 0;
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     if (aSeparate)
     {
         SuccessOrExit(error = Get<Coap::CoapSecure>().SendAck(aMessage, aMessageInfo));
     }
 
-    forwardContext = static_cast<ForwardContext *>(GetInstance().HeapCAlloc(1, sizeof(ForwardContext)));
-    VerifyOrExit(forwardContext != nullptr, error = OT_ERROR_NO_BUFS);
+    forwardContext = static_cast<ForwardContext *>(Instance::HeapCAlloc(1, sizeof(ForwardContext)));
+    VerifyOrExit(forwardContext != nullptr, error = kErrorNoBufs);
 
     forwardContext->Init(GetInstance(), aMessage, aPetition, aSeparate);
 
@@ -526,11 +525,11 @@
 exit:
     LogError("forward to leader", error);
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         if (forwardContext != nullptr)
         {
-            GetInstance().HeapFree(forwardContext);
+            Instance::HeapFree(forwardContext);
         }
 
         FreeMessage(message);
@@ -562,12 +561,12 @@
     }
 }
 
-otError BorderAgent::Start(void)
+Error BorderAgent::Start(void)
 {
-    otError           error;
+    Error             error;
     Coap::CoapSecure &coaps = Get<Coap::CoapSecure>();
 
-    VerifyOrExit(mState == kStateStopped, error = OT_ERROR_ALREADY);
+    VerifyOrExit(mState == kStateStopped, error = kErrorAlready);
 
     SuccessOrExit(error = coaps.Start(kBorderAgentUdpPort));
     SuccessOrExit(error = coaps.SetPsk(Get<KeyManager>().GetPskc().m8, OT_PSKC_MAX_SIZE));
@@ -594,7 +593,7 @@
 
 void BorderAgent::HandleTimeout(Timer &aTimer)
 {
-    aTimer.GetOwner<BorderAgent>().HandleTimeout();
+    aTimer.Get<BorderAgent>().HandleTimeout();
 }
 
 void BorderAgent::HandleTimeout(void)
@@ -606,12 +605,12 @@
     }
 }
 
-otError BorderAgent::Stop(void)
+Error BorderAgent::Stop(void)
 {
-    otError           error = OT_ERROR_NONE;
+    Error             error = kErrorNone;
     Coap::CoapSecure &coaps = Get<Coap::CoapSecure>();
 
-    VerifyOrExit(mState != kStateStopped, error = OT_ERROR_ALREADY);
+    VerifyOrExit(mState != kStateStopped, error = kErrorAlready);
 
     mTimer.Stop();
 
diff --git a/src/core/meshcop/border_agent.hpp b/src/core/meshcop/border_agent.hpp
index e785bd2..43ee78f 100644
--- a/src/core/meshcop/border_agent.hpp
+++ b/src/core/meshcop/border_agent.hpp
@@ -75,20 +75,20 @@
     /**
      * This method starts the Border Agent service.
      *
-     * @retval OT_ERROR_NONE    Successfully started the Border Agent service.
-     * @retval OT_ERROR_ALREADY Border Agent is already started.
+     * @retval kErrorNone    Successfully started the Border Agent service.
+     * @retval kErrorAlready Border Agent is already started.
      *
      */
-    otError Start(void);
+    Error Start(void);
 
     /**
      * This method stops the Border Agent service.
      *
-     * @retval OT_ERROR_NONE    Successfully stopped the Border Agent service.
-     * @retval OT_ERROR_ALREADY Border Agent is already stopped.
+     * @retval kErrorNone    Successfully stopped the Border Agent service.
+     * @retval kErrorAlready Border Agent is already stopped.
      *
      */
-    otError Stop(void);
+    Error Stop(void);
 
     /**
      * This method gets the state of the Border Agent service.
@@ -111,7 +111,7 @@
         void     Init(Instance &aInstance, const Coap::Message &aMessage, bool aPetition, bool aSeparate);
         bool     IsPetition(void) const { return mPetition; }
         uint16_t GetMessageId(void) const { return mMessageId; }
-        otError  ToHeader(Coap::Message &aMessage, uint8_t aCode);
+        Error    ToHeader(Coap::Message &aMessage, uint8_t aCode);
 
     private:
         uint16_t mMessageId;                             // The CoAP Message ID of the original request.
@@ -124,9 +124,9 @@
 
     void HandleNotifierEvents(Events aEvents);
 
-    Coap::Message::Code CoapCodeFromError(otError aError);
-    void                SendErrorMessage(ForwardContext &aForwardContext, otError aError);
-    void                SendErrorMessage(const Coap::Message &aRequest, bool aSeparate, otError aError);
+    Coap::Message::Code CoapCodeFromError(Error aError);
+    void                SendErrorMessage(ForwardContext &aForwardContext, Error aError);
+    void                SendErrorMessage(const Coap::Message &aRequest, bool aSeparate, Error aError);
 
     static void HandleConnected(bool aConnected, void *aContext);
     void        HandleConnected(bool aConnected);
@@ -140,15 +140,15 @@
     static void HandleCoapResponse(void *               aContext,
                                    otMessage *          aMessage,
                                    const otMessageInfo *aMessageInfo,
-                                   otError              aResult);
-    void        HandleCoapResponse(ForwardContext &aForwardContext, const Coap::Message *aResponse, otError aResult);
+                                   Error                aResult);
+    void        HandleCoapResponse(ForwardContext &aForwardContext, const Coap::Message *aResponse, Error aResult);
 
-    otError     ForwardToLeader(const Coap::Message &   aMessage,
+    Error       ForwardToLeader(const Coap::Message &   aMessage,
                                 const Ip6::MessageInfo &aMessageInfo,
                                 const char *            aPath,
                                 bool                    aPetition,
                                 bool                    aSeparate);
-    otError     ForwardToCommissioner(Coap::Message &aForwardMessage, const Message &aMessage);
+    Error       ForwardToCommissioner(Coap::Message &aForwardMessage, const Message &aMessage);
     void        HandleKeepAlive(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
     void        HandleRelayTransmit(const Coap::Message &aMessage);
     void        HandleRelayReceive(const Coap::Message &aMessage);
diff --git a/src/core/meshcop/commissioner.cpp b/src/core/meshcop/commissioner.cpp
index b9537c0..010d5ed 100644
--- a/src/core/meshcop/commissioner.cpp
+++ b/src/core/meshcop/commissioner.cpp
@@ -41,7 +41,6 @@
 #include "common/locator-getters.hpp"
 #include "common/logging.hpp"
 #include "common/string.hpp"
-#include "crypto/pbkdf2_cmac.h"
 #include "meshcop/joiner.hpp"
 #include "meshcop/joiner_router.hpp"
 #include "meshcop/meshcop.hpp"
@@ -62,8 +61,8 @@
     , mJoinerRloc(0)
     , mSessionId(0)
     , mTransmitAttempts(0)
-    , mJoinerExpirationTimer(aInstance, HandleJoinerExpirationTimer, this)
-    , mTimer(aInstance, HandleTimer, this)
+    , mJoinerExpirationTimer(aInstance, HandleJoinerExpirationTimer)
+    , mTimer(aInstance, HandleTimer)
     , mRelayReceive(UriPath::kRelayRx, &Commissioner::HandleRelayReceive, this)
     , mDatasetChanged(UriPath::kDatasetChanged, &Commissioner::HandleDatasetChanged, this)
     , mJoinerFinalize(UriPath::kJoinerFinalize, &Commissioner::HandleJoinerFinalize, this)
@@ -295,18 +294,18 @@
     SignalJoinerEvent(kJoinerEventRemoved, &joinerCopy);
 }
 
-otError Commissioner::Start(otCommissionerStateCallback  aStateCallback,
-                            otCommissionerJoinerCallback aJoinerCallback,
-                            void *                       aCallbackContext)
+Error Commissioner::Start(otCommissionerStateCallback  aStateCallback,
+                          otCommissionerJoinerCallback aJoinerCallback,
+                          void *                       aCallbackContext)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(Get<Mle::MleRouter>().IsAttached(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(mState == kStateDisabled, error = OT_ERROR_ALREADY);
+    VerifyOrExit(Get<Mle::MleRouter>().IsAttached(), error = kErrorInvalidState);
+    VerifyOrExit(mState == kStateDisabled, error = kErrorAlready);
 
 #if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
     error = Get<MeshCoP::BorderAgent>().Stop();
-    VerifyOrExit(error == OT_ERROR_NONE || error == OT_ERROR_ALREADY);
+    VerifyOrExit(error == kErrorNone || error == kErrorAlready);
 #endif
 
     SuccessOrExit(error = Get<Coap::CoapSecure>().Start(SendRelayTransmit, this));
@@ -321,7 +320,7 @@
     SetState(kStatePetition);
 
 exit:
-    if ((error != OT_ERROR_NONE) && (error != OT_ERROR_ALREADY))
+    if ((error != kErrorNone) && (error != kErrorAlready))
     {
         Get<Coap::CoapSecure>().Stop();
     }
@@ -330,12 +329,12 @@
     return error;
 }
 
-otError Commissioner::Stop(bool aResign)
+Error Commissioner::Stop(bool aResign)
 {
-    otError error      = OT_ERROR_NONE;
-    bool    needResign = false;
+    Error error      = kErrorNone;
+    bool  needResign = false;
 
-    VerifyOrExit(mState != kStateDisabled, error = OT_ERROR_ALREADY);
+    VerifyOrExit(mState != kStateDisabled, error = kErrorAlready);
 
     Get<Coap::CoapSecure>().Stop();
 
@@ -399,10 +398,10 @@
 
 void Commissioner::SendCommissionerSet(void)
 {
-    otError                error = OT_ERROR_NONE;
+    Error                  error = kErrorNone;
     otCommissioningDataset dataset;
 
-    VerifyOrExit(mState == kStateActive, error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(mState == kStateActive, error = kErrorInvalidState);
 
     memset(&dataset, 0, sizeof(dataset));
 
@@ -428,19 +427,19 @@
     SendCommissionerSet();
 }
 
-otError Commissioner::AddJoiner(const Mac::ExtAddress *aEui64,
-                                const JoinerDiscerner *aDiscerner,
-                                const char *           aPskd,
-                                uint32_t               aTimeout)
+Error Commissioner::AddJoiner(const Mac::ExtAddress *aEui64,
+                              const JoinerDiscerner *aDiscerner,
+                              const char *           aPskd,
+                              uint32_t               aTimeout)
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     Joiner *joiner;
 
-    VerifyOrExit(mState == kStateActive, error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(mState == kStateActive, error = kErrorInvalidState);
 
     if (aDiscerner != nullptr)
     {
-        VerifyOrExit(aDiscerner->IsValid(), error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit(aDiscerner->IsValid(), error = kErrorInvalidArgs);
         joiner = FindJoinerEntry(*aDiscerner);
     }
     else
@@ -453,7 +452,7 @@
         joiner = GetUnusedJoinerEntry();
     }
 
-    VerifyOrExit(joiner != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(joiner != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = joiner->mPskd.SetFrom(aPskd));
 
@@ -515,9 +514,9 @@
     return;
 }
 
-otError Commissioner::GetNextJoinerInfo(uint16_t &aIterator, otJoinerInfo &aJoinerInfo) const
+Error Commissioner::GetNextJoinerInfo(uint16_t &aIterator, otJoinerInfo &aJoinerInfo) const
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     while (aIterator < OT_ARRAY_LENGTH(mJoiners))
     {
@@ -530,22 +529,22 @@
         }
     }
 
-    error = OT_ERROR_NOT_FOUND;
+    error = kErrorNotFound;
 
 exit:
     return error;
 }
 
-otError Commissioner::RemoveJoiner(const Mac::ExtAddress *aEui64, const JoinerDiscerner *aDiscerner, uint32_t aDelay)
+Error Commissioner::RemoveJoiner(const Mac::ExtAddress *aEui64, const JoinerDiscerner *aDiscerner, uint32_t aDelay)
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     Joiner *joiner;
 
-    VerifyOrExit(mState == kStateActive, error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(mState == kStateActive, error = kErrorInvalidState);
 
     if (aDiscerner != nullptr)
     {
-        VerifyOrExit(aDiscerner->IsValid(), error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit(aDiscerner->IsValid(), error = kErrorInvalidArgs);
         joiner = FindJoinerEntry(*aDiscerner);
     }
     else
@@ -553,7 +552,7 @@
         joiner = FindJoinerEntry(aEui64);
     }
 
-    VerifyOrExit(joiner != nullptr, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(joiner != nullptr, error = kErrorNotFound);
 
     RemoveJoiner(*joiner, aDelay);
 
@@ -579,9 +578,9 @@
     }
 }
 
-otError Commissioner::SetProvisioningUrl(const char *aProvisioningUrl)
+Error Commissioner::SetProvisioningUrl(const char *aProvisioningUrl)
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     uint8_t len;
 
     if (aProvisioningUrl == nullptr)
@@ -590,11 +589,11 @@
         ExitNow();
     }
 
-    VerifyOrExit(IsValidUtf8String(aProvisioningUrl), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(IsValidUtf8String(aProvisioningUrl), error = kErrorInvalidArgs);
 
     len = static_cast<uint8_t>(StringLength(aProvisioningUrl, sizeof(mProvisioningUrl)));
 
-    VerifyOrExit(len < sizeof(mProvisioningUrl), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(len < sizeof(mProvisioningUrl), error = kErrorInvalidArgs);
 
     memcpy(mProvisioningUrl, aProvisioningUrl, len);
     mProvisioningUrl[len] = '\0';
@@ -605,7 +604,7 @@
 
 void Commissioner::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<Commissioner>().HandleTimer();
+    aTimer.Get<Commissioner>().HandleTimer();
 }
 
 void Commissioner::HandleTimer(void)
@@ -627,7 +626,7 @@
 
 void Commissioner::HandleJoinerExpirationTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<Commissioner>().HandleJoinerExpirationTimer();
+    aTimer.Get<Commissioner>().HandleJoinerExpirationTimer();
 }
 
 void Commissioner::HandleJoinerExpirationTimer(void)
@@ -683,14 +682,14 @@
     }
 }
 
-otError Commissioner::SendMgmtCommissionerGetRequest(const uint8_t *aTlvs, uint8_t aLength)
+Error Commissioner::SendMgmtCommissionerGetRequest(const uint8_t *aTlvs, uint8_t aLength)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     Coap::Message *  message;
     Ip6::MessageInfo messageInfo;
     MeshCoP::Tlv     tlv;
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsConfirmablePost(UriPath::kCommissionerGet));
 
@@ -723,7 +722,7 @@
 void Commissioner::HandleMgmtCommissionerGetResponse(void *               aContext,
                                                      otMessage *          aMessage,
                                                      const otMessageInfo *aMessageInfo,
-                                                     otError              aResult)
+                                                     Error                aResult)
 {
     static_cast<Commissioner *>(aContext)->HandleMgmtCommissionerGetResponse(
         static_cast<Coap::Message *>(aMessage), static_cast<const Ip6::MessageInfo *>(aMessageInfo), aResult);
@@ -731,26 +730,26 @@
 
 void Commissioner::HandleMgmtCommissionerGetResponse(Coap::Message *         aMessage,
                                                      const Ip6::MessageInfo *aMessageInfo,
-                                                     otError                 aResult)
+                                                     Error                   aResult)
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
 
-    VerifyOrExit(aResult == OT_ERROR_NONE && aMessage->GetCode() == Coap::kCodeChanged);
+    VerifyOrExit(aResult == kErrorNone && aMessage->GetCode() == Coap::kCodeChanged);
     otLogInfoMeshCoP("received MGMT_COMMISSIONER_GET response");
 
 exit:
     return;
 }
 
-otError Commissioner::SendMgmtCommissionerSetRequest(const otCommissioningDataset &aDataset,
-                                                     const uint8_t *               aTlvs,
-                                                     uint8_t                       aLength)
+Error Commissioner::SendMgmtCommissionerSetRequest(const otCommissioningDataset &aDataset,
+                                                   const uint8_t *               aTlvs,
+                                                   uint8_t                       aLength)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     Coap::Message *  message;
     Ip6::MessageInfo messageInfo;
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsConfirmablePost(UriPath::kCommissionerSet));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -803,7 +802,7 @@
 void Commissioner::HandleMgmtCommissionerSetResponse(void *               aContext,
                                                      otMessage *          aMessage,
                                                      const otMessageInfo *aMessageInfo,
-                                                     otError              aResult)
+                                                     Error                aResult)
 {
     static_cast<Commissioner *>(aContext)->HandleMgmtCommissionerSetResponse(
         static_cast<Coap::Message *>(aMessage), static_cast<const Ip6::MessageInfo *>(aMessageInfo), aResult);
@@ -811,27 +810,27 @@
 
 void Commissioner::HandleMgmtCommissionerSetResponse(Coap::Message *         aMessage,
                                                      const Ip6::MessageInfo *aMessageInfo,
-                                                     otError                 aResult)
+                                                     Error                   aResult)
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
 
-    VerifyOrExit(aResult == OT_ERROR_NONE && aMessage->GetCode() == Coap::kCodeChanged);
+    VerifyOrExit(aResult == kErrorNone && aMessage->GetCode() == Coap::kCodeChanged);
     otLogInfoMeshCoP("received MGMT_COMMISSIONER_SET response");
 
 exit:
     return;
 }
 
-otError Commissioner::SendPetition(void)
+Error Commissioner::SendPetition(void)
 {
-    otError           error   = OT_ERROR_NONE;
+    Error             error   = kErrorNone;
     Coap::Message *   message = nullptr;
     Ip6::MessageInfo  messageInfo;
     CommissionerIdTlv commissionerId;
 
     mTransmitAttempts++;
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsConfirmablePost(UriPath::kLeaderPetition));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -857,7 +856,7 @@
 void Commissioner::HandleLeaderPetitionResponse(void *               aContext,
                                                 otMessage *          aMessage,
                                                 const otMessageInfo *aMessageInfo,
-                                                otError              aResult)
+                                                Error                aResult)
 {
     static_cast<Commissioner *>(aContext)->HandleLeaderPetitionResponse(
         static_cast<Coap::Message *>(aMessage), static_cast<const Ip6::MessageInfo *>(aMessageInfo), aResult);
@@ -865,7 +864,7 @@
 
 void Commissioner::HandleLeaderPetitionResponse(Coap::Message *         aMessage,
                                                 const Ip6::MessageInfo *aMessageInfo,
-                                                otError                 aResult)
+                                                Error                   aResult)
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
 
@@ -873,7 +872,7 @@
     bool    retransmit = false;
 
     VerifyOrExit(mState != kStateActive);
-    VerifyOrExit(aResult == OT_ERROR_NONE && aMessage->GetCode() == Coap::kCodeChanged,
+    VerifyOrExit(aResult == kErrorNone && aMessage->GetCode() == Coap::kCodeChanged,
                  retransmit = (mState == kStatePetition));
 
     otLogInfoMeshCoP("received Leader Petition response");
@@ -922,11 +921,11 @@
 
 void Commissioner::SendKeepAlive(uint16_t aSessionId)
 {
-    otError          error   = OT_ERROR_NONE;
+    Error            error   = kErrorNone;
     Coap::Message *  message = nullptr;
     Ip6::MessageInfo messageInfo;
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsConfirmablePost(UriPath::kLeaderKeepAlive));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -952,7 +951,7 @@
 void Commissioner::HandleLeaderKeepAliveResponse(void *               aContext,
                                                  otMessage *          aMessage,
                                                  const otMessageInfo *aMessageInfo,
-                                                 otError              aResult)
+                                                 Error                aResult)
 {
     static_cast<Commissioner *>(aContext)->HandleLeaderKeepAliveResponse(
         static_cast<Coap::Message *>(aMessage), static_cast<const Ip6::MessageInfo *>(aMessageInfo), aResult);
@@ -960,14 +959,14 @@
 
 void Commissioner::HandleLeaderKeepAliveResponse(Coap::Message *         aMessage,
                                                  const Ip6::MessageInfo *aMessageInfo,
-                                                 otError                 aResult)
+                                                 Error                   aResult)
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
 
     uint8_t state;
 
     VerifyOrExit(mState == kStateActive);
-    VerifyOrExit(aResult == OT_ERROR_NONE && aMessage->GetCode() == Coap::kCodeChanged,
+    VerifyOrExit(aResult == kErrorNone && aMessage->GetCode() == Coap::kCodeChanged,
                  IgnoreError(Stop(/* aResign */ false)));
 
     otLogInfoMeshCoP("received Leader keep-alive response");
@@ -991,7 +990,7 @@
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
 
-    otError                  error;
+    Error                    error;
     uint16_t                 joinerPort;
     Ip6::InterfaceIdentifier joinerIid;
     uint16_t                 joinerRloc;
@@ -999,7 +998,7 @@
     uint16_t                 offset;
     uint16_t                 length;
 
-    VerifyOrExit(mState == kStateActive, error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(mState == kStateActive, error = kErrorInvalidState);
 
     VerifyOrExit(aMessage.IsNonConfirmablePostRequest());
 
@@ -1008,7 +1007,7 @@
     SuccessOrExit(error = Tlv::Find<JoinerRouterLocatorTlv>(aMessage, joinerRloc));
 
     SuccessOrExit(error = Tlv::FindTlvValueOffset(aMessage, Tlv::kJoinerDtlsEncapsulation, offset, length));
-    VerifyOrExit(length <= aMessage.GetLength() - offset, error = OT_ERROR_PARSE);
+    VerifyOrExit(length <= aMessage.GetLength() - offset, error = kErrorParse);
 
     if (!Get<Coap::CoapSecure>().IsConnectionActive())
     {
@@ -1085,7 +1084,7 @@
 
     otLogInfoMeshCoP("received joiner finalize");
 
-    if (Tlv::FindTlv(aMessage, provisioningUrl) == OT_ERROR_NONE)
+    if (Tlv::FindTlv(aMessage, provisioningUrl) == kErrorNone)
     {
         uint8_t len = static_cast<uint8_t>(StringLength(mProvisioningUrl, sizeof(mProvisioningUrl)));
 
@@ -1112,11 +1111,11 @@
 
 void Commissioner::SendJoinFinalizeResponse(const Coap::Message &aRequest, StateTlv::State aState)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     Ip6::MessageInfo joinerMessageInfo;
     Coap::Message *  message;
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Coap::CoapSecure>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Coap::CoapSecure>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->SetDefaultResponseHeader(aRequest));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -1153,22 +1152,22 @@
     FreeMessageOnError(message, error);
 }
 
-otError Commissioner::SendRelayTransmit(void *aContext, Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
+Error Commissioner::SendRelayTransmit(void *aContext, Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
     return static_cast<Commissioner *>(aContext)->SendRelayTransmit(aMessage, aMessageInfo);
 }
 
-otError Commissioner::SendRelayTransmit(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
+Error Commissioner::SendRelayTransmit(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
 
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     ExtendedTlv      tlv;
     Coap::Message *  message;
     uint16_t         offset;
     Ip6::MessageInfo messageInfo;
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     message->InitAsNonConfirmablePost();
     SuccessOrExit(error = message->AppendUriPathOptions(UriPath::kRelayTx));
@@ -1222,22 +1221,17 @@
 
 const char *Commissioner::StateToString(State aState)
 {
-    const char *str = "Unknown";
+    static const char *const kStateStrings[] = {
+        "disabled", // (0) kStateDisabled
+        "petition", // (1) kStatePetition
+        "active",   // (2) kStateActive
+    };
 
-    switch (aState)
-    {
-    case kStateDisabled:
-        str = "disabled";
-        break;
-    case kStatePetition:
-        str = "petition";
-        break;
-    case kStateActive:
-        str = "active";
-        break;
-    }
+    static_assert(kStateDisabled == 0, "kStateDisabled value is incorrect");
+    static_assert(kStatePetition == 1, "kStatePetition value is incorrect");
+    static_assert(kStateActive == 2, "kStateActive value is incorrect");
 
-    return str;
+    return kStateStrings[aState];
 }
 
 void Commissioner::LogJoinerEntry(const char *aAction, const Joiner &aJoiner) const
diff --git a/src/core/meshcop/commissioner.hpp b/src/core/meshcop/commissioner.hpp
index 4bfde65..8a21c43 100644
--- a/src/core/meshcop/commissioner.hpp
+++ b/src/core/meshcop/commissioner.hpp
@@ -86,25 +86,25 @@
      * @param[in]  aJoinerCallback   A pointer to a function that is called when a joiner event occurs.
      * @param[in]  aCallbackContext  A pointer to application-specific context.
      *
-     * @retval OT_ERROR_NONE           Successfully started the Commissioner service.
-     * @retval OT_ERROR_ALREADY        Commissioner is already started.
-     * @retval OT_ERROR_INVALID_STATE  Device is not currently attached to a network.
+     * @retval kErrorNone           Successfully started the Commissioner service.
+     * @retval kErrorAlready        Commissioner is already started.
+     * @retval kErrorInvalidState   Device is not currently attached to a network.
      *
      */
-    otError Start(otCommissionerStateCallback  aStateCallback,
-                  otCommissionerJoinerCallback aJoinerCallback,
-                  void *                       aCallbackContext);
+    Error Start(otCommissionerStateCallback  aStateCallback,
+                otCommissionerJoinerCallback aJoinerCallback,
+                void *                       aCallbackContext);
 
     /**
      * This method stops the Commissioner service.
      *
      * @param[in]  aResign      Whether send LEAD_KA.req to resign as Commissioner
      *
-     * @retval OT_ERROR_NONE     Successfully stopped the Commissioner service.
-     * @retval OT_ERROR_ALREADY  Commissioner is already stopped.
+     * @retval kErrorNone     Successfully stopped the Commissioner service.
+     * @retval kErrorAlready  Commissioner is already stopped.
      *
      */
-    otError Stop(bool aResign);
+    Error Stop(bool aResign);
 
     /**
      * This method clears all Joiner entries.
@@ -118,12 +118,12 @@
      * @param[in]  aPskd         A pointer to the PSKd.
      * @param[in]  aTimeout      A time after which a Joiner is automatically removed, in seconds.
      *
-     * @retval OT_ERROR_NONE           Successfully added the Joiner.
-     * @retval OT_ERROR_NO_BUFS        No buffers available to add the Joiner.
-     * @retval OT_ERROR_INVALID_STATE  Commissioner service is not started.
+     * @retval kErrorNone          Successfully added the Joiner.
+     * @retval kErrorNoBufs        No buffers available to add the Joiner.
+     * @retval kErrorInvalidState  Commissioner service is not started.
      *
      */
-    otError AddJoinerAny(const char *aPskd, uint32_t aTimeout) { return AddJoiner(nullptr, nullptr, aPskd, aTimeout); }
+    Error AddJoinerAny(const char *aPskd, uint32_t aTimeout) { return AddJoiner(nullptr, nullptr, aPskd, aTimeout); }
 
     /**
      * This method adds a Joiner entry.
@@ -132,12 +132,12 @@
      * @param[in]  aPskd         A pointer to the PSKd.
      * @param[in]  aTimeout      A time after which a Joiner is automatically removed, in seconds.
      *
-     * @retval OT_ERROR_NONE           Successfully added the Joiner.
-     * @retval OT_ERROR_NO_BUFS        No buffers available to add the Joiner.
-     * @retval OT_ERROR_INVALID_STATE  Commissioner service is not started.
+     * @retval kErrorNone          Successfully added the Joiner.
+     * @retval kErrorNoBufs        No buffers available to add the Joiner.
+     * @retval kErrorInvalidState  Commissioner service is not started.
      *
      */
-    otError AddJoiner(const Mac::ExtAddress &aEui64, const char *aPskd, uint32_t aTimeout)
+    Error AddJoiner(const Mac::ExtAddress &aEui64, const char *aPskd, uint32_t aTimeout)
     {
         return AddJoiner(&aEui64, nullptr, aPskd, aTimeout);
     }
@@ -149,12 +149,12 @@
      * @param[in]  aPskd       A pointer to the PSKd.
      * @param[in]  aTimeout    A time after which a Joiner is automatically removed, in seconds.
      *
-     * @retval OT_ERROR_NONE           Successfully added the Joiner.
-     * @retval OT_ERROR_NO_BUFS        No buffers available to add the Joiner.
-     * @retval OT_ERROR_INVALID_STATE  Commissioner service is not started.
+     * @retval kErrorNone          Successfully added the Joiner.
+     * @retval kErrorNoBufs        No buffers available to add the Joiner.
+     * @retval kErrorInvalidState  Commissioner service is not started.
      *
      */
-    otError AddJoiner(const JoinerDiscerner &aDiscerner, const char *aPskd, uint32_t aTimeout)
+    Error AddJoiner(const JoinerDiscerner &aDiscerner, const char *aPskd, uint32_t aTimeout)
     {
         return AddJoiner(nullptr, &aDiscerner, aPskd, aTimeout);
     }
@@ -165,23 +165,23 @@
      * @param[inout]    aIterator   A iterator to the index of the joiner.
      * @param[out]      aJoiner     A reference to Joiner info.
      *
-     * @retval OT_ERROR_NONE        Successfully get the Joiner info.
-     * @retval OT_ERROR_NOT_FOUND   Not found next Joiner.
+     * @retval kErrorNone       Successfully get the Joiner info.
+     * @retval kErrorNotFound   Not found next Joiner.
      *
      */
-    otError GetNextJoinerInfo(uint16_t &aIterator, otJoinerInfo &aJoiner) const;
+    Error GetNextJoinerInfo(uint16_t &aIterator, otJoinerInfo &aJoiner) const;
 
     /**
      * This method removes a Joiner entry accepting any Joiner.
      *
      * @param[in]  aDelay         The delay to remove Joiner (in seconds).
      *
-     * @retval OT_ERROR_NONE           Successfully added the Joiner.
-     * @retval OT_ERROR_NOT_FOUND      The Joiner entry accepting any Joiner was not found.
-     * @retval OT_ERROR_INVALID_STATE  Commissioner service is not started.
+     * @retval kErrorNone          Successfully added the Joiner.
+     * @retval kErrorNotFound      The Joiner entry accepting any Joiner was not found.
+     * @retval kErrorInvalidState  Commissioner service is not started.
      *
      */
-    otError RemoveJoinerAny(uint32_t aDelay) { return RemoveJoiner(nullptr, nullptr, aDelay); }
+    Error RemoveJoinerAny(uint32_t aDelay) { return RemoveJoiner(nullptr, nullptr, aDelay); }
 
     /**
      * This method removes a Joiner entry.
@@ -189,12 +189,12 @@
      * @param[in]  aEui64         The Joiner's IEEE EUI-64.
      * @param[in]  aDelay         The delay to remove Joiner (in seconds).
      *
-     * @retval OT_ERROR_NONE           Successfully added the Joiner.
-     * @retval OT_ERROR_NOT_FOUND      The Joiner specified by @p aEui64 was not found.
-     * @retval OT_ERROR_INVALID_STATE  Commissioner service is not started.
+     * @retval kErrorNone          Successfully added the Joiner.
+     * @retval kErrorNotFound      The Joiner specified by @p aEui64 was not found.
+     * @retval kErrorInvalidState  Commissioner service is not started.
      *
      */
-    otError RemoveJoiner(const Mac::ExtAddress &aEui64, uint32_t aDelay)
+    Error RemoveJoiner(const Mac::ExtAddress &aEui64, uint32_t aDelay)
     {
         return RemoveJoiner(&aEui64, nullptr, aDelay);
     }
@@ -205,12 +205,12 @@
      * @param[in]  aDiscerner     A Joiner Discerner.
      * @param[in]  aDelay         The delay to remove Joiner (in seconds).
      *
-     * @retval OT_ERROR_NONE           Successfully added the Joiner.
-     * @retval OT_ERROR_NOT_FOUND      The Joiner specified by @p aEui64 was not found.
-     * @retval OT_ERROR_INVALID_STATE  Commissioner service is not started.
+     * @retval kErrorNone          Successfully added the Joiner.
+     * @retval kErrorNotFound      The Joiner specified by @p aEui64 was not found.
+     * @retval kErrorInvalidState  Commissioner service is not started.
      *
      */
-    otError RemoveJoiner(const JoinerDiscerner &aDiscerner, uint32_t aDelay)
+    Error RemoveJoiner(const JoinerDiscerner &aDiscerner, uint32_t aDelay)
     {
         return RemoveJoiner(nullptr, &aDiscerner, aDelay);
     }
@@ -228,11 +228,11 @@
      *
      * @param[in]  aProvisioningUrl  A pointer to the Provisioning URL (may be nullptr to set URL to empty string).
      *
-     * @retval OT_ERROR_NONE          Successfully set the Provisioning URL.
-     * @retval OT_ERROR_INVALID_ARGS  @p aProvisioningUrl is invalid (too long).
+     * @retval kErrorNone         Successfully set the Provisioning URL.
+     * @retval kErrorInvalidArgs  @p aProvisioningUrl is invalid (too long).
      *
      */
-    otError SetProvisioningUrl(const char *aProvisioningUrl);
+    Error SetProvisioningUrl(const char *aProvisioningUrl);
 
     /**
      * This method returns the Commissioner Session ID.
@@ -272,12 +272,12 @@
      * @param[in]  aTlvs        A pointer to Commissioning Data TLVs.
      * @param[in]  aLength      The length of requested TLVs in bytes.
      *
-     * @retval OT_ERROR_NONE           Send MGMT_COMMISSIONER_GET successfully.
-     * @retval OT_ERROR_NO_BUFS        Insufficient buffer space to send.
-     * @retval OT_ERROR_INVALID_STATE  Commissioner service is not started.
+     * @retval kErrorNone          Send MGMT_COMMISSIONER_GET successfully.
+     * @retval kErrorNoBufs        Insufficient buffer space to send.
+     * @retval kErrorInvalidState  Commissioner service is not started.
      *
      */
-    otError SendMgmtCommissionerGetRequest(const uint8_t *aTlvs, uint8_t aLength);
+    Error SendMgmtCommissionerGetRequest(const uint8_t *aTlvs, uint8_t aLength);
 
     /**
      * This method sends MGMT_COMMISSIONER_SET.
@@ -286,14 +286,12 @@
      * @param[in]  aTlvs        A pointer to user specific Commissioning Data TLVs.
      * @param[in]  aLength      The length of user specific TLVs in bytes.
      *
-     * @retval OT_ERROR_NONE           Send MGMT_COMMISSIONER_SET successfully.
-     * @retval OT_ERROR_NO_BUFS        Insufficient buffer space to send.
-     * @retval OT_ERROR_INVALID_STATE  Commissioner service is not started.
+     * @retval kErrorNone          Send MGMT_COMMISSIONER_SET successfully.
+     * @retval kErrorNoBufs        Insufficient buffer space to send.
+     * @retval kErrorInvalidState  Commissioner service is not started.
      *
      */
-    otError SendMgmtCommissionerSetRequest(const otCommissioningDataset &aDataset,
-                                           const uint8_t *               aTlvs,
-                                           uint8_t                       aLength);
+    Error SendMgmtCommissionerSetRequest(const otCommissioningDataset &aDataset, const uint8_t *aTlvs, uint8_t aLength);
 
     /**
      * This method returns a reference to the AnnounceBeginClient instance.
@@ -374,12 +372,12 @@
     Joiner *FindBestMatchingJoinerEntry(const Mac::ExtAddress &aReceivedJoinerId);
     void    RemoveJoinerEntry(Joiner &aJoiner);
 
-    otError AddJoiner(const Mac::ExtAddress *aEui64,
-                      const JoinerDiscerner *aDiscerner,
-                      const char *           aPskd,
-                      uint32_t               aTimeout);
-    otError RemoveJoiner(const Mac::ExtAddress *aEui64, const JoinerDiscerner *aDiscerner, uint32_t aDelay);
-    void    RemoveJoiner(Joiner &aJoiner, uint32_t aDelay);
+    Error AddJoiner(const Mac::ExtAddress *aEui64,
+                    const JoinerDiscerner *aDiscerner,
+                    const char *           aPskd,
+                    uint32_t               aTimeout);
+    Error RemoveJoiner(const Mac::ExtAddress *aEui64, const JoinerDiscerner *aDiscerner, uint32_t aDelay);
+    void  RemoveJoiner(Joiner &aJoiner, uint32_t aDelay);
 
     void AddCoapResources(void);
     void RemoveCoapResources(void);
@@ -395,27 +393,27 @@
     static void HandleMgmtCommissionerSetResponse(void *               aContext,
                                                   otMessage *          aMessage,
                                                   const otMessageInfo *aMessageInfo,
-                                                  otError              aResult);
+                                                  Error                aResult);
     void        HandleMgmtCommissionerSetResponse(Coap::Message *         aMessage,
                                                   const Ip6::MessageInfo *aMessageInfo,
-                                                  otError                 aResult);
+                                                  Error                   aResult);
     static void HandleMgmtCommissionerGetResponse(void *               aContext,
                                                   otMessage *          aMessage,
                                                   const otMessageInfo *aMessageInfo,
-                                                  otError              aResult);
+                                                  Error                aResult);
     void        HandleMgmtCommissionerGetResponse(Coap::Message *         aMessage,
                                                   const Ip6::MessageInfo *aMessageInfo,
-                                                  otError                 aResult);
+                                                  Error                   aResult);
     static void HandleLeaderPetitionResponse(void *               aContext,
                                              otMessage *          aMessage,
                                              const otMessageInfo *aMessageInfo,
-                                             otError              aResult);
-    void HandleLeaderPetitionResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, otError aResult);
+                                             Error                aResult);
+    void HandleLeaderPetitionResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, Error aResult);
     static void HandleLeaderKeepAliveResponse(void *               aContext,
                                               otMessage *          aMessage,
                                               const otMessageInfo *aMessageInfo,
-                                              otError              aResult);
-    void HandleLeaderKeepAliveResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, otError aResult);
+                                              Error                aResult);
+    void HandleLeaderKeepAliveResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, Error aResult);
 
     static void HandleCoapsConnected(bool aConnected, void *aContext);
     void        HandleCoapsConnected(bool aConnected);
@@ -431,14 +429,14 @@
 
     void SendJoinFinalizeResponse(const Coap::Message &aRequest, StateTlv::State aState);
 
-    static otError SendRelayTransmit(void *aContext, Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
-    otError        SendRelayTransmit(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    static Error SendRelayTransmit(void *aContext, Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    Error        SendRelayTransmit(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
 
-    void    ComputeBloomFilter(SteeringData &aSteeringData) const;
-    void    SendCommissionerSet(void);
-    otError SendPetition(void);
-    void    SendKeepAlive(void);
-    void    SendKeepAlive(uint16_t aSessionId);
+    void  ComputeBloomFilter(SteeringData &aSteeringData) const;
+    void  SendCommissionerSet(void);
+    Error SendPetition(void);
+    void  SendKeepAlive(void);
+    void  SendKeepAlive(uint16_t aSessionId);
 
     void SetState(State aState);
     void SignalJoinerEvent(JoinerEvent aEvent, const Joiner *aJoiner) const;
diff --git a/src/core/meshcop/dataset.cpp b/src/core/meshcop/dataset.cpp
index 2a7f05b..e9216e2 100644
--- a/src/core/meshcop/dataset.cpp
+++ b/src/core/meshcop/dataset.cpp
@@ -48,9 +48,9 @@
 namespace ot {
 namespace MeshCoP {
 
-otError Dataset::Info::GenerateRandom(Instance &aInstance)
+Error Dataset::Info::GenerateRandom(Instance &aInstance)
 {
-    otError          error;
+    Error            error;
     Mac::ChannelMask supportedChannels = aInstance.Get<Mac::Mac>().GetSupportedChannelMask();
     Mac::ChannelMask preferredChannels(aInstance.Get<Radio>().GetPreferredChannelMask());
 
@@ -285,9 +285,9 @@
     memcpy(mTlvs, aDataset.mTlvs, mLength);
 }
 
-otError Dataset::SetFrom(const Info &aDatasetInfo)
+Error Dataset::SetFrom(const Info &aDatasetInfo)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     if (aDatasetInfo.IsActiveTimestampPresent())
     {
@@ -400,9 +400,9 @@
     IgnoreError(SetTlv((mType == kActive) ? Tlv::kActiveTimestamp : Tlv::kPendingTimestamp, aTimestamp));
 }
 
-otError Dataset::SetTlv(Tlv::Type aType, const void *aValue, uint8_t aLength)
+Error Dataset::SetTlv(Tlv::Type aType, const void *aValue, uint8_t aLength)
 {
-    otError  error          = OT_ERROR_NONE;
+    Error    error          = kErrorNone;
     uint16_t bytesAvailable = sizeof(mTlvs) - mLength;
     Tlv *    old            = GetTlv(aType);
     Tlv      tlv;
@@ -412,7 +412,7 @@
         bytesAvailable += sizeof(Tlv) + old->GetLength();
     }
 
-    VerifyOrExit(sizeof(Tlv) + aLength <= bytesAvailable, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(sizeof(Tlv) + aLength <= bytesAvailable, error = kErrorNoBufs);
 
     if (old != nullptr)
     {
@@ -433,20 +433,20 @@
     return error;
 }
 
-otError Dataset::SetTlv(const Tlv &aTlv)
+Error Dataset::SetTlv(const Tlv &aTlv)
 {
     return SetTlv(aTlv.GetType(), aTlv.GetValue(), aTlv.GetLength());
 }
 
-otError Dataset::Set(const Message &aMessage, uint16_t aOffset, uint8_t aLength)
+Error Dataset::Set(const Message &aMessage, uint16_t aOffset, uint8_t aLength)
 {
-    otError error = OT_ERROR_INVALID_ARGS;
+    Error error = kErrorInvalidArgs;
 
     SuccessOrExit(aMessage.Read(aOffset, mTlvs, aLength));
     mLength = aLength;
 
     mUpdateTime = TimerMilli::GetNow();
-    error       = OT_ERROR_NONE;
+    error       = kErrorNone;
 
 exit:
     return error;
@@ -463,9 +463,9 @@
     return;
 }
 
-otError Dataset::AppendMleDatasetTlv(Message &aMessage) const
+Error Dataset::AppendMleDatasetTlv(Message &aMessage) const
 {
-    otError        error = OT_ERROR_NONE;
+    Error          error = kErrorNone;
     Mle::Tlv       tlv;
     Mle::Tlv::Type type;
 
@@ -519,13 +519,13 @@
     mLength -= length;
 }
 
-otError Dataset::ApplyConfiguration(Instance &aInstance, bool *aIsMasterKeyUpdated) const
+Error Dataset::ApplyConfiguration(Instance &aInstance, bool *aIsMasterKeyUpdated) const
 {
     Mac::Mac &  mac        = aInstance.Get<Mac::Mac>();
     KeyManager &keyManager = aInstance.Get<KeyManager>();
-    otError     error      = OT_ERROR_NONE;
+    Error       error      = kErrorNone;
 
-    VerifyOrExit(IsValid(), error = OT_ERROR_PARSE);
+    VerifyOrExit(IsValid(), error = kErrorParse);
 
     if (aIsMasterKeyUpdated)
     {
@@ -542,10 +542,10 @@
 
             error = mac.SetPanChannel(channel);
 
-            if (error != OT_ERROR_NONE)
+            if (error != kErrorNone)
             {
                 otLogWarnMeshCoP("DatasetManager::ApplyConfiguration() Failed to set channel to %d (%s)", channel,
-                                 otThreadErrorToString(error));
+                                 ErrorToString(error));
                 ExitNow();
             }
 
diff --git a/src/core/meshcop/dataset.hpp b/src/core/meshcop/dataset.hpp
index 3aa3ec9..b258e01 100644
--- a/src/core/meshcop/dataset.hpp
+++ b/src/core/meshcop/dataset.hpp
@@ -603,10 +603,10 @@
          *
          * @param[in] aInstance    The OpenThread instance.
          *
-         * @retval OT_ERROR_NONE If the Dataset was generated successfully.
+         * @retval kErrorNone If the Dataset was generated successfully.
          *
          */
-        otError GenerateRandom(Instance &aInstance);
+        Error GenerateRandom(Instance &aInstance);
 
         /**
          * This method checks whether the Dataset is a subset of another one, i.e., all the components in the current
@@ -764,11 +764,11 @@
      *
      * @param[in]  aTlv  A reference to the TLV.
      *
-     * @retval OT_ERROR_NONE     Successfully set the TLV.
-     * @retval OT_ERROR_NO_BUFS  Could not set the TLV due to insufficient buffer space.
+     * @retval kErrorNone    Successfully set the TLV.
+     * @retval kErrorNoBufs  Could not set the TLV due to insufficient buffer space.
      *
      */
-    otError SetTlv(const Tlv &aTlv);
+    Error SetTlv(const Tlv &aTlv);
 
     /**
      * This method sets a TLV with a given TLV Type and Value.
@@ -777,11 +777,11 @@
      * @param[in] aValue    A pointer to TLV Value.
      * @param[in] aLength   The TLV Length in bytes (length of @p aValue).
      *
-     * @retval OT_ERROR_NONE     Successfully set the TLV.
-     * @retval OT_ERROR_NO_BUFS  Could not set the TLV due to insufficient buffer space.
+     * @retval kErrorNone    Successfully set the TLV.
+     * @retval kErrorNoBufs  Could not set the TLV due to insufficient buffer space.
      *
      */
-    otError SetTlv(Tlv::Type aType, const void *aValue, uint8_t aLength);
+    Error SetTlv(Tlv::Type aType, const void *aValue, uint8_t aLength);
 
     /**
      * This template method sets a TLV with a given TLV Type and Value.
@@ -791,11 +791,11 @@
      * @param[in] aType     The TLV Type.
      * @param[in] aValue    The TLV Value (of type `ValueType`).
      *
-     * @retval OT_ERROR_NONE     Successfully set the TLV.
-     * @retval OT_ERROR_NO_BUFS  Could not set the TLV due to insufficient buffer space.
+     * @retval kErrorNone    Successfully set the TLV.
+     * @retval kErrorNoBufs  Could not set the TLV due to insufficient buffer space.
      *
      */
-    template <typename ValueType> otError SetTlv(Tlv::Type aType, const ValueType &aValue)
+    template <typename ValueType> Error SetTlv(Tlv::Type aType, const ValueType &aValue)
     {
         static_assert(!TypeTraits::IsPointer<ValueType>::kValue, "ValueType must not be a pointer");
 
@@ -809,11 +809,11 @@
      * @param[in]  aOffset   The message buffer offset where the dataset starts.
      * @param[in]  aLength   The TLVs length in the message buffer in bytes.
      *
-     * @retval OT_ERROR_NONE          Successfully set the Dataset.
-     * @retval OT_ERROR_INVALID_ARGS  The values of @p aOffset and @p aLength are not valid for @p aMessage.
+     * @retval kErrorNone         Successfully set the Dataset.
+     * @retval kErrorInvalidArgs  The values of @p aOffset and @p aLength are not valid for @p aMessage.
      *
      */
-    otError Set(const Message &aMessage, uint16_t aOffset, uint8_t aLength);
+    Error Set(const Message &aMessage, uint16_t aOffset, uint8_t aLength);
 
     /**
      * This method sets the Dataset using an existing Dataset.
@@ -831,11 +831,11 @@
      *
      * @param[in]  aDatasetInfo  The input Dataset as `Dataset::Info`.
      *
-     * @retval OT_ERROR_NONE          Successfully set the Dataset.
-     * @retval OT_ERROR_INVALID_ARGS  Dataset is missing Active and/or Pending Timestamp.
+     * @retval kErrorNone         Successfully set the Dataset.
+     * @retval kErrorInvalidArgs  Dataset is missing Active and/or Pending Timestamp.
      *
      */
-    otError SetFrom(const Info &aDatasetInfo);
+    Error SetFrom(const Info &aDatasetInfo);
 
     /**
      * This method sets the Dataset using @p aDataset.
@@ -858,11 +858,11 @@
      *
      * @param[in] aMessage       A message to append to.
      *
-     * @retval OT_ERROR_NONE     Successfully append MLE Dataset TLV without MeshCoP Sub Timestamp TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient available buffers to append the message with MLE Dataset TLV.
+     * @retval kErrorNone    Successfully append MLE Dataset TLV without MeshCoP Sub Timestamp TLV.
+     * @retval kErrorNoBufs  Insufficient available buffers to append the message with MLE Dataset TLV.
      *
      */
-    otError AppendMleDatasetTlv(Message &aMessage) const;
+    Error AppendMleDatasetTlv(Message &aMessage) const;
 
     /**
      * This method applies the Active or Pending Dataset to the Thread interface.
@@ -870,11 +870,11 @@
      * @param[in]  aInstance           A reference to the OpenThread instance.
      * @param[out] aIsMasterKeyUpdated A pointer to where to place whether master key was updated.
      *
-     * @retval OT_ERROR_NONE   Successfully applied configuration.
-     * @retval OT_ERROR_PARSE  The dataset has at least one TLV with invalid format.
+     * @retval kErrorNone   Successfully applied configuration.
+     * @retval kErrorParse  The dataset has at least one TLV with invalid format.
      *
      */
-    otError ApplyConfiguration(Instance &aInstance, bool *aIsMasterKeyUpdated = nullptr) const;
+    Error ApplyConfiguration(Instance &aInstance, bool *aIsMasterKeyUpdated = nullptr) const;
 
     /**
      * This method converts a Pending Dataset to an Active Dataset.
@@ -943,11 +943,11 @@
  * @param[in] aType     The TLV Type.
  * @param[in] aValue    The TLV value (as `uint16_t`).
  *
- * @retval OT_ERROR_NONE     Successfully set the TLV.
- * @retval OT_ERROR_NO_BUFS  Could not set the TLV due to insufficient buffer space.
+ * @retval kErrorNone    Successfully set the TLV.
+ * @retval kErrorNoBufs  Could not set the TLV due to insufficient buffer space.
  *
  */
-template <> inline otError Dataset::SetTlv(Tlv::Type aType, const uint16_t &aValue)
+template <> inline Error Dataset::SetTlv(Tlv::Type aType, const uint16_t &aValue)
 {
     uint16_t value = Encoding::BigEndian::HostSwap16(aValue);
 
@@ -960,11 +960,11 @@
  * @param[in] aType     The TLV Type.
  * @param[in] aValue    The TLV value (as `uint32_t`).
  *
- * @retval OT_ERROR_NONE     Successfully set the TLV.
- * @retval OT_ERROR_NO_BUFS  Could not set the TLV due to insufficient buffer space.
+ * @retval kErrorNone    Successfully set the TLV.
+ * @retval kErrorNoBufs  Could not set the TLV due to insufficient buffer space.
  *
  */
-template <> inline otError Dataset::SetTlv(Tlv::Type aType, const uint32_t &aValue)
+template <> inline Error Dataset::SetTlv(Tlv::Type aType, const uint32_t &aValue)
 {
     uint32_t value = Encoding::BigEndian::HostSwap32(aValue);
 
diff --git a/src/core/meshcop/dataset_local.cpp b/src/core/meshcop/dataset_local.cpp
index a733a6d..f6391e6 100644
--- a/src/core/meshcop/dataset_local.cpp
+++ b/src/core/meshcop/dataset_local.cpp
@@ -66,10 +66,10 @@
     mSaved            = false;
 }
 
-otError DatasetLocal::Restore(Dataset &aDataset)
+Error DatasetLocal::Restore(Dataset &aDataset)
 {
     const Timestamp *timestamp;
-    otError          error;
+    Error            error;
 
     mTimestampPresent = false;
 
@@ -89,14 +89,14 @@
     return error;
 }
 
-otError DatasetLocal::Read(Dataset &aDataset) const
+Error DatasetLocal::Read(Dataset &aDataset) const
 {
     DelayTimerTlv *delayTimer;
     uint32_t       elapsed;
-    otError        error;
+    Error          error;
 
     error = Get<Settings>().ReadOperationalDataset(IsActive(), aDataset);
-    VerifyOrExit(error == OT_ERROR_NONE, aDataset.mLength = 0);
+    VerifyOrExit(error == kErrorNone, aDataset.mLength = 0);
 
     if (mType == Dataset::kActive)
     {
@@ -126,10 +126,10 @@
     return error;
 }
 
-otError DatasetLocal::Read(Dataset::Info &aDatasetInfo) const
+Error DatasetLocal::Read(Dataset::Info &aDatasetInfo) const
 {
     Dataset dataset(mType);
-    otError error;
+    Error   error;
 
     aDatasetInfo.Clear();
 
@@ -140,10 +140,10 @@
     return error;
 }
 
-otError DatasetLocal::Read(otOperationalDatasetTlvs &aDataset) const
+Error DatasetLocal::Read(otOperationalDatasetTlvs &aDataset) const
 {
     Dataset dataset(mType);
-    otError error;
+    Error   error;
 
     memset(&aDataset, 0, sizeof(aDataset));
 
@@ -154,9 +154,9 @@
     return error;
 }
 
-otError DatasetLocal::Save(const Dataset::Info &aDatasetInfo)
+Error DatasetLocal::Save(const Dataset::Info &aDatasetInfo)
 {
-    otError error;
+    Error   error;
     Dataset dataset(mType);
 
     SuccessOrExit(error = dataset.SetFrom(aDatasetInfo));
@@ -166,7 +166,7 @@
     return error;
 }
 
-otError DatasetLocal::Save(const otOperationalDatasetTlvs &aDataset)
+Error DatasetLocal::Save(const otOperationalDatasetTlvs &aDataset)
 {
     Dataset dataset(mType);
 
@@ -175,10 +175,10 @@
     return Save(dataset);
 }
 
-otError DatasetLocal::Save(const Dataset &aDataset)
+Error DatasetLocal::Save(const Dataset &aDataset)
 {
     const Timestamp *timestamp;
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
 
     if (aDataset.GetSize() == 0)
     {
diff --git a/src/core/meshcop/dataset_local.hpp b/src/core/meshcop/dataset_local.hpp
index e5d4464..66c4d3f 100644
--- a/src/core/meshcop/dataset_local.hpp
+++ b/src/core/meshcop/dataset_local.hpp
@@ -95,44 +95,44 @@
      *
      * @param[out]  aDataset  Where to place the dataset.
      *
-     * @retval OT_ERROR_NONE       Successfully retrieved the dataset.
-     * @retval OT_ERROR_NOT_FOUND  There is no corresponding dataset stored in non-volatile memory.
+     * @retval kErrorNone      Successfully retrieved the dataset.
+     * @retval kErrorNotFound  There is no corresponding dataset stored in non-volatile memory.
      *
      */
-    otError Restore(Dataset &aDataset);
+    Error Restore(Dataset &aDataset);
 
     /**
      * This method retrieves the dataset from non-volatile memory.
      *
      * @param[out]  aDataset  Where to place the dataset.
      *
-     * @retval OT_ERROR_NONE       Successfully retrieved the dataset.
-     * @retval OT_ERROR_NOT_FOUND  There is no corresponding dataset stored in non-volatile memory.
+     * @retval kErrorNone      Successfully retrieved the dataset.
+     * @retval kErrorNotFound  There is no corresponding dataset stored in non-volatile memory.
      *
      */
-    otError Read(Dataset &aDataset) const;
+    Error Read(Dataset &aDataset) const;
 
     /**
      * This method retrieves the dataset from non-volatile memory.
      *
      * @param[out]  aDatasetInfo  Where to place the dataset as `Dataset::Info`.
      *
-     * @retval OT_ERROR_NONE       Successfully retrieved the dataset.
-     * @retval OT_ERROR_NOT_FOUND  There is no corresponding dataset stored in non-volatile memory.
+     * @retval kErrorNone      Successfully retrieved the dataset.
+     * @retval kErrorNotFound  There is no corresponding dataset stored in non-volatile memory.
      *
      */
-    otError Read(Dataset::Info &aDatasetInfo) const;
+    Error Read(Dataset::Info &aDatasetInfo) const;
 
     /**
      * This method retrieves the dataset from non-volatile memory.
      *
      * @param[out]  aDataset  Where to place the dataset.
      *
-     * @retval OT_ERROR_NONE       Successfully retrieved the dataset.
-     * @retval OT_ERROR_NOT_FOUND  There is no corresponding dataset stored in non-volatile memory.
+     * @retval kErrorNone      Successfully retrieved the dataset.
+     * @retval kErrorNotFound  There is no corresponding dataset stored in non-volatile memory.
      *
      */
-    otError Read(otOperationalDatasetTlvs &aDataset) const;
+    Error Read(otOperationalDatasetTlvs &aDataset) const;
 
     /**
      * This method returns the local time this dataset was last updated or restored.
@@ -147,33 +147,33 @@
      *
      * @param[in] aDatasetInfo     The Dataset to save as `Dataset::Info`.
      *
-     * @retval OT_ERROR_NONE              Successfully saved the dataset.
-     * @retval OT_ERROR_NOT_IMPLEMENTED   The platform does not implement settings functionality.
+     * @retval kErrorNone             Successfully saved the dataset.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
      *
      */
-    otError Save(const Dataset::Info &aDatasetInfo);
+    Error Save(const Dataset::Info &aDatasetInfo);
 
     /**
      * This method stores the dataset into non-volatile memory.
      *
      * @param[in]  aDataset  The Dataset to save as `otOperationalDatasetTlvs`.
      *
-     * @retval OT_ERROR_NONE              Successfully saved the dataset.
-     * @retval OT_ERROR_NOT_IMPLEMENTED   The platform does not implement settings functionality.
+     * @retval kErrorNone             Successfully saved the dataset.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
      *
      */
-    otError Save(const otOperationalDatasetTlvs &aDataset);
+    Error Save(const otOperationalDatasetTlvs &aDataset);
 
     /**
      * This method stores the dataset into non-volatile memory.
      *
      * @param[in]  aDataset  The Dataset to save.
      *
-     * @retval OT_ERROR_NONE              Successfully saved the dataset.
-     * @retval OT_ERROR_NOT_IMPLEMENTED   The platform does not implement settings functionality.
+     * @retval kErrorNone             Successfully saved the dataset.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
      *
      */
-    otError Save(const Dataset &aDataset);
+    Error Save(const Dataset &aDataset);
 
     /**
      * This method compares this dataset to another based on the timestamp.
diff --git a/src/core/meshcop/dataset_manager.cpp b/src/core/meshcop/dataset_manager.cpp
index 5403cf2..ae94d2e 100644
--- a/src/core/meshcop/dataset_manager.cpp
+++ b/src/core/meshcop/dataset_manager.cpp
@@ -55,7 +55,7 @@
     , mLocal(aInstance, aType)
     , mTimestampValid(false)
     , mCoapPending(false)
-    , mTimer(aInstance, aTimerHandler, this)
+    , mTimer(aInstance, aTimerHandler)
 {
     mTimestamp.Init();
 }
@@ -78,9 +78,9 @@
     return rval;
 }
 
-otError DatasetManager::Restore(void)
+Error DatasetManager::Restore(void)
 {
-    otError          error;
+    Error            error;
     Dataset          dataset(GetType());
     const Timestamp *timestamp;
 
@@ -109,9 +109,9 @@
     return error;
 }
 
-otError DatasetManager::ApplyConfiguration(void) const
+Error DatasetManager::ApplyConfiguration(void) const
 {
-    otError error;
+    Error   error;
     Dataset dataset(GetType());
 
     SuccessOrExit(error = Read(dataset));
@@ -135,9 +135,9 @@
     IgnoreError(Restore());
 }
 
-otError DatasetManager::Save(const Dataset &aDataset)
+Error DatasetManager::Save(const Dataset &aDataset)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     const Timestamp *timestamp;
     int              compare;
     bool             isMasterkeyUpdated = false;
@@ -167,7 +167,7 @@
     }
     else if (compare < 0)
     {
-        VerifyOrExit(!Get<Mle::MleRouter>().IsLeader(), error = OT_ERROR_INVALID_STATE);
+        VerifyOrExit(!Get<Mle::MleRouter>().IsLeader(), error = kErrorInvalidState);
         SendSet();
     }
 
@@ -177,9 +177,9 @@
     return error;
 }
 
-otError DatasetManager::Save(const Dataset::Info &aDatasetInfo)
+Error DatasetManager::Save(const Dataset::Info &aDatasetInfo)
 {
-    otError error;
+    Error error;
 
     SuccessOrExit(error = mLocal.Save(aDatasetInfo));
     HandleDatasetUpdated();
@@ -188,9 +188,20 @@
     return error;
 }
 
-otError DatasetManager::Save(const otOperationalDatasetTlvs &aDataset)
+Error DatasetManager::Save(const otOperationalDatasetTlvs &aDataset)
 {
-    otError error;
+    Error error;
+
+    SuccessOrExit(error = mLocal.Save(aDataset));
+    HandleDatasetUpdated();
+
+exit:
+    return error;
+}
+
+Error DatasetManager::SaveLocal(const Dataset &aDataset)
+{
+    Error error;
 
     SuccessOrExit(error = mLocal.Save(aDataset));
     HandleDatasetUpdated();
@@ -234,9 +245,9 @@
                                                                 : kEventPendingDatasetChanged);
 }
 
-otError DatasetManager::GetChannelMask(Mac::ChannelMask &aChannelMask) const
+Error DatasetManager::GetChannelMask(Mac::ChannelMask &aChannelMask) const
 {
-    otError                        error;
+    Error                          error;
     const MeshCoP::ChannelMaskTlv *channelMaskTlv;
     uint32_t                       mask;
     Dataset                        dataset(GetType());
@@ -244,12 +255,12 @@
     SuccessOrExit(error = Read(dataset));
 
     channelMaskTlv = dataset.GetTlv<ChannelMaskTlv>();
-    VerifyOrExit(channelMaskTlv != nullptr, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(channelMaskTlv != nullptr, error = kErrorNotFound);
     VerifyOrExit((mask = channelMaskTlv->GetChannelMask()) != 0);
 
     aChannelMask.SetMask(mask & Get<Mac::Mac>().GetSupportedChannelMask().GetMask());
 
-    VerifyOrExit(!aChannelMask.IsEmpty(), error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(!aChannelMask.IsEmpty(), error = kErrorNotFound);
 
 exit:
     return error;
@@ -262,14 +273,14 @@
 
 void DatasetManager::SendSet(void)
 {
-    otError          error;
+    Error            error;
     Coap::Message *  message = nullptr;
     Ip6::MessageInfo messageInfo;
     Dataset          dataset(GetType());
 
-    VerifyOrExit(!mCoapPending, error = OT_ERROR_BUSY);
-    VerifyOrExit(Get<Mle::MleRouter>().IsAttached(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(mLocal.Compare(GetTimestamp()) < 0, error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(!mCoapPending, error = kErrorBusy);
+    VerifyOrExit(Get<Mle::MleRouter>().IsAttached(), error = kErrorInvalidState);
+    VerifyOrExit(mLocal.Compare(GetTimestamp()) < 0, error = kErrorInvalidState);
 
     if (IsActiveDataset())
     {
@@ -282,11 +293,11 @@
         if (pendingActiveTimestamp != nullptr && mLocal.Compare(pendingActiveTimestamp) == 0)
         {
             // stop registration attempts during dataset transition
-            ExitNow(error = OT_ERROR_INVALID_STATE);
+            ExitNow(error = kErrorInvalidState);
         }
     }
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error =
                       message->InitAsConfirmablePost(IsActiveDataset() ? UriPath::kActiveSet : UriPath::kPendingSet));
@@ -307,13 +318,13 @@
 
     switch (error)
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         mCoapPending = true;
         break;
 
-    case OT_ERROR_NO_BUFS:
+    case kErrorNoBufs:
         mTimer.Start(kDelayNoBufs);
-        // fall through
+        OT_FALL_THROUGH;
 
     default:
         LogError("send Dataset set to leader", error);
@@ -325,7 +336,7 @@
 void DatasetManager::HandleCoapResponse(void *               aContext,
                                         otMessage *          aMessage,
                                         const otMessageInfo *aMessageInfo,
-                                        otError              aError)
+                                        Error                aError)
 {
     OT_UNUSED_VARIABLE(aMessage);
     OT_UNUSED_VARIABLE(aMessageInfo);
@@ -390,13 +401,13 @@
                                      uint8_t *               aTlvs,
                                      uint8_t                 aLength) const
 {
-    otError        error = OT_ERROR_NONE;
+    Error          error = kErrorNone;
     Coap::Message *message;
     Dataset        dataset(GetType());
 
     IgnoreError(Read(dataset));
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->SetDefaultResponseHeader(aRequest));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -443,9 +454,9 @@
     FreeMessageOnError(message, error);
 }
 
-otError DatasetManager::AppendDatasetToMessage(const Dataset::Info &aDatasetInfo, Message &aMessage) const
+Error DatasetManager::AppendDatasetToMessage(const Dataset::Info &aDatasetInfo, Message &aMessage) const
 {
-    otError error;
+    Error   error;
     Dataset dataset(GetType());
 
     SuccessOrExit(error = dataset.SetFrom(aDatasetInfo));
@@ -455,13 +466,13 @@
     return error;
 }
 
-otError DatasetManager::SendSetRequest(const Dataset::Info &aDatasetInfo, const uint8_t *aTlvs, uint8_t aLength)
+Error DatasetManager::SendSetRequest(const Dataset::Info &aDatasetInfo, const uint8_t *aTlvs, uint8_t aLength)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     Coap::Message *  message;
     Ip6::MessageInfo messageInfo;
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error =
                       message->InitAsConfirmablePost(IsActiveDataset() ? UriPath::kActiveSet : UriPath::kPendingSet));
@@ -476,7 +487,7 @@
 
         for (const Tlv *cur = reinterpret_cast<const Tlv *>(aTlvs); cur < end; cur = cur->GetNext())
         {
-            VerifyOrExit((cur + 1) <= end, error = OT_ERROR_INVALID_ARGS);
+            VerifyOrExit((cur + 1) <= end, error = kErrorInvalidArgs);
 
             if (cur->GetType() == Tlv::kCommissionerSessionId)
             {
@@ -518,12 +529,12 @@
     return error;
 }
 
-otError DatasetManager::SendGetRequest(const Dataset::Components &aDatasetComponents,
-                                       const uint8_t *            aTlvTypes,
-                                       uint8_t                    aLength,
-                                       const otIp6Address *       aAddress) const
+Error DatasetManager::SendGetRequest(const Dataset::Components &aDatasetComponents,
+                                     const uint8_t *            aTlvTypes,
+                                     uint8_t                    aLength,
+                                     const otIp6Address *       aAddress) const
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     Coap::Message *  message;
     Ip6::MessageInfo messageInfo;
     Tlv              tlv;
@@ -592,7 +603,7 @@
         datasetTlvs[length++] = Tlv::kChannelMask;
     }
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error =
                       message->InitAsConfirmablePost(IsActiveDataset() ? UriPath::kActiveGet : UriPath::kPendingGet));
@@ -668,9 +679,9 @@
     return isValid;
 }
 
-otError ActiveDataset::Save(const Timestamp &aTimestamp, const Message &aMessage, uint16_t aOffset, uint8_t aLength)
+Error ActiveDataset::Save(const Timestamp &aTimestamp, const Message &aMessage, uint16_t aOffset, uint8_t aLength)
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     Dataset dataset(GetType());
 
     SuccessOrExit(error = dataset.Set(aMessage, aOffset, aLength));
@@ -694,12 +705,12 @@
 
 void ActiveDataset::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<ActiveDataset>().HandleTimer();
+    aTimer.Get<ActiveDataset>().HandleTimer();
 }
 
 PendingDataset::PendingDataset(Instance &aInstance)
     : DatasetManager(aInstance, Dataset::kPending, PendingDataset::HandleTimer)
-    , mDelayTimer(aInstance, PendingDataset::HandleDelayTimer, this)
+    , mDelayTimer(aInstance, PendingDataset::HandleDelayTimer)
     , mResourceGet(UriPath::kPendingGet, &PendingDataset::HandleGet, this)
 #if OPENTHREAD_FTD
     , mResourceSet(UriPath::kPendingSet, &PendingDataset::HandleSet, this)
@@ -723,9 +734,9 @@
     IgnoreError(DatasetManager::Save(dataset));
 }
 
-otError PendingDataset::Save(const Dataset::Info &aDatasetInfo)
+Error PendingDataset::Save(const Dataset::Info &aDatasetInfo)
 {
-    otError error;
+    Error error;
 
     SuccessOrExit(error = DatasetManager::Save(aDatasetInfo));
     StartDelayTimer();
@@ -734,9 +745,9 @@
     return error;
 }
 
-otError PendingDataset::Save(const otOperationalDatasetTlvs &aDataset)
+Error PendingDataset::Save(const otOperationalDatasetTlvs &aDataset)
 {
-    otError error;
+    Error error;
 
     SuccessOrExit(error = DatasetManager::Save(aDataset));
     StartDelayTimer();
@@ -745,9 +756,20 @@
     return error;
 }
 
-otError PendingDataset::Save(const Timestamp &aTimestamp, const Message &aMessage, uint16_t aOffset, uint8_t aLength)
+Error PendingDataset::Save(const Dataset &aDataset)
 {
-    otError error = OT_ERROR_NONE;
+    Error error;
+
+    SuccessOrExit(error = DatasetManager::SaveLocal(aDataset));
+    StartDelayTimer();
+
+exit:
+    return error;
+}
+
+Error PendingDataset::Save(const Timestamp &aTimestamp, const Message &aMessage, uint16_t aOffset, uint8_t aLength)
+{
+    Error   error = kErrorNone;
     Dataset dataset(GetType());
 
     SuccessOrExit(error = dataset.Set(aMessage, aOffset, aLength));
@@ -785,7 +807,7 @@
 
 void PendingDataset::HandleDelayTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<PendingDataset>().HandleDelayTimer();
+    aTimer.Get<PendingDataset>().HandleDelayTimer();
 }
 
 void PendingDataset::HandleDelayTimer(void)
@@ -834,7 +856,7 @@
 
 void PendingDataset::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<PendingDataset>().HandleTimer();
+    aTimer.Get<PendingDataset>().HandleTimer();
 }
 
 } // namespace MeshCoP
diff --git a/src/core/meshcop/dataset_manager.hpp b/src/core/meshcop/dataset_manager.hpp
index 98d08a7..5fbcd94 100644
--- a/src/core/meshcop/dataset_manager.hpp
+++ b/src/core/meshcop/dataset_manager.hpp
@@ -64,11 +64,11 @@
     /**
      * This method restores the Operational Dataset from non-volatile memory.
      *
-     * @retval OT_ERROR_NONE       Successfully restore the dataset.
-     * @retval OT_ERROR_NOT_FOUND  There is no corresponding dataset stored in non-volatile memory.
+     * @retval kErrorNone      Successfully restore the dataset.
+     * @retval kErrorNotFound  There is no corresponding dataset stored in non-volatile memory.
      *
      */
-    otError Restore(void);
+    Error Restore(void);
 
     /**
      * This method compares @p aTimestamp to the dataset's timestamp value.
@@ -87,53 +87,53 @@
      *
      * @param[out]  aDataset  Where to place the dataset.
      *
-     * @retval OT_ERROR_NONE       Successfully retrieved the dataset.
-     * @retval OT_ERROR_NOT_FOUND  There is no corresponding dataset stored in non-volatile memory.
+     * @retval kErrorNone      Successfully retrieved the dataset.
+     * @retval kErrorNotFound  There is no corresponding dataset stored in non-volatile memory.
      *
      */
-    otError Read(Dataset &aDataset) const { return mLocal.Read(aDataset); }
+    Error Read(Dataset &aDataset) const { return mLocal.Read(aDataset); }
 
     /**
      * This method retrieves the dataset from non-volatile memory.
      *
      * @param[out]  aDatasetInfo  Where to place the dataset (as `Dataset::Info`).
      *
-     * @retval OT_ERROR_NONE       Successfully retrieved the dataset.
-     * @retval OT_ERROR_NOT_FOUND  There is no corresponding dataset stored in non-volatile memory.
+     * @retval kErrorNone      Successfully retrieved the dataset.
+     * @retval kErrorNotFound  There is no corresponding dataset stored in non-volatile memory.
      *
      */
-    otError Read(Dataset::Info &aDatasetInfo) const { return mLocal.Read(aDatasetInfo); }
+    Error Read(Dataset::Info &aDatasetInfo) const { return mLocal.Read(aDatasetInfo); }
 
     /**
      * This method retrieves the dataset from non-volatile memory.
      *
      * @param[out]  aDataset  Where to place the dataset.
      *
-     * @retval OT_ERROR_NONE       Successfully retrieved the dataset.
-     * @retval OT_ERROR_NOT_FOUND  There is no corresponding dataset stored in non-volatile memory.
+     * @retval kErrorNone      Successfully retrieved the dataset.
+     * @retval kErrorNotFound  There is no corresponding dataset stored in non-volatile memory.
      *
      */
-    otError Read(otOperationalDatasetTlvs &aDataset) const { return mLocal.Read(aDataset); }
+    Error Read(otOperationalDatasetTlvs &aDataset) const { return mLocal.Read(aDataset); }
 
     /**
      * This method retrieves the channel mask from local dataset.
      *
      * @param[out]  aChannelMask  A reference to the channel mask.
      *
-     * @retval OT_ERROR_NONE       Successfully retrieved the channel mask.
-     * @retval OT_ERROR_NOT_FOUND  There is no valid channel mask stored in local dataset.
+     * @retval kErrorNone      Successfully retrieved the channel mask.
+     * @retval kErrorNotFound  There is no valid channel mask stored in local dataset.
      *
      */
-    otError GetChannelMask(Mac::ChannelMask &aChannelMask) const;
+    Error GetChannelMask(Mac::ChannelMask &aChannelMask) const;
 
     /**
      * This method applies the Active or Pending Dataset to the Thread interface.
      *
-     * @retval OT_ERROR_NONE   Successfully applied configuration.
-     * @retval OT_ERROR_PARSE  The dataset has at least one TLV with invalid format.
+     * @retval kErrorNone   Successfully applied configuration.
+     * @retval kErrorParse  The dataset has at least one TLV with invalid format.
      *
      */
-    otError ApplyConfiguration(void) const;
+    Error ApplyConfiguration(void) const;
 
     /**
      * This method updates the Operational Dataset when detaching from the network.
@@ -150,11 +150,11 @@
      * @param[in]  aTlvs         Any additional raw TLVs to include.
      * @param[in]  aLength       Number of bytes in @p aTlvs.
      *
-     * @retval OT_ERROR_NONE     Successfully send the meshcop dataset command.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffer space to send.
+     * @retval kErrorNone    Successfully send the meshcop dataset command.
+     * @retval kErrorNoBufs  Insufficient buffer space to send.
      *
      */
-    otError SendSetRequest(const Dataset::Info &aDatasetInfo, const uint8_t *aTlvs, uint8_t aLength);
+    Error SendSetRequest(const Dataset::Info &aDatasetInfo, const uint8_t *aTlvs, uint8_t aLength);
 
     /**
      * This method sends a MGMT_GET request.
@@ -164,25 +164,25 @@
      * @param[in]  aLength             Number of bytes in @p aTlvTypes.
      * @param[in]  aAddress            The IPv6 destination address for the MGMT_GET request.
      *
-     * @retval OT_ERROR_NONE     Successfully send the meshcop dataset command.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffer space to send.
+     * @retval kErrorNone     Successfully send the meshcop dataset command.
+     * @retval kErrorNoBufs   Insufficient buffer space to send.
      *
      */
-    otError SendGetRequest(const Dataset::Components &aDatasetComponents,
-                           const uint8_t *            aTlvTypes,
-                           uint8_t                    aLength,
-                           const otIp6Address *       aAddress) const;
+    Error SendGetRequest(const Dataset::Components &aDatasetComponents,
+                         const uint8_t *            aTlvTypes,
+                         uint8_t                    aLength,
+                         const otIp6Address *       aAddress) const;
 #if OPENTHREAD_FTD
     /**
      * This method appends the MLE Dataset TLV but excluding MeshCoP Sub Timestamp TLV.
      *
      * @param[in] aMessage       The message to append the TLV to.
      *
-     * @retval OT_ERROR_NONE     Successfully append MLE Dataset TLV without MeshCoP Sub Timestamp TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient available buffers to append the message with MLE Dataset TLV.
+     * @retval kErrorNone    Successfully append MLE Dataset TLV without MeshCoP Sub Timestamp TLV.
+     * @retval kErrorNoBufs  Insufficient available buffers to append the message with MLE Dataset TLV.
      *
      */
-    otError AppendMleDatasetTlv(Message &aMessage) const;
+    Error AppendMleDatasetTlv(Message &aMessage) const;
 #endif
 
 protected:
@@ -200,11 +200,11 @@
          * @param[in]  aMessage  A message to read the TLV from.
          * @param[in]  aOffset   An offset into the message to read from.
          *
-         * @retval OT_ERROR_NONE    The TLV was read successfully.
-         * @retval OT_ERROR_PARSE   The TLV was not well-formed and could not be parsed.
+         * @retval kErrorNone    The TLV was read successfully.
+         * @retval kErrorParse   The TLV was not well-formed and could not be parsed.
          *
          */
-        otError ReadFromMessage(const Message &aMessage, uint16_t aOffset);
+        Error ReadFromMessage(const Message &aMessage, uint16_t aOffset);
 
     private:
         enum
@@ -244,33 +244,33 @@
      *
      * @param[in]  aDataset  The Operational Dataset.
      *
-     * @retval OT_ERROR_NONE   Successfully applied configuration.
-     * @retval OT_ERROR_PARSE  The dataset has at least one TLV with invalid format.
+     * @retval kErrorNone   Successfully applied configuration.
+     * @retval kErrorParse  The dataset has at least one TLV with invalid format.
      *
      */
-    otError Save(const Dataset &aDataset);
+    Error Save(const Dataset &aDataset);
 
     /**
      * This method saves the Operational Dataset in non-volatile memory.
      *
      * @param[in]  aDatasetInfo  The Operational Dataset as `Dataset::Info`.
      *
-     * @retval OT_ERROR_NONE             Successfully saved the dataset.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  The platform does not implement settings functionality.
+     * @retval kErrorNone             Successfully saved the dataset.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
      *
      */
-    otError Save(const Dataset::Info &aDatasetInfo);
+    Error Save(const Dataset::Info &aDatasetInfo);
 
     /**
      * This method saves the Operational Dataset in non-volatile memory.
      *
      * @param[in]  aDataset  The Operational Dataset.
      *
-     * @retval OT_ERROR_NONE             Successfully saved the dataset.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  The platform does not implement settings functionality.
+     * @retval kErrorNone             Successfully saved the dataset.
+     * @retval kErrorNotImplemented   The platform does not implement settings functionality.
      *
      */
-    otError Save(const otOperationalDatasetTlvs &aDataset);
+    Error Save(const otOperationalDatasetTlvs &aDataset);
 
     /**
      * This method sets the Operational Dataset for the partition.
@@ -283,7 +283,18 @@
      * @param[in]  aLength     The length of the Operational Dataset.
      *
      */
-    otError Save(const Timestamp &aTimestamp, const Message &aMessage, uint16_t aOffset, uint8_t aLength);
+    Error Save(const Timestamp &aTimestamp, const Message &aMessage, uint16_t aOffset, uint8_t aLength);
+
+    /**
+     * This method saves the Operational Dataset in non-volatile memory.
+     *
+     * @param[in]  aDataset  The Operational Dataset.
+     *
+     * @retval kErrorNone   Successfully applied configuration.
+     * @retval kErrorParse  The dataset has at least one TLV with invalid format.
+     *
+     */
+    Error SaveLocal(const Dataset &aDataset);
 
     /**
      * This method handles a MGMT_GET request message.
@@ -316,11 +327,11 @@
      * @param[in]  aMessage      The CoAP message buffer.
      * @param[in]  aMessageInfo  The message info.
      *
-     * @retval OT_ERROR_NONE  The MGMT_SET request message was handled successfully.
-     * @retval OT_ERROR_DROP  The MGMT_SET request message was dropped.
+     * @retval kErrorNone  The MGMT_SET request message was handled successfully.
+     * @retval kErrorDrop  The MGMT_SET request message was dropped.
      *
      */
-    otError HandleSet(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    Error HandleSet(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
 #endif
 
     DatasetLocal mLocal;
@@ -331,19 +342,19 @@
     static void HandleCoapResponse(void *               aContext,
                                    otMessage *          aMessage,
                                    const otMessageInfo *aMessageInfo,
-                                   otError              aError);
+                                   Error                aError);
     void        HandleCoapResponse(void);
 
-    bool    IsActiveDataset(void) const { return GetType() == Dataset::kActive; }
-    bool    IsPendingDataset(void) const { return GetType() == Dataset::kPending; }
-    void    SignalDatasetChange(void) const;
-    void    HandleDatasetUpdated(void);
-    otError AppendDatasetToMessage(const Dataset::Info &aDatasetInfo, Message &aMessage) const;
-    void    SendSet(void);
-    void    SendGetResponse(const Coap::Message &   aRequest,
-                            const Ip6::MessageInfo &aMessageInfo,
-                            uint8_t *               aTlvs,
-                            uint8_t                 aLength) const;
+    bool  IsActiveDataset(void) const { return GetType() == Dataset::kActive; }
+    bool  IsPendingDataset(void) const { return GetType() == Dataset::kPending; }
+    void  SignalDatasetChange(void) const;
+    void  HandleDatasetUpdated(void);
+    Error AppendDatasetToMessage(const Dataset::Info &aDatasetInfo, Message &aMessage) const;
+    void  SendSet(void);
+    void  SendGetResponse(const Coap::Message &   aRequest,
+                          const Ip6::MessageInfo &aMessageInfo,
+                          uint8_t *               aTlvs,
+                          uint8_t                 aLength) const;
 
 #if OPENTHREAD_FTD
     void SendSetResponse(const Coap::Message &aRequest, const Ip6::MessageInfo &aMessageInfo, StateTlv::State aState);
@@ -419,29 +430,29 @@
      * @param[in]  aLength     The length of the Operational Dataset.
      *
      */
-    otError Save(const Timestamp &aTimestamp, const Message &aMessage, uint16_t aOffset, uint8_t aLength);
+    Error Save(const Timestamp &aTimestamp, const Message &aMessage, uint16_t aOffset, uint8_t aLength);
 
     /**
      * This method sets the Operational Dataset in non-volatile memory.
      *
      * @param[in]  aDatasetInfo  The Operational Dataset as `Dataset::Info`.
      *
-     * @retval OT_ERROR_NONE             Successfully saved the dataset.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  The platform does not implement settings functionality.
+     * @retval kErrorNone            Successfully saved the dataset.
+     * @retval kErrorNotImplemented  The platform does not implement settings functionality.
      *
      */
-    otError Save(const Dataset::Info &aDatasetInfo) { return DatasetManager::Save(aDatasetInfo); }
+    Error Save(const Dataset::Info &aDatasetInfo) { return DatasetManager::Save(aDatasetInfo); }
 
     /**
      * This method sets the Operational Dataset in non-volatile memory.
      *
      * @param[in]  aDataset  The Operational Dataset.
      *
-     * @retval OT_ERROR_NONE             Successfully saved the dataset.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  The platform does not implement settings functionality.
+     * @retval kErrorNone            Successfully saved the dataset.
+     * @retval kErrorNotImplemented  The platform does not implement settings functionality.
      *
      */
-    otError Save(const otOperationalDatasetTlvs &aDataset) { return DatasetManager::Save(aDataset); }
+    Error Save(const otOperationalDatasetTlvs &aDataset) { return DatasetManager::Save(aDataset); }
 
 #if OPENTHREAD_FTD
 
@@ -450,11 +461,11 @@
      *
      * @param[out]  aDatasetInfo  The Operational Dataset as `Dataset::Info`.
      *
-     * @retval OT_ERROR_NONE    Successfully created a new Operational Dataset.
-     * @retval OT_ERROR_FAILED  Failed to generate random values for new parameters.
+     * @retval kErrorNone    Successfully created a new Operational Dataset.
+     * @retval kErrorFailed  Failed to generate random values for new parameters.
      *
      */
-    otError CreateNewNetwork(Dataset::Info &aDatasetInfo) { return aDatasetInfo.GenerateRandom(GetInstance()); }
+    Error CreateNewNetwork(Dataset::Info &aDatasetInfo) { return aDatasetInfo.GenerateRandom(GetInstance()); }
 
     /**
      * This method starts the Leader functions for maintaining the Active Operational Dataset.
@@ -471,12 +482,12 @@
     /**
      * This method generate a default Active Operational Dataset.
      *
-     * @retval OT_ERROR_NONE           Successfully generated an Active Operational Dataset.
-     * @retval OT_ERROR_ALREADY        A valid Active Operational Dataset already exists.
-     * @retval OT_ERROR_INVALID_STATE  Device is not currently attached to a network.
+     * @retval kErrorNone          Successfully generated an Active Operational Dataset.
+     * @retval kErrorAlready       A valid Active Operational Dataset already exists.
+     * @retval kErrorInvalidState  Device is not currently attached to a network.
      *
      */
-    otError GenerateLocal(void);
+    Error GenerateLocal(void);
 #endif
 
 private:
@@ -532,11 +543,11 @@
      *
      * @param[in]  aDatasetInfo  The Operational Dataset as `Dataset::Info`.
      *
-     * @retval OT_ERROR_NONE             Successfully saved the dataset.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  The platform does not implement settings functionality.
+     * @retval kErrorNone            Successfully saved the dataset.
+     * @retval kErrorNotImplemented  The platform does not implement settings functionality.
      *
      */
-    otError Save(const Dataset::Info &aDatasetInfo);
+    Error Save(const Dataset::Info &aDatasetInfo);
 
     /**
      * This method saves the Operational Dataset in non-volatile memory.
@@ -545,11 +556,11 @@
      *
      * @param[in]  aDataset  The Operational Dataset.
      *
-     * @retval OT_ERROR_NONE             Successfully saved the dataset.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  The platform does not implement settings functionality.
+     * @retval kErrorNone            Successfully saved the dataset.
+     * @retval kErrorNotImplemented  The platform does not implement settings functionality.
      *
      */
-    otError Save(const otOperationalDatasetTlvs &aDataset);
+    Error Save(const otOperationalDatasetTlvs &aDataset);
 
     /**
      * This method sets the Operational Dataset for the partition.
@@ -564,7 +575,18 @@
      * @param[in]  aLength     The length of the Operational Dataset.
      *
      */
-    otError Save(const Timestamp &aTimestamp, const Message &aMessage, uint16_t aOffset, uint8_t aLength);
+    Error Save(const Timestamp &aTimestamp, const Message &aMessage, uint16_t aOffset, uint8_t aLength);
+
+    /**
+     * This method saves the Operational Dataset in non-volatile memory.
+     *
+     * @param[in]  aDataset  The Operational Dataset.
+     *
+     * @retval kErrorNone   Successfully applied configuration.
+     * @retval kErrorParse  The dataset has at least one TLV with invalid format.
+     *
+     */
+    Error Save(const Dataset &aDataset);
 
 #if OPENTHREAD_FTD
     /**
diff --git a/src/core/meshcop/dataset_manager_ftd.cpp b/src/core/meshcop/dataset_manager_ftd.cpp
index 02affdd..e8e5ffb 100644
--- a/src/core/meshcop/dataset_manager_ftd.cpp
+++ b/src/core/meshcop/dataset_manager_ftd.cpp
@@ -57,7 +57,7 @@
 namespace ot {
 namespace MeshCoP {
 
-otError DatasetManager::AppendMleDatasetTlv(Message &aMessage) const
+Error DatasetManager::AppendMleDatasetTlv(Message &aMessage) const
 {
     Dataset dataset(GetType());
 
@@ -66,7 +66,7 @@
     return dataset.AppendMleDatasetTlv(aMessage);
 }
 
-otError DatasetManager::HandleSet(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
+Error DatasetManager::HandleSet(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
     Tlv             tlv;
     Timestamp *     timestamp;
@@ -107,14 +107,14 @@
 
     type = (GetType() == Dataset::kActive) ? Tlv::kActiveTimestamp : Tlv::kPendingTimestamp;
 
-    if (Tlv::FindTlv(aMessage, activeTimestamp) != OT_ERROR_NONE)
+    if (Tlv::FindTlv(aMessage, activeTimestamp) != kErrorNone)
     {
         ExitNow();
     }
 
     VerifyOrExit(activeTimestamp.IsValid());
 
-    if (Tlv::FindTlv(aMessage, pendingTimestamp) == OT_ERROR_NONE)
+    if (Tlv::FindTlv(aMessage, pendingTimestamp) == kErrorNone)
     {
         VerifyOrExit(pendingTimestamp.IsValid());
     }
@@ -126,7 +126,7 @@
     VerifyOrExit(mLocal.Compare(timestamp) > 0);
 
     // check channel
-    if (Tlv::FindTlv(aMessage, channel) == OT_ERROR_NONE)
+    if (Tlv::FindTlv(aMessage, channel) == kErrorNone)
     {
         VerifyOrExit(channel.IsValid());
 
@@ -137,20 +137,20 @@
     }
 
     // check PAN ID
-    if (Tlv::Find<PanIdTlv>(aMessage, panId) == OT_ERROR_NONE && panId != Get<Mac::Mac>().GetPanId())
+    if (Tlv::Find<PanIdTlv>(aMessage, panId) == kErrorNone && panId != Get<Mac::Mac>().GetPanId())
     {
         doesAffectConnectivity = true;
     }
 
     // check mesh local prefix
-    if (Tlv::Find<MeshLocalPrefixTlv>(aMessage, meshLocalPrefix) == OT_ERROR_NONE &&
+    if (Tlv::Find<MeshLocalPrefixTlv>(aMessage, meshLocalPrefix) == kErrorNone &&
         meshLocalPrefix != Get<Mle::MleRouter>().GetMeshLocalPrefix())
     {
         doesAffectConnectivity = true;
     }
 
     // check network master key
-    if (Tlv::Find<NetworkMasterKeyTlv>(aMessage, masterKey) == OT_ERROR_NONE)
+    if (Tlv::Find<NetworkMasterKeyTlv>(aMessage, masterKey) == kErrorNone)
     {
         hasMasterKey = true;
 
@@ -171,7 +171,7 @@
     }
 
     // check commissioner session id
-    if (Tlv::Find<CommissionerSessionIdTlv>(aMessage, sessionId) == OT_ERROR_NONE)
+    if (Tlv::Find<CommissionerSessionIdTlv>(aMessage, sessionId) == kErrorNone)
     {
         const CommissionerSessionIdTlv *localId;
 
@@ -223,7 +223,7 @@
                 }
             }
 
-                // fall through
+                OT_FALL_THROUGH;
 
             default:
                 SuccessOrExit(dataset.SetTlv(datasetTlv));
@@ -266,17 +266,17 @@
         SendSetResponse(aMessage, aMessageInfo, state);
     }
 
-    return (state == StateTlv::kAccept) ? OT_ERROR_NONE : OT_ERROR_DROP;
+    return (state == StateTlv::kAccept) ? kErrorNone : kErrorDrop;
 }
 
 void DatasetManager::SendSetResponse(const Coap::Message &   aRequest,
                                      const Ip6::MessageInfo &aMessageInfo,
                                      StateTlv::State         aState)
 {
-    otError        error = OT_ERROR_NONE;
+    Error          error = kErrorNone;
     Coap::Message *message;
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->SetDefaultResponseHeader(aRequest));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -291,26 +291,26 @@
     FreeMessageOnError(message, error);
 }
 
-otError DatasetManager::DatasetTlv::ReadFromMessage(const Message &aMessage, uint16_t aOffset)
+Error DatasetManager::DatasetTlv::ReadFromMessage(const Message &aMessage, uint16_t aOffset)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     SuccessOrExit(error = aMessage.Read(aOffset, this, sizeof(Tlv)));
-    VerifyOrExit(GetLength() <= kMaxValueSize, error = OT_ERROR_PARSE);
+    VerifyOrExit(GetLength() <= kMaxValueSize, error = kErrorParse);
     SuccessOrExit(error = aMessage.Read(aOffset + sizeof(Tlv), mValue, GetLength()));
-    VerifyOrExit(Tlv::IsValid(*this), error = OT_ERROR_PARSE);
+    VerifyOrExit(Tlv::IsValid(*this), error = kErrorParse);
 
 exit:
     return error;
 }
 
-otError ActiveDataset::GenerateLocal(void)
+Error ActiveDataset::GenerateLocal(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     Dataset dataset(GetType());
 
-    VerifyOrExit(Get<Mle::MleRouter>().IsAttached(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(!mLocal.IsTimestampPresent(), error = OT_ERROR_ALREADY);
+    VerifyOrExit(Get<Mle::MleRouter>().IsAttached(), error = kErrorInvalidState);
+    VerifyOrExit(!mLocal.IsTimestampPresent(), error = kErrorAlready);
 
     IgnoreError(Read(dataset));
 
diff --git a/src/core/meshcop/dataset_updater.cpp b/src/core/meshcop/dataset_updater.cpp
new file mode 100644
index 0000000..9c70e45
--- /dev/null
+++ b/src/core/meshcop/dataset_updater.cpp
@@ -0,0 +1,216 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file implements Dataset Updater.
+ *
+ */
+
+#include "dataset_updater.hpp"
+
+#include "common/code_utils.hpp"
+#include "common/instance.hpp"
+#include "common/locator-getters.hpp"
+#include "common/logging.hpp"
+#include "common/random.hpp"
+
+#if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD
+
+namespace ot {
+namespace MeshCoP {
+
+DatasetUpdater::DatasetUpdater(Instance &aInstance)
+    : InstanceLocator(aInstance)
+    , mCallback(nullptr)
+    , mCallbackContext(nullptr)
+    , mTimer(aInstance, DatasetUpdater::HandleTimer)
+    , mDataset(nullptr)
+{
+}
+
+Error DatasetUpdater::RequestUpdate(const MeshCoP::Dataset::Info &aDataset, Callback aCallback, void *aContext)
+{
+    Error    error   = kErrorNone;
+    Message *message = nullptr;
+
+    VerifyOrExit(!Get<Mle::Mle>().IsDisabled(), error = kErrorInvalidState);
+    VerifyOrExit(mDataset == nullptr, error = kErrorBusy);
+
+    VerifyOrExit(!aDataset.IsActiveTimestampPresent() && !aDataset.IsPendingTimestampPresent(),
+                 error = kErrorInvalidArgs);
+
+    message = Get<MessagePool>().New(Message::kTypeOther, 0);
+    VerifyOrExit(message != nullptr, error = kErrorNoBufs);
+
+    SuccessOrExit(error = message->Append(aDataset));
+
+    mCallback        = aCallback;
+    mCallbackContext = aContext;
+    mDataset         = message;
+
+    mTimer.Start(1);
+
+exit:
+    FreeMessageOnError(message, error);
+    return error;
+}
+
+void DatasetUpdater::CancelUpdate(void)
+{
+    VerifyOrExit(mDataset != nullptr);
+
+    FreeMessage(mDataset);
+    mDataset = nullptr;
+    mTimer.Stop();
+
+exit:
+    return;
+}
+
+void DatasetUpdater::HandleTimer(Timer &aTimer)
+{
+    aTimer.Get<DatasetUpdater>().HandleTimer();
+}
+
+void DatasetUpdater::HandleTimer(void)
+{
+    PreparePendingDataset();
+}
+
+void DatasetUpdater::PreparePendingDataset(void)
+{
+    Dataset                dataset(Dataset::kPending);
+    MeshCoP::Dataset::Info requestedDataset;
+    Error                  error;
+
+    VerifyOrExit(!Get<Mle::Mle>().IsDisabled(), error = kErrorInvalidState);
+
+    IgnoreError(mDataset->Read(0, requestedDataset));
+
+    error = Get<ActiveDataset>().Read(dataset);
+
+    if (error != kErrorNone)
+    {
+        // If there is no valid Active Dataset but MLE is not disabled,
+        // set the timer to try again after the retry interval. This
+        // handles the situation where a dataset update request comes
+        // right after the network is formed but before the active
+        // dataset is created.
+
+        mTimer.Start(kRetryInterval);
+        ExitNow(error = kErrorNone);
+    }
+
+    IgnoreError(dataset.SetFrom(requestedDataset));
+
+    if (!requestedDataset.IsDelayPresent())
+    {
+        uint32_t delay = kDefaultDelay;
+
+        SuccessOrExit(error = dataset.SetTlv(Tlv::kDelayTimer, delay));
+    }
+
+    {
+        Timestamp timestamp;
+
+        if (Get<PendingDataset>().GetTimestamp() != nullptr)
+        {
+            timestamp = *Get<PendingDataset>().GetTimestamp();
+        }
+
+        timestamp.AdvanceRandomTicks();
+        dataset.SetTimestamp(timestamp);
+    }
+
+    {
+        ActiveTimestampTlv *tlv = dataset.GetTlv<ActiveTimestampTlv>();
+        tlv->AdvanceRandomTicks();
+    }
+
+    SuccessOrExit(error = Get<PendingDataset>().Save(dataset));
+
+exit:
+    if (error != kErrorNone)
+    {
+        Finish(error);
+    }
+}
+
+void DatasetUpdater::Finish(Error aError)
+{
+    OT_ASSERT(mDataset != nullptr);
+
+    FreeMessage(mDataset);
+    mDataset = nullptr;
+
+    if (mCallback != nullptr)
+    {
+        mCallback(aError, mCallbackContext);
+    }
+}
+
+void DatasetUpdater::HandleNotifierEvents(Events aEvents)
+{
+    MeshCoP::Dataset::Info requestedDataset;
+    MeshCoP::Dataset::Info dataset;
+
+    VerifyOrExit(mDataset != nullptr);
+
+    VerifyOrExit(aEvents.ContainsAny(kEventActiveDatasetChanged | kEventPendingDatasetChanged));
+
+    IgnoreError(mDataset->Read(0, requestedDataset));
+
+    if (aEvents.Contains(kEventActiveDatasetChanged) && Get<MeshCoP::ActiveDataset>().Read(dataset) == kErrorNone)
+    {
+        if (requestedDataset.IsSubsetOf(dataset))
+        {
+            Finish(kErrorNone);
+        }
+        else if (requestedDataset.GetActiveTimestamp() <= dataset.GetActiveTimestamp())
+        {
+            Finish(kErrorAlready);
+        }
+    }
+
+    if (aEvents.Contains(kEventPendingDatasetChanged) && Get<MeshCoP::PendingDataset>().Read(dataset) == kErrorNone)
+    {
+        if (!requestedDataset.IsSubsetOf(dataset))
+        {
+            Finish(kErrorAlready);
+        }
+    }
+
+exit:
+    return;
+}
+
+} // namespace MeshCoP
+} // namespace ot
+
+#endif // #if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD
diff --git a/src/core/utils/dataset_updater.hpp b/src/core/meshcop/dataset_updater.hpp
similarity index 66%
rename from src/core/utils/dataset_updater.hpp
rename to src/core/meshcop/dataset_updater.hpp
index 63f13d3..7d77196 100644
--- a/src/core/utils/dataset_updater.hpp
+++ b/src/core/meshcop/dataset_updater.hpp
@@ -47,7 +47,7 @@
 #include "meshcop/meshcop_tlvs.hpp"
 
 namespace ot {
-namespace Utils {
+namespace MeshCoP {
 
 #if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD
 
@@ -72,13 +72,13 @@
      * This type represents the callback function pointer which is called when a Dataset update request finishes,
      * reporting success or failure status of the request.
      *
-     * The function pointer has the syntax `void (*Callback)(otError aError, void *aContext)`.
+     * The function pointer has the syntax `void (*Callback)(Error aError, void *aContext)`.
      *
      * @param[in] aError   The error status.
-     *                     OT_ERROR_NONE           indicates Dataset update successfully finished.
-     *                     OT_ERROR_INVALID_STATE  indicates failure due invalid state (MLE being disabled).
-     *                     OT_ERROR_ALREADY        indicates failure due to another device within network requesting
-     *                                             a conflicting Dataset update.
+     *                     kErrorNone           indicates Dataset update successfully finished.
+     *                     kErrorInvalidState   indicates failure due invalid state (MLE being disabled).
+     *                     kErrorAlready        indicates failure due to another device within network requesting a
+     *                                          conflicting Dataset update.
      * @param[in] aContext A pointer to the arbitrary context provided by the user.
      *
      */
@@ -93,19 +93,15 @@
      * @param[in]  aDataset                Dataset info containing fields to change.
      * @param[in]  aCallback               A callback to indicate when Dataset update request finishes.
      * @param[in]  aContext                An arbitrary context passed to callback.
-     * @param[in]  aRetryWaitInterval      The wait time after sending Pending dataset before retrying (interval in ms).
      *
-     * @retval OT_ERROR_NONE           Dataset update started successfully (@p aCallback will be invoked on completion).
-     * @retval OT_ERROR_INVALID_STATE  Device is disabled (MLE is disabled).
-     * @retval OT_ERROR_INVALID_ARGS   The @p aDataset is not valid (contains Active or Pending Timestamp).
-     * @retval OT_ERROR_BUSY           Cannot start update, a previous one is ongoing.
-     * @retval OT_ERROR_NO_BUFS        Could not allocated buffer to save Dataset.
+     * @retval kErrorNone           Dataset update started successfully (@p aCallback will be invoked on completion).
+     * @retval kErrorInvalidState   Device is disabled (MLE is disabled).
+     * @retval kErrorInvalidArgs    The @p aDataset is not valid (contains Active or Pending Timestamp).
+     * @retval kErrorBusy           Cannot start update, a previous one is ongoing.
+     * @retval kErrorNoBufs         Could not allocated buffer to save Dataset.
      *
      */
-    otError RequestUpdate(const MeshCoP::Dataset::Info &aDataset,
-                          Callback                      aCallback,
-                          void *                        aContext,
-                          uint32_t                      aReryWaitInterval = kWaitInterval);
+    Error RequestUpdate(const MeshCoP::Dataset::Info &aDataset, Callback aCallback, void *aContext);
 
     /**
      * This method cancels an ongoing (if any) Operational Dataset update request.
@@ -120,36 +116,22 @@
      * @retval FALSE   There is no ongoing update.
      *
      */
-    bool IsUpdateOngoing(void) const { return (mState != kStateIdle); }
+    bool IsUpdateOngoing(void) const { return mDataset != nullptr; }
 
 private:
-    enum State : uint8_t
-    {
-        kStateIdle,
-        kStateUpdateRequested,
-        kStateSentMgmtPendingDataset,
-    };
-
     enum : uint32_t
     {
-        // Default delay (in ms) in Pending Dataset.
-        kDefaultDelay = OPENTHREAD_CONFIG_DATASET_UPDATER_DEFAULT_DELAY,
+        kDefaultDelay = OPENTHREAD_CONFIG_DATASET_UPDATER_DEFAULT_DELAY, // Default delay (in ms) in Pending Dataset.
 
-        // Default wait interval (in ms) after sending Pending Dataset to retry (in addition Dataset Delay)
-        kWaitInterval = OPENTHREAD_CONFIG_DATASET_UPDATER_DEFAULT_RETRY_WAIT_INTERVAL,
-
-        kRetryInterval        = 1000, // In ms. Retry interval when preparing and/or sending Pending Dataset fails.
-        kMaxTimestampIncrease = 128,  // Maximum increase of Pending/Active Timestamp during Dataset Update.
+        kRetryInterval = 1000, // In ms. Retry interval when preparing and/or sending Pending Dataset fails.
     };
 
     static void HandleTimer(Timer &aTimer);
     void        HandleTimer(void);
     void        PreparePendingDataset(void);
-    void        Finish(otError aError);
+    void        Finish(Error aError);
     void        HandleNotifierEvents(Events aEvents);
 
-    State      mState;
-    uint32_t   mWaitInterval;
     Callback   mCallback;
     void *     mCallbackContext;
     TimerMilli mTimer;
@@ -158,7 +140,7 @@
 
 #endif // (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD
 
-} // namespace Utils
+} // namespace MeshCoP
 } // namespace ot
 
 #endif // DATASET_UPDATER_HPP_
diff --git a/src/core/meshcop/dtls.cpp b/src/core/meshcop/dtls.cpp
index e10096a..57ab274 100644
--- a/src/core/meshcop/dtls.cpp
+++ b/src/core/meshcop/dtls.cpp
@@ -56,8 +56,8 @@
 namespace MeshCoP {
 
 const mbedtls_ecp_group_id Dtls::sCurves[] = {MBEDTLS_ECP_DP_SECP256R1, MBEDTLS_ECP_DP_NONE};
-#ifdef MBEDTLS_KEY_EXCHANGE__WITH_CERT__ENABLED
-const int Dtls::sHashes[] = {MBEDTLS_MD_NONE};
+#if defined(MBEDTLS_KEY_EXCHANGE__WITH_CERT__ENABLED) || defined(MBEDTLS_KEY_EXCHANGE_WITH_CERT_ENABLED)
+const int Dtls::sHashes[] = {MBEDTLS_MD_SHA256, MBEDTLS_MD_NONE};
 #endif
 
 Dtls::Dtls(Instance &aInstance, bool aLayerTwoSecurity)
@@ -126,11 +126,11 @@
     mbedtls_ssl_free(&mSsl);
 }
 
-otError Dtls::Open(ReceiveHandler aReceiveHandler, ConnectedHandler aConnectedHandler, void *aContext)
+Error Dtls::Open(ReceiveHandler aReceiveHandler, ConnectedHandler aConnectedHandler, void *aContext)
 {
-    otError error;
+    Error error;
 
-    VerifyOrExit(mState == kStateClosed, error = OT_ERROR_ALREADY);
+    VerifyOrExit(mState == kStateClosed, error = kErrorAlready);
 
     SuccessOrExit(error = mSocket.Open(&Dtls::HandleUdpReceive, this));
 
@@ -143,11 +143,11 @@
     return error;
 }
 
-otError Dtls::Connect(const Ip6::SockAddr &aSockAddr)
+Error Dtls::Connect(const Ip6::SockAddr &aSockAddr)
 {
-    otError error;
+    Error error;
 
-    VerifyOrExit(mState == kStateOpen, error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(mState == kStateOpen, error = kErrorInvalidState);
 
     mMessageInfo.SetPeerAddr(aSockAddr.GetAddress());
     mMessageInfo.SetPeerPort(aSockAddr.mPort);
@@ -208,26 +208,26 @@
     return;
 }
 
-otError Dtls::Bind(uint16_t aPort)
+Error Dtls::Bind(uint16_t aPort)
 {
-    otError error;
+    Error error;
 
-    VerifyOrExit(mState == kStateOpen, error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(mTransportCallback == nullptr, error = OT_ERROR_ALREADY);
+    VerifyOrExit(mState == kStateOpen, error = kErrorInvalidState);
+    VerifyOrExit(mTransportCallback == nullptr, error = kErrorAlready);
 
-    SuccessOrExit(error = mSocket.Bind(aPort));
+    SuccessOrExit(error = mSocket.Bind(aPort, OT_NETIF_UNSPECIFIED));
 
 exit:
     return error;
 }
 
-otError Dtls::Bind(TransportCallback aCallback, void *aContext)
+Error Dtls::Bind(TransportCallback aCallback, void *aContext)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(mState == kStateOpen, error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(!mSocket.IsBound(), error = OT_ERROR_ALREADY);
-    VerifyOrExit(mTransportCallback == nullptr, error = OT_ERROR_ALREADY);
+    VerifyOrExit(mState == kStateOpen, error = kErrorInvalidState);
+    VerifyOrExit(!mSocket.IsBound(), error = kErrorAlready);
+    VerifyOrExit(mTransportCallback == nullptr, error = kErrorAlready);
 
     mTransportCallback = aCallback;
     mTransportContext  = aContext;
@@ -236,7 +236,7 @@
     return error;
 }
 
-otError Dtls::Setup(bool aClient)
+Error Dtls::Setup(bool aClient)
 {
     int rval;
 
@@ -284,7 +284,7 @@
     if (mCipherSuites[0] == MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8)
     {
         mbedtls_ssl_conf_curves(&mConf, sCurves);
-#ifdef MBEDTLS_KEY_EXCHANGE__WITH_CERT__ENABLED
+#if defined(MBEDTLS_KEY_EXCHANGE__WITH_CERT__ENABLED) || defined(MBEDTLS_KEY_EXCHANGE_WITH_CERT_ENABLED)
         mbedtls_ssl_conf_sig_hashes(&mConf, sHashes);
 #endif
     }
@@ -431,11 +431,11 @@
     return;
 }
 
-otError Dtls::SetPsk(const uint8_t *aPsk, uint8_t aPskLength)
+Error Dtls::SetPsk(const uint8_t *aPsk, uint8_t aPskLength)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(aPskLength <= sizeof(mPsk), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aPskLength <= sizeof(mPsk), error = kErrorInvalidArgs);
 
     memcpy(mPsk, aPsk, aPskLength);
     mPskLength       = aPskLength;
@@ -500,15 +500,15 @@
 
 #ifdef MBEDTLS_BASE64_C
 
-otError Dtls::GetPeerCertificateBase64(unsigned char *aPeerCert, size_t *aCertLength, size_t aCertBufferSize)
+Error Dtls::GetPeerCertificateBase64(unsigned char *aPeerCert, size_t *aCertLength, size_t aCertBufferSize)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(mState == kStateConnected, error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(mState == kStateConnected, error = kErrorInvalidState);
 
     VerifyOrExit(mbedtls_base64_encode(aPeerCert, aCertBufferSize, aCertLength, mSsl.session->peer_cert->raw.p,
                                        mSsl.session->peer_cert->raw.len) == 0,
-                 error = OT_ERROR_NO_BUFS);
+                 error = kErrorNoBufs);
 
 exit:
     return error;
@@ -518,19 +518,19 @@
 #endif // OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE
 
 #ifdef MBEDTLS_SSL_SRV_C
-otError Dtls::SetClientId(const uint8_t *aClientId, uint8_t aLength)
+Error Dtls::SetClientId(const uint8_t *aClientId, uint8_t aLength)
 {
     int rval = mbedtls_ssl_set_client_transport_id(&mSsl, aClientId, aLength);
     return Crypto::MbedTls::MapError(rval);
 }
 #endif
 
-otError Dtls::Send(Message &aMessage, uint16_t aLength)
+Error Dtls::Send(Message &aMessage, uint16_t aLength)
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     uint8_t buffer[kApplicationDataMaxLength];
 
-    VerifyOrExit(aLength <= kApplicationDataMaxLength, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(aLength <= kApplicationDataMaxLength, error = kErrorNoBufs);
 
     // Store message specific sub type.
     if (aMessage.GetSubType() != Message::kSubTypeNone)
@@ -564,8 +564,8 @@
 
 int Dtls::HandleMbedtlsTransmit(const unsigned char *aBuf, size_t aLength)
 {
-    otError error;
-    int     rval = 0;
+    Error error;
+    int   rval = 0;
 
     if (mCipherSuites[0] == MBEDTLS_TLS_ECJPAKE_WITH_AES_128_CCM_8)
     {
@@ -585,16 +585,16 @@
 
     switch (error)
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         rval = static_cast<int>(aLength);
         break;
 
-    case OT_ERROR_NO_BUFS:
+    case kErrorNoBufs:
         rval = MBEDTLS_ERR_SSL_WANT_WRITE;
         break;
 
     default:
-        otLogWarnMeshCoP("Dtls::HandleMbedtlsTransmit: %s error", otThreadErrorToString(error));
+        otLogWarnMeshCoP("Dtls::HandleMbedtlsTransmit: %s error", ErrorToString(error));
         rval = MBEDTLS_ERR_NET_SEND_FAILED;
         break;
     }
@@ -781,7 +781,7 @@
 
 void Dtls::Process(void)
 {
-    uint8_t buf[MBEDTLS_SSL_MAX_CONTENT_LEN];
+    uint8_t buf[OPENTHREAD_CONFIG_DTLS_MAX_CONTENT_LEN];
     bool    shouldDisconnect = false;
     int     rval;
 
@@ -904,12 +904,12 @@
     }
 }
 
-otError Dtls::HandleDtlsSend(const uint8_t *aBuf, uint16_t aLength, Message::SubType aMessageSubType)
+Error Dtls::HandleDtlsSend(const uint8_t *aBuf, uint16_t aLength, Message::SubType aMessageSubType)
 {
-    otError      error   = OT_ERROR_NONE;
+    Error        error   = kErrorNone;
     ot::Message *message = nullptr;
 
-    VerifyOrExit((message = mSocket.NewMessage(0)) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = mSocket.NewMessage(0)) != nullptr, error = kErrorNoBufs);
     message->SetSubType(aMessageSubType);
     message->SetLinkSecurityEnabled(mLayerTwoSecurity);
 
diff --git a/src/core/meshcop/dtls.hpp b/src/core/meshcop/dtls.hpp
index a3cc414..0e507d0 100644
--- a/src/core/meshcop/dtls.hpp
+++ b/src/core/meshcop/dtls.hpp
@@ -108,7 +108,7 @@
      * @param[in]  aMessageInfo  A reference to the message info associated with @p aMessage.
      *
      */
-    typedef otError (*TransportCallback)(void *aContext, ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    typedef Error (*TransportCallback)(void *aContext, ot::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
 
     /**
      * This method opens the DTLS socket.
@@ -117,23 +117,23 @@
      * @param[in]  aConnectedHandler    A pointer to a function that is called when connected or disconnected.
      * @param[in]  aContext             A pointer to arbitrary context information.
      *
-     * @retval OT_ERROR_NONE     Successfully opened the socket.
-     * @retval OT_ERROR_ALREADY  The DTLS is already open.
+     * @retval kErrorNone     Successfully opened the socket.
+     * @retval kErrorAlready  The DTLS is already open.
      *
      */
-    otError Open(ReceiveHandler aReceiveHandler, ConnectedHandler aConnectedHandler, void *aContext);
+    Error Open(ReceiveHandler aReceiveHandler, ConnectedHandler aConnectedHandler, void *aContext);
 
     /**
      * This method binds this DTLS to a UDP port.
      *
      * @param[in]  aPort              The port to bind.
      *
-     * @retval OT_ERROR_NONE           Successfully bound the DTLS socket.
-     * @retval OT_ERROR_INVALID_STATE  The DTLS socket is not open.
-     * @retval OT_ERROR_ALREADY        Already bound.
+     * @retval kErrorNone           Successfully bound the DTLS socket.
+     * @retval kErrorInvalidState   The DTLS socket is not open.
+     * @retval kErrorAlready        Already bound.
      *
      */
-    otError Bind(uint16_t aPort);
+    Error Bind(uint16_t aPort);
 
     /**
      * This method binds this DTLS with a transport callback.
@@ -141,12 +141,12 @@
      * @param[in]  aCallback  A pointer to a function for sending messages.
      * @param[in]  aContext   A pointer to arbitrary context information.
      *
-     * @retval OT_ERROR_NONE           Successfully bound the DTLS socket.
-     * @retval OT_ERROR_INVALID_STATE  The DTLS socket is not open.
-     * @retval OT_ERROR_ALREADY        Already bound.
+     * @retval kErrorNone           Successfully bound the DTLS socket.
+     * @retval kErrorInvalidState   The DTLS socket is not open.
+     * @retval kErrorAlready        Already bound.
      *
      */
-    otError Bind(TransportCallback aCallback, void *aContext);
+    Error Bind(TransportCallback aCallback, void *aContext);
 
     /**
      * This method establishes a DTLS session.
@@ -157,11 +157,11 @@
      *
      * @param[in]  aSockAddr               A reference to the remote sockaddr.
      *
-     * @retval OT_ERROR_NONE           Successfully started DTLS handshake.
-     * @retval OT_ERROR_INVALID_STATE  The DTLS socket is not open.
+     * @retval kErrorNone          Successfully started DTLS handshake.
+     * @retval kErrorInvalidState  The DTLS socket is not open.
      *
      */
-    otError Connect(const Ip6::SockAddr &aSockAddr);
+    Error Connect(const Ip6::SockAddr &aSockAddr);
 
     /**
      * This method indicates whether or not the DTLS session is active.
@@ -198,11 +198,11 @@
      *
      * @param[in]  aPsk  A pointer to the PSK.
      *
-     * @retval OT_ERROR_NONE          Successfully set the PSK.
-     * @retval OT_ERROR_INVALID_ARGS  The PSK is invalid.
+     * @retval kErrorNone          Successfully set the PSK.
+     * @retval kErrorInvalidArgs   The PSK is invalid.
      *
      */
-    otError SetPsk(const uint8_t *aPsk, uint8_t aPskLength);
+    Error SetPsk(const uint8_t *aPsk, uint8_t aPskLength);
 
 #if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE
 #ifdef MBEDTLS_KEY_EXCHANGE_PSK_ENABLED
@@ -217,7 +217,7 @@
      * @param[in]  aPskIdentity  The Identity Name for the PSK.
      * @param[in]  aPskIdLength  The PSK Identity Length.
      *
-     * @retval OT_ERROR_NONE  Successfully set the PSK.
+     * @retval kErrorNone  Successfully set the PSK.
      *
      */
     void SetPreSharedKey(const uint8_t *aPsk, uint16_t aPskLength, const uint8_t *aPskIdentity, uint16_t aPskIdLength);
@@ -264,12 +264,12 @@
      * @param[out]  aCertLength      The length of the base64 encoded peer certificate.
      * @param[in]   aCertBufferSize  The buffer size of aPeerCert.
      *
-     * @retval OT_ERROR_INVALID_STATE   Not connected yet.
-     * @retval OT_ERROR_NONE            Successfully get the peer certificate.
-     * @retval OT_ERROR_NO_BUFS         Can't allocate memory for certificate.
+     * @retval kErrorInvalidState   Not connected yet.
+     * @retval kErrorNone           Successfully get the peer certificate.
+     * @retval kErrorNoBufs         Can't allocate memory for certificate.
      *
      */
-    otError GetPeerCertificateBase64(unsigned char *aPeerCert, size_t *aCertLength, size_t aCertBufferSize);
+    Error GetPeerCertificateBase64(unsigned char *aPeerCert, size_t *aCertLength, size_t aCertBufferSize);
 #endif
 
     /**
@@ -291,10 +291,10 @@
      * @param[in]  aClientId  A pointer to the Client ID.
      * @param[in]  aLength    Number of bytes in the Client ID.
      *
-     * @retval OT_ERROR_NONE  Successfully set the Client ID.
+     * @retval kErrorNone  Successfully set the Client ID.
      *
      */
-    otError SetClientId(const uint8_t *aClientId, uint8_t aLength);
+    Error SetClientId(const uint8_t *aClientId, uint8_t aLength);
 #endif
 
     /**
@@ -303,11 +303,11 @@
      * @param[in]  aMessage  A message to send via DTLS.
      * @param[in]  aLength   Number of bytes in the data buffer.
      *
-     * @retval OT_ERROR_NONE     Successfully sent the data via the DTLS session.
-     * @retval OT_ERROR_NO_BUFS  A message is too long.
+     * @retval kErrorNone     Successfully sent the data via the DTLS session.
+     * @retval kErrorNoBufs   A message is too long.
      *
      */
-    otError Send(Message &aMessage, uint16_t aLength);
+    Error Send(Message &aMessage, uint16_t aLength);
 
     /**
      * This method provides a received DTLS message to the DTLS object.
@@ -357,8 +357,8 @@
 #endif
     };
 
-    void    FreeMbedtls(void);
-    otError Setup(bool aClient);
+    void  FreeMbedtls(void);
+    Error Setup(bool aClient);
 
 #if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE
     /**
@@ -402,8 +402,8 @@
 
     static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
 
-    void    HandleDtlsReceive(const uint8_t *aBuf, uint16_t aLength);
-    otError HandleDtlsSend(const uint8_t *aBuf, uint16_t aLength, Message::SubType aMessageSubType);
+    void  HandleDtlsReceive(const uint8_t *aBuf, uint16_t aLength);
+    Error HandleDtlsSend(const uint8_t *aBuf, uint16_t aLength, Message::SubType aMessageSubType);
 
     void Process(void);
 
@@ -414,7 +414,7 @@
     uint8_t mPskLength;
 
     static const mbedtls_ecp_group_id sCurves[];
-#ifdef MBEDTLS_KEY_EXCHANGE__WITH_CERT__ENABLED
+#if defined(MBEDTLS_KEY_EXCHANGE__WITH_CERT__ENABLED) || defined(MBEDTLS_KEY_EXCHANGE_WITH_CERT_ENABLED)
     static const int sHashes[];
 #endif
 
diff --git a/src/core/meshcop/energy_scan_client.cpp b/src/core/meshcop/energy_scan_client.cpp
index f279878..f40e5ab 100644
--- a/src/core/meshcop/energy_scan_client.cpp
+++ b/src/core/meshcop/energy_scan_client.cpp
@@ -58,21 +58,21 @@
     Get<Tmf::TmfAgent>().AddResource(mEnergyScan);
 }
 
-otError EnergyScanClient::SendQuery(uint32_t                           aChannelMask,
-                                    uint8_t                            aCount,
-                                    uint16_t                           aPeriod,
-                                    uint16_t                           aScanDuration,
-                                    const Ip6::Address &               aAddress,
-                                    otCommissionerEnergyReportCallback aCallback,
-                                    void *                             aContext)
+Error EnergyScanClient::SendQuery(uint32_t                           aChannelMask,
+                                  uint8_t                            aCount,
+                                  uint16_t                           aPeriod,
+                                  uint16_t                           aScanDuration,
+                                  const Ip6::Address &               aAddress,
+                                  otCommissionerEnergyReportCallback aCallback,
+                                  void *                             aContext)
 {
-    otError                 error = OT_ERROR_NONE;
+    Error                   error = kErrorNone;
     MeshCoP::ChannelMaskTlv channelMask;
     Ip6::MessageInfo        messageInfo;
     Coap::Message *         message = nullptr;
 
-    VerifyOrExit(Get<MeshCoP::Commissioner>().IsActive(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit((message = MeshCoP::NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(Get<MeshCoP::Commissioner>().IsActive(), error = kErrorInvalidState);
+    VerifyOrExit((message = MeshCoP::NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsPost(aAddress, UriPath::kEnergyScan));
     SuccessOrExit(error = message->SetPayloadMarker());
diff --git a/src/core/meshcop/energy_scan_client.hpp b/src/core/meshcop/energy_scan_client.hpp
index ae0074e..6e14842 100644
--- a/src/core/meshcop/energy_scan_client.hpp
+++ b/src/core/meshcop/energy_scan_client.hpp
@@ -69,17 +69,17 @@
      * @param[in]  aCallback      A pointer to a function called on receiving an Energy Report message.
      * @param[in]  aContext       A pointer to application-specific context.
      *
-     * @retval OT_ERROR_NONE     Successfully enqueued the Energy Scan Query message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers to generate an Energy Scan Query message.
+     * @retval kErrorNone     Successfully enqueued the Energy Scan Query message.
+     * @retval kErrorNoBufs   Insufficient buffers to generate an Energy Scan Query message.
      *
      */
-    otError SendQuery(uint32_t                           aChannelMask,
-                      uint8_t                            aCount,
-                      uint16_t                           aPeriod,
-                      uint16_t                           aScanDuration,
-                      const Ip6::Address &               aAddress,
-                      otCommissionerEnergyReportCallback aCallback,
-                      void *                             aContext);
+    Error SendQuery(uint32_t                           aChannelMask,
+                    uint8_t                            aCount,
+                    uint16_t                           aPeriod,
+                    uint16_t                           aScanDuration,
+                    const Ip6::Address &               aAddress,
+                    otCommissionerEnergyReportCallback aCallback,
+                    void *                             aContext);
 
 private:
     static void HandleReport(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
diff --git a/src/core/meshcop/joiner.cpp b/src/core/meshcop/joiner.cpp
index 349a4c6..176bbdd 100644
--- a/src/core/meshcop/joiner.cpp
+++ b/src/core/meshcop/joiner.cpp
@@ -62,7 +62,7 @@
     , mContext(nullptr)
     , mJoinerRouterIndex(0)
     , mFinalizeMessage(nullptr)
-    , mTimer(aInstance, Joiner::HandleTimer, this)
+    , mTimer(aInstance, Joiner::HandleTimer)
     , mJoinerEntrust(UriPath::kJoinerEntrust, &Joiner::HandleJoinerEntrust, this)
 {
     SetIdFromIeeeEui64();
@@ -84,12 +84,12 @@
     return mDiscerner.IsEmpty() ? nullptr : &mDiscerner;
 }
 
-otError Joiner::SetDiscerner(const JoinerDiscerner &aDiscerner)
+Error Joiner::SetDiscerner(const JoinerDiscerner &aDiscerner)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(aDiscerner.IsValid(), error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit(mState == kStateIdle, error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(aDiscerner.IsValid(), error = kErrorInvalidArgs);
+    VerifyOrExit(mState == kStateIdle, error = kErrorInvalidState);
 
     mDiscerner = aDiscerner;
     mDiscerner.GenerateJoinerId(mId);
@@ -98,11 +98,11 @@
     return error;
 }
 
-otError Joiner::ClearDiscerner(void)
+Error Joiner::ClearDiscerner(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(mState == kStateIdle, error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(mState == kStateIdle, error = kErrorInvalidState);
     VerifyOrExit(!mDiscerner.IsEmpty());
 
     mDiscerner.Clear();
@@ -124,29 +124,29 @@
     return;
 }
 
-otError Joiner::Start(const char *     aPskd,
-                      const char *     aProvisioningUrl,
-                      const char *     aVendorName,
-                      const char *     aVendorModel,
-                      const char *     aVendorSwVersion,
-                      const char *     aVendorData,
-                      otJoinerCallback aCallback,
-                      void *           aContext)
+Error Joiner::Start(const char *     aPskd,
+                    const char *     aProvisioningUrl,
+                    const char *     aVendorName,
+                    const char *     aVendorModel,
+                    const char *     aVendorSwVersion,
+                    const char *     aVendorData,
+                    otJoinerCallback aCallback,
+                    void *           aContext)
 {
-    otError                      error;
+    Error                        error;
     JoinerPskd                   joinerPskd;
     Mac::ExtAddress              randomAddress;
     SteeringData::HashBitIndexes filterIndexes;
 
     otLogInfoMeshCoP("Joiner starting");
 
-    VerifyOrExit(aProvisioningUrl == nullptr || IsValidUtf8String(aProvisioningUrl), error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit(aVendorName == nullptr || IsValidUtf8String(aVendorName), error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit(aVendorSwVersion == nullptr || IsValidUtf8String(aVendorSwVersion), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aProvisioningUrl == nullptr || IsValidUtf8String(aProvisioningUrl), error = kErrorInvalidArgs);
+    VerifyOrExit(aVendorName == nullptr || IsValidUtf8String(aVendorName), error = kErrorInvalidArgs);
+    VerifyOrExit(aVendorSwVersion == nullptr || IsValidUtf8String(aVendorSwVersion), error = kErrorInvalidArgs);
 
-    VerifyOrExit(mState == kStateIdle, error = OT_ERROR_BUSY);
+    VerifyOrExit(mState == kStateIdle, error = kErrorBusy);
     VerifyOrExit(Get<ThreadNetif>().IsUp() && Get<Mle::Mle>().GetRole() == Mle::kRoleDisabled,
-                 error = OT_ERROR_INVALID_STATE);
+                 error = kErrorInvalidState);
 
     SuccessOrExit(error = joinerPskd.SetFrom(aPskd));
 
@@ -184,7 +184,7 @@
     SetState(kStateDiscover);
 
 exit:
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         FreeJoinerFinalizeMessage();
     }
@@ -199,10 +199,10 @@
 
     // Callback is set to `nullptr` to skip calling it from `Finish()`
     mCallback = nullptr;
-    Finish(OT_ERROR_ABORT);
+    Finish(kErrorAbort);
 }
 
-void Joiner::Finish(otError aError)
+void Joiner::Finish(Error aError)
 {
     switch (mState)
     {
@@ -217,7 +217,7 @@
         IgnoreError(Get<Ip6::Filter>().RemoveUnsecurePort(kJoinerUdpPort));
         mTimer.Stop();
 
-        // Fall through
+        OT_FALL_THROUGH;
 
     case kStateDiscover:
         Get<Coap::CoapSecure>().Stop();
@@ -290,7 +290,7 @@
         Get<Mle::MleRouter>().UpdateLinkLocalAddress();
 
         mJoinerRouterIndex = 0;
-        TryNextJoinerRouter(OT_ERROR_NONE);
+        TryNextJoinerRouter(kErrorNone);
     }
 
 exit:
@@ -339,12 +339,12 @@
     return;
 }
 
-void Joiner::TryNextJoinerRouter(otError aPrevError)
+void Joiner::TryNextJoinerRouter(Error aPrevError)
 {
     for (; mJoinerRouterIndex < OT_ARRAY_LENGTH(mJoinerRouters); mJoinerRouterIndex++)
     {
         JoinerRouter &router = mJoinerRouters[mJoinerRouterIndex];
-        otError       error;
+        Error         error;
 
         if (router.mPriority == 0)
         {
@@ -352,7 +352,7 @@
         }
 
         error = Connect(router);
-        VerifyOrExit(error != OT_ERROR_NONE, mJoinerRouterIndex++);
+        VerifyOrExit(error != kErrorNone, mJoinerRouterIndex++);
 
         // Save the error from `Connect` only if there is no previous
         // error from earlier attempts. This ensures that if there has
@@ -361,15 +361,15 @@
         // emitted from `Finish()` call corresponds to the error from
         // that attempt.
 
-        if (aPrevError == OT_ERROR_NONE)
+        if (aPrevError == kErrorNone)
         {
             aPrevError = error;
         }
     }
 
-    if (aPrevError == OT_ERROR_NONE)
+    if (aPrevError == kErrorNone)
     {
-        aPrevError = OT_ERROR_NOT_FOUND;
+        aPrevError = kErrorNotFound;
     }
 
     Finish(aPrevError);
@@ -378,9 +378,9 @@
     return;
 }
 
-otError Joiner::Connect(JoinerRouter &aRouter)
+Error Joiner::Connect(JoinerRouter &aRouter)
 {
-    otError       error = OT_ERROR_NOT_FOUND;
+    Error         error = kErrorNotFound;
     Ip6::SockAddr sockAddr(aRouter.mJoinerUdpPort);
 
     otLogInfoMeshCoP("Joiner connecting to %s, pan:0x%04x, chan:%d", aRouter.mExtAddr.ToString().AsCString(),
@@ -418,27 +418,27 @@
     }
     else
     {
-        TryNextJoinerRouter(OT_ERROR_SECURITY);
+        TryNextJoinerRouter(kErrorSecurity);
     }
 
 exit:
     return;
 }
 
-otError Joiner::PrepareJoinerFinalizeMessage(const char *aProvisioningUrl,
-                                             const char *aVendorName,
-                                             const char *aVendorModel,
-                                             const char *aVendorSwVersion,
-                                             const char *aVendorData)
+Error Joiner::PrepareJoinerFinalizeMessage(const char *aProvisioningUrl,
+                                           const char *aVendorName,
+                                           const char *aVendorModel,
+                                           const char *aVendorSwVersion,
+                                           const char *aVendorData)
 {
-    otError               error = OT_ERROR_NONE;
+    Error                 error = kErrorNone;
     VendorNameTlv         vendorNameTlv;
     VendorModelTlv        vendorModelTlv;
     VendorSwVersionTlv    vendorSwVersionTlv;
     VendorStackVersionTlv vendorStackVersionTlv;
     ProvisioningUrlTlv    provisioningUrlTlv;
 
-    VerifyOrExit((mFinalizeMessage = NewMeshCoPMessage(Get<Coap::CoapSecure>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((mFinalizeMessage = NewMeshCoPMessage(Get<Coap::CoapSecure>())) != nullptr, error = kErrorNoBufs);
 
     mFinalizeMessage->InitAsConfirmablePost();
     SuccessOrExit(error = mFinalizeMessage->AppendUriPathOptions(UriPath::kJoinerFinalize));
@@ -483,7 +483,7 @@
     }
 
 exit:
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         FreeJoinerFinalizeMessage();
     }
@@ -522,21 +522,19 @@
 void Joiner::HandleJoinerFinalizeResponse(void *               aContext,
                                           otMessage *          aMessage,
                                           const otMessageInfo *aMessageInfo,
-                                          otError              aResult)
+                                          Error                aResult)
 {
     static_cast<Joiner *>(aContext)->HandleJoinerFinalizeResponse(
         *static_cast<Coap::Message *>(aMessage), static_cast<const Ip6::MessageInfo *>(aMessageInfo), aResult);
 }
 
-void Joiner::HandleJoinerFinalizeResponse(Coap::Message &         aMessage,
-                                          const Ip6::MessageInfo *aMessageInfo,
-                                          otError                 aResult)
+void Joiner::HandleJoinerFinalizeResponse(Coap::Message &aMessage, const Ip6::MessageInfo *aMessageInfo, Error aResult)
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
 
     uint8_t state;
 
-    VerifyOrExit(mState == kStateConnected && aResult == OT_ERROR_NONE && aMessage.IsAck() &&
+    VerifyOrExit(mState == kStateConnected && aResult == kErrorNone && aMessage.IsAck() &&
                  aMessage.GetCode() == Coap::kCodeChanged);
 
     SuccessOrExit(Tlv::Find<StateTlv>(aMessage, state));
@@ -563,10 +561,10 @@
 
 void Joiner::HandleJoinerEntrust(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError       error;
+    Error         error;
     Dataset::Info datasetInfo;
 
-    VerifyOrExit(mState == kStateEntrust && aMessage.IsConfirmablePostRequest(), error = OT_ERROR_DROP);
+    VerifyOrExit(mState == kStateEntrust && aMessage.IsConfirmablePostRequest(), error = kErrorDrop);
 
     otLogInfoMeshCoP("Joiner received entrust");
     otLogCertMeshCoP("[THCI] direction=recv | type=JOIN_ENT.ntf");
@@ -593,11 +591,11 @@
 
 void Joiner::SendJoinerEntrustResponse(const Coap::Message &aRequest, const Ip6::MessageInfo &aRequestInfo)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     Coap::Message *  message;
     Ip6::MessageInfo responseInfo(aRequestInfo);
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = message->SetDefaultResponseHeader(aRequest));
     message->SetSubType(Message::kSubTypeJoinerEntrust);
 
@@ -615,12 +613,12 @@
 
 void Joiner::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<Joiner>().HandleTimer();
+    aTimer.Get<Joiner>().HandleTimer();
 }
 
 void Joiner::HandleTimer(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     switch (mState)
     {
@@ -632,7 +630,7 @@
 
     case kStateConnected:
     case kStateEntrust:
-        error = OT_ERROR_RESPONSE_TIMEOUT;
+        error = kErrorResponseTimeout;
         break;
 
     case kStateJoined:
@@ -642,7 +640,7 @@
         Get<Mac::Mac>().SetExtAddress(extAddress);
         Get<Mle::MleRouter>().UpdateLinkLocalAddress();
 
-        error = OT_ERROR_NONE;
+        error = kErrorNone;
         break;
     }
 
@@ -653,31 +651,23 @@
 
 const char *Joiner::StateToString(State aState)
 {
-    const char *str = "Unknown";
+    static const char *const kStateStrings[] = {
+        "Idle",       // (0) kStateIdle
+        "Discover",   // (1) kStateDiscover
+        "Connecting", // (2) kStateConnect
+        "Connected",  // (3) kStateConnected
+        "Entrust",    // (4) kStateEntrust
+        "Joined",     // (5) kStateJoined
+    };
 
-    switch (aState)
-    {
-    case kStateIdle:
-        str = "Idle";
-        break;
-    case kStateDiscover:
-        str = "Discover";
-        break;
-    case kStateConnect:
-        str = "Connecting";
-        break;
-    case kStateConnected:
-        str = "Connected";
-        break;
-    case kStateEntrust:
-        str = "Entrust";
-        break;
-    case kStateJoined:
-        str = "Joined";
-        break;
-    }
+    static_assert(kStateIdle == 0, "kStateIdle value is incorrect");
+    static_assert(kStateDiscover == 1, "kStateDiscover value is incorrect");
+    static_assert(kStateConnect == 2, "kStateConnect value is incorrect");
+    static_assert(kStateConnected == 3, "kStateConnected value is incorrect");
+    static_assert(kStateEntrust == 4, "kStateEntrust value is incorrect");
+    static_assert(kStateJoined == 5, "kStateJoined value is incorrect");
 
-    return str;
+    return kStateStrings[aState];
 }
 
 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
diff --git a/src/core/meshcop/joiner.hpp b/src/core/meshcop/joiner.hpp
index 176ad33..904a3ed 100644
--- a/src/core/meshcop/joiner.hpp
+++ b/src/core/meshcop/joiner.hpp
@@ -92,19 +92,19 @@
      * @param[in]  aCallback         A pointer to a function that is called when the join operation completes.
      * @param[in]  aContext          A pointer to application-specific context.
      *
-     * @retval OT_ERROR_NONE          Successfully started the Joiner service.
-     * @retval OT_ERROR_BUSY          The previous attempt is still on-going.
-     * @retval OT_ERROR_INVALID_STATE The IPv6 stack is not enabled or Thread stack is fully enabled.
+     * @retval kErrorNone          Successfully started the Joiner service.
+     * @retval kErrorBusy          The previous attempt is still on-going.
+     * @retval kErrorInvalidState  The IPv6 stack is not enabled or Thread stack is fully enabled.
      *
      */
-    otError Start(const char *     aPskd,
-                  const char *     aProvisioningUrl,
-                  const char *     aVendorName,
-                  const char *     aVendorModel,
-                  const char *     aVendorSwVersion,
-                  const char *     aVendorData,
-                  otJoinerCallback aCallback,
-                  void *           aContext);
+    Error Start(const char *     aPskd,
+                const char *     aProvisioningUrl,
+                const char *     aVendorName,
+                const char *     aVendorModel,
+                const char *     aVendorSwVersion,
+                const char *     aVendorData,
+                otJoinerCallback aCallback,
+                void *           aContext);
 
     /**
      * This method stops the Joiner service.
@@ -147,23 +147,23 @@
      *
      * @param[in]   aDiscerner  A Joiner Discerner
      *
-     * @retval OT_ERROR_NONE           The Joiner Discerner updated successfully.
-     * @retval OT_ERROR_INVALID_ARGS   @p aDiscerner is not valid (specified length is not within valid range).
-     * @retval OT_ERROR_INVALID_STATE  There is an ongoing Joining process so Joiner Discerner could not be changed.
+     * @retval kErrorNone          The Joiner Discerner updated successfully.
+     * @retval kErrorInvalidArgs   @p aDiscerner is not valid (specified length is not within valid range).
+     * @retval kErrorInvalidState  There is an ongoing Joining process so Joiner Discerner could not be changed.
      *
      */
-    otError SetDiscerner(const JoinerDiscerner &aDiscerner);
+    Error SetDiscerner(const JoinerDiscerner &aDiscerner);
 
     /**
      * This method clears any previously set Joiner Discerner.
      *
      * When cleared, Joiner ID is derived as first 64 bits of SHA-256 of factory-assigned IEEE EUI-64.
      *
-     * @retval OT_ERROR_NONE           The Joiner Discerner cleared and Joiner ID updated.
-     * @retval OT_ERROR_INVALID_STATE  There is an ongoing Joining process so Joiner Discerner could not be changed.
+     * @retval kErrorNone          The Joiner Discerner cleared and Joiner ID updated.
+     * @retval kErrorInvalidState  There is an ongoing Joining process so Joiner Discerner could not be changed.
      *
      */
-    otError ClearDiscerner(void);
+    Error ClearDiscerner(void);
 
 private:
     enum
@@ -191,8 +191,8 @@
     static void HandleJoinerFinalizeResponse(void *               aContext,
                                              otMessage *          aMessage,
                                              const otMessageInfo *aMessageInfo,
-                                             otError              aResult);
-    void HandleJoinerFinalizeResponse(Coap::Message &aMessage, const Ip6::MessageInfo *aMessageInfo, otError aResult);
+                                             Error                aResult);
+    void HandleJoinerFinalizeResponse(Coap::Message &aMessage, const Ip6::MessageInfo *aMessageInfo, Error aResult);
 
     static void HandleJoinerEntrust(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
     void        HandleJoinerEntrust(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
@@ -205,19 +205,19 @@
     void    SetState(State aState);
     void    SetIdFromIeeeEui64(void);
     void    SaveDiscoveredJoinerRouter(const Mle::DiscoverScanner::ScanResult &aResult);
-    void    TryNextJoinerRouter(otError aPrevError);
-    otError Connect(JoinerRouter &aRouter);
-    void    Finish(otError aError);
+    void    TryNextJoinerRouter(Error aPrevError);
+    Error   Connect(JoinerRouter &aRouter);
+    void    Finish(Error aError);
     uint8_t CalculatePriority(int8_t aRssi, bool aSteeringDataAllowsAny);
 
-    otError PrepareJoinerFinalizeMessage(const char *aProvisioningUrl,
-                                         const char *aVendorName,
-                                         const char *aVendorModel,
-                                         const char *aVendorSwVersion,
-                                         const char *aVendorData);
-    void    FreeJoinerFinalizeMessage(void);
-    void    SendJoinerFinalize(void);
-    void    SendJoinerEntrustResponse(const Coap::Message &aRequest, const Ip6::MessageInfo &aRequestInfo);
+    Error PrepareJoinerFinalizeMessage(const char *aProvisioningUrl,
+                                       const char *aVendorName,
+                                       const char *aVendorModel,
+                                       const char *aVendorSwVersion,
+                                       const char *aVendorData);
+    void  FreeJoinerFinalizeMessage(void);
+    void  SendJoinerFinalize(void);
+    void  SendJoinerEntrustResponse(const Coap::Message &aRequest, const Ip6::MessageInfo &aRequestInfo);
 
 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
     void LogCertMessage(const char *aText, const Coap::Message &aMessage) const;
diff --git a/src/core/meshcop/joiner_router.cpp b/src/core/meshcop/joiner_router.cpp
index 7ceb9be..7216ad6 100644
--- a/src/core/meshcop/joiner_router.cpp
+++ b/src/core/meshcop/joiner_router.cpp
@@ -55,7 +55,7 @@
     : InstanceLocator(aInstance)
     , mSocket(aInstance)
     , mRelayTransmit(UriPath::kRelayTx, &JoinerRouter::HandleRelayTransmit, this)
-    , mTimer(aInstance, JoinerRouter::HandleTimer, this)
+    , mTimer(aInstance, JoinerRouter::HandleTimer)
     , mJoinerUdpPort(0)
     , mIsJoinerPortConfigured(false)
 {
@@ -130,7 +130,7 @@
 
 void JoinerRouter::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError          error;
+    Error            error;
     Coap::Message *  message = nullptr;
     Ip6::MessageInfo messageInfo;
     ExtendedTlv      tlv;
@@ -141,7 +141,7 @@
 
     SuccessOrExit(error = GetBorderAgentRloc(Get<ThreadNetif>(), borderAgentRloc));
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsNonConfirmablePost(UriPath::kRelayRx));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -180,7 +180,7 @@
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
 
-    otError                  error;
+    Error                    error;
     uint16_t                 joinerPort;
     Ip6::InterfaceIdentifier joinerIid;
     Kek                      kek;
@@ -190,7 +190,7 @@
     Message::Settings        settings(Message::kNoLinkSecurity, Message::kPriorityNet);
     Ip6::MessageInfo         messageInfo;
 
-    VerifyOrExit(aMessage.IsNonConfirmablePostRequest(), error = OT_ERROR_DROP);
+    VerifyOrExit(aMessage.IsNonConfirmablePostRequest(), error = kErrorDrop);
 
     otLogInfoMeshCoP("Received relay transmit");
 
@@ -199,7 +199,7 @@
 
     SuccessOrExit(error = Tlv::FindTlvValueOffset(aMessage, Tlv::kJoinerDtlsEncapsulation, offset, length));
 
-    VerifyOrExit((message = mSocket.NewMessage(0, settings)) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = mSocket.NewMessage(0, settings)) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->SetLength(length));
     aMessage.CopyTo(offset, 0, length, *message);
@@ -209,7 +209,7 @@
 
     SuccessOrExit(error = mSocket.SendTo(*message, messageInfo));
 
-    if (Tlv::Find<JoinerRouterKekTlv>(aMessage, kek) == OT_ERROR_NONE)
+    if (Tlv::Find<JoinerRouterKekTlv>(aMessage, kek) == kErrorNone)
     {
         otLogInfoMeshCoP("Received kek");
 
@@ -222,11 +222,11 @@
 
 void JoinerRouter::DelaySendingJoinerEntrust(const Ip6::MessageInfo &aMessageInfo, const Kek &aKek)
 {
-    otError               error   = OT_ERROR_NONE;
+    Error                 error   = kErrorNone;
     Message *             message = Get<MessagePool>().New(Message::kTypeOther, 0);
     JoinerEntrustMetadata metadata;
 
-    VerifyOrExit(message != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(message != nullptr, error = kErrorNoBufs);
 
     metadata.mMessageInfo = aMessageInfo;
     metadata.mMessageInfo.SetPeerPort(Tmf::kUdpPort);
@@ -249,7 +249,7 @@
 
 void JoinerRouter::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<JoinerRouter>().HandleTimer();
+    aTimer.Get<JoinerRouter>().HandleTimer();
 }
 
 void JoinerRouter::HandleTimer(void)
@@ -278,7 +278,7 @@
 
         Get<KeyManager>().SetKek(metadata.mKek);
 
-        if (SendJoinerEntrust(metadata.mMessageInfo) != OT_ERROR_NONE)
+        if (SendJoinerEntrust(metadata.mMessageInfo) != kErrorNone)
         {
             mTimer.Start(0);
         }
@@ -288,13 +288,13 @@
     return;
 }
 
-otError JoinerRouter::SendJoinerEntrust(const Ip6::MessageInfo &aMessageInfo)
+Error JoinerRouter::SendJoinerEntrust(const Ip6::MessageInfo &aMessageInfo)
 {
-    otError        error = OT_ERROR_NONE;
+    Error          error = kErrorNone;
     Coap::Message *message;
 
     message = PrepareJoinerEntrustMessage();
-    VerifyOrExit(message != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(message != nullptr, error = kErrorNoBufs);
 
     IgnoreError(Get<Tmf::TmfAgent>().AbortTransaction(&JoinerRouter::HandleJoinerEntrustResponse, this));
 
@@ -312,14 +312,14 @@
 
 Coap::Message *JoinerRouter::PrepareJoinerEntrustMessage(void)
 {
-    otError        error;
+    Error          error;
     Coap::Message *message = nullptr;
     Dataset        dataset(Dataset::kActive);
 
     NetworkNameTlv networkName;
     const Tlv *    tlv;
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     message->InitAsConfirmablePost();
     SuccessOrExit(error = message->AppendUriPathOptions(UriPath::kJoinerEntrust));
@@ -390,7 +390,7 @@
 void JoinerRouter::HandleJoinerEntrustResponse(void *               aContext,
                                                otMessage *          aMessage,
                                                const otMessageInfo *aMessageInfo,
-                                               otError              aResult)
+                                               Error                aResult)
 {
     static_cast<JoinerRouter *>(aContext)->HandleJoinerEntrustResponse(
         static_cast<Coap::Message *>(aMessage), static_cast<const Ip6::MessageInfo *>(aMessageInfo), aResult);
@@ -398,13 +398,13 @@
 
 void JoinerRouter::HandleJoinerEntrustResponse(Coap::Message *         aMessage,
                                                const Ip6::MessageInfo *aMessageInfo,
-                                               otError                 aResult)
+                                               Error                   aResult)
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
 
     SendDelayedJoinerEntrust();
 
-    VerifyOrExit(aResult == OT_ERROR_NONE && aMessage != nullptr);
+    VerifyOrExit(aResult == kErrorNone && aMessage != nullptr);
 
     VerifyOrExit(aMessage->GetCode() == Coap::kCodeChanged);
 
diff --git a/src/core/meshcop/joiner_router.hpp b/src/core/meshcop/joiner_router.hpp
index 42b3fb1..47323f7 100644
--- a/src/core/meshcop/joiner_router.hpp
+++ b/src/core/meshcop/joiner_router.hpp
@@ -89,8 +89,8 @@
 
     struct JoinerEntrustMetadata
     {
-        otError AppendTo(Message &aMessage) const { return aMessage.Append(*this); }
-        void    ReadFrom(const Message &aMessage);
+        Error AppendTo(Message &aMessage) const { return aMessage.Append(*this); }
+        void  ReadFrom(const Message &aMessage);
 
         Ip6::MessageInfo mMessageInfo; // Message info of the message to send.
         TimeMilli        mSendTime;    // Time when the message shall be sent.
@@ -108,8 +108,8 @@
     static void HandleJoinerEntrustResponse(void *               aContext,
                                             otMessage *          aMessage,
                                             const otMessageInfo *aMessageInfo,
-                                            otError              aResult);
-    void HandleJoinerEntrustResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, otError aResult);
+                                            Error                aResult);
+    void HandleJoinerEntrustResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, Error aResult);
 
     static void HandleTimer(Timer &aTimer);
     void        HandleTimer(void);
@@ -117,7 +117,7 @@
     void           Start(void);
     void           DelaySendingJoinerEntrust(const Ip6::MessageInfo &aMessageInfo, const Kek &aKek);
     void           SendDelayedJoinerEntrust(void);
-    otError        SendJoinerEntrust(const Ip6::MessageInfo &aMessageInfo);
+    Error          SendJoinerEntrust(const Ip6::MessageInfo &aMessageInfo);
     Coap::Message *PrepareJoinerEntrustMessage(void);
 
     Ip6::Udp::Socket mSocket;
diff --git a/src/core/meshcop/meshcop.cpp b/src/core/meshcop/meshcop.cpp
index 24e4c1f..4866e96 100644
--- a/src/core/meshcop/meshcop.cpp
+++ b/src/core/meshcop/meshcop.cpp
@@ -38,7 +38,7 @@
 #include "common/locator-getters.hpp"
 #include "common/logging.hpp"
 #include "common/string.hpp"
-#include "crypto/pbkdf2_cmac.h"
+#include "crypto/pbkdf2_cmac.hpp"
 #include "crypto/sha256.hpp"
 #include "mac/mac_types.hpp"
 #include "thread/thread_netif.hpp"
@@ -46,11 +46,11 @@
 namespace ot {
 namespace MeshCoP {
 
-otError JoinerPskd::SetFrom(const char *aPskdString)
+Error JoinerPskd::SetFrom(const char *aPskdString)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(IsPskdValid(aPskdString), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(IsPskdValid(aPskdString), error = kErrorInvalidArgs);
 
     Clear();
     memcpy(m8, aPskdString, StringLength(aPskdString, sizeof(m8)));
@@ -291,21 +291,21 @@
     Crypto::Sha256::Hash hash;
 
     sha256.Start();
-    sha256.Update(aEui64.m8, sizeof(aEui64));
+    sha256.Update(aEui64);
     sha256.Finish(hash);
 
     memcpy(&aJoinerId, hash.GetBytes(), sizeof(aJoinerId));
     aJoinerId.SetLocal(true);
 }
 
-otError GetBorderAgentRloc(ThreadNetif &aNetif, uint16_t &aRloc)
+Error GetBorderAgentRloc(ThreadNetif &aNetif, uint16_t &aRloc)
 {
-    otError                      error = OT_ERROR_NONE;
+    Error                        error = kErrorNone;
     const BorderAgentLocatorTlv *borderAgentLocator;
 
     borderAgentLocator = static_cast<const BorderAgentLocatorTlv *>(
         aNetif.Get<NetworkData::Leader>().GetCommissioningDataSubTlv(Tlv::kBorderAgentLocator));
-    VerifyOrExit(borderAgentLocator != nullptr, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(borderAgentLocator != nullptr, error = kErrorNotFound);
 
     aRloc = borderAgentLocator->GetBorderAgentLocator();
 
@@ -314,19 +314,19 @@
 }
 
 #if OPENTHREAD_FTD
-otError GeneratePskc(const char *              aPassPhrase,
-                     const Mac::NetworkName &  aNetworkName,
-                     const Mac::ExtendedPanId &aExtPanId,
-                     Pskc &                    aPskc)
+Error GeneratePskc(const char *              aPassPhrase,
+                   const Mac::NetworkName &  aNetworkName,
+                   const Mac::ExtendedPanId &aExtPanId,
+                   Pskc &                    aPskc)
 {
-    otError    error        = OT_ERROR_NONE;
+    Error      error        = kErrorNone;
     const char saltPrefix[] = "Thread";
-    uint8_t    salt[OT_PBKDF2_SALT_MAX_LEN];
+    uint8_t    salt[Crypto::Pbkdf2::kMaxSaltLength];
     uint16_t   saltLen = 0;
     uint16_t   passphraseLen;
     uint8_t    networkNameLen;
 
-    VerifyOrExit(IsValidUtf8String(aPassPhrase), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(IsValidUtf8String(aPassPhrase), error = kErrorInvalidArgs);
 
     passphraseLen  = static_cast<uint16_t>(StringLength(aPassPhrase, OT_COMMISSIONING_PASSPHRASE_MAX_SIZE + 1));
     networkNameLen = static_cast<uint8_t>(StringLength(aNetworkName.GetAsCString(), OT_NETWORK_NAME_MAX_SIZE + 1));
@@ -334,7 +334,7 @@
     VerifyOrExit((passphraseLen >= OT_COMMISSIONING_PASSPHRASE_MIN_SIZE) &&
                      (passphraseLen <= OT_COMMISSIONING_PASSPHRASE_MAX_SIZE) &&
                      (networkNameLen <= OT_NETWORK_NAME_MAX_SIZE),
-                 error = OT_ERROR_INVALID_ARGS);
+                 error = kErrorInvalidArgs);
 
     memset(salt, 0, sizeof(salt));
     memcpy(salt, saltPrefix, sizeof(saltPrefix) - 1);
@@ -346,8 +346,8 @@
     memcpy(salt + saltLen, aNetworkName.GetAsCString(), networkNameLen);
     saltLen += networkNameLen;
 
-    otPbkdf2Cmac(reinterpret_cast<const uint8_t *>(aPassPhrase), passphraseLen, reinterpret_cast<const uint8_t *>(salt),
-                 saltLen, 16384, OT_PSKC_MAX_SIZE, aPskc.m8);
+    Crypto::Pbkdf2::GenerateKey(reinterpret_cast<const uint8_t *>(aPassPhrase), passphraseLen, salt, saltLen, 16384,
+                                OT_PSKC_MAX_SIZE, aPskc.m8);
 
 exit:
     return error;
@@ -355,11 +355,11 @@
 #endif // OPENTHREAD_FTD
 
 #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_WARN) && (OPENTHREAD_CONFIG_LOG_MESHCOP == 1)
-void LogError(const char *aActionText, otError aError)
+void LogError(const char *aActionText, Error aError)
 {
-    if (aError != OT_ERROR_NONE)
+    if (aError != kErrorNone)
     {
-        otLogWarnMeshCoP("Failed to %s: %s", aActionText, otThreadErrorToString(aError));
+        otLogWarnMeshCoP("Failed to %s: %s", aActionText, ErrorToString(aError));
     }
 }
 #endif
diff --git a/src/core/meshcop/meshcop.hpp b/src/core/meshcop/meshcop.hpp
index 2f1c1a0..f5e8d3a 100644
--- a/src/core/meshcop/meshcop.hpp
+++ b/src/core/meshcop/meshcop.hpp
@@ -45,6 +45,7 @@
 
 #include "coap/coap.hpp"
 #include "common/clearable.hpp"
+#include "common/equatable.hpp"
 #include "common/message.hpp"
 #include "common/string.hpp"
 #include "mac/mac_types.hpp"
@@ -65,7 +66,7 @@
  * This type represents a Joiner PSKd.
  *
  */
-class JoinerPskd : public otJoinerPskd, public Clearable<JoinerPskd>
+class JoinerPskd : public otJoinerPskd, public Clearable<JoinerPskd>, public Unequatable<JoinerPskd>
 {
 public:
     enum
@@ -91,11 +92,11 @@
      *
      * @param[in] aPskdString   A pointer to the PSKd C string array.
      *
-     * @retval OT_ERROR_NONE           The PSKd was updated successfully.
-     * @retval OT_ERROR_INVALID_ARGS   The given PSKd C string is not valid.
+     * @retval kErrorNone          The PSKd was updated successfully.
+     * @retval kErrorInvalidArgs   The given PSKd C string is not valid.
      *
      */
-    otError SetFrom(const char *aPskdString);
+    Error SetFrom(const char *aPskdString);
 
     /**
      * This method gets the PSKd as a null terminated C string.
@@ -129,17 +130,6 @@
     bool operator==(const JoinerPskd &aOther) const;
 
     /**
-     * This method overloads operator `!=` to evaluate whether or not two PSKds are unequal.
-     *
-     * @param[in]  aOther  The other PSKd to compare with.
-     *
-     * @retval TRUE   If the two are not equal.
-     * @retval FALSE  If the two are equal.
-     *
-     */
-    bool operator!=(const JoinerPskd &aOther) const { return !(*this == aOther); }
-
-    /**
      * This static method indicates whether a given PSKd string if well-formed and valid.
      *
      * @param[in] aPskdString  A pointer to a PSKd string array.
@@ -156,7 +146,7 @@
  * This type represents a Joiner Discerner.
  *
  */
-class JoinerDiscerner : public otJoinerDiscerner
+class JoinerDiscerner : public otJoinerDiscerner, public Unequatable<JoinerDiscerner>
 {
     friend class SteeringData;
 
@@ -242,17 +232,6 @@
     bool operator==(const JoinerDiscerner &aOther) const;
 
     /**
-     * This method overloads operator `!=` to evaluate whether or not two Joiner Discerner instances are equal.
-     *
-     * @param[in]  aOther  The other Joiner Discerner to compare with.
-     *
-     * @retval TRUE   If the two are not equal.
-     * @retval FALSE  If the two are equal.
-     *
-     */
-    bool operator!=(const JoinerDiscerner &aOther) const { return !(*this == aOther); }
-
-    /**
      * This method converts the Joiner Discerner to a string.
      *
      * @returns An `InfoString` representation of Joiner Discerner.
@@ -463,14 +442,14 @@
  * @param[in]  aExtPanId     The extended PAN ID for PSKc computation.
  * @param[out] aPskc         A reference to a PSKc where the generated PSKc will be placed.
  *
- * @retval OT_ERROR_NONE          Successfully generate PSKc.
- * @retval OT_ERROR_INVALID_ARGS  If the length of passphrase is out of range.
+ * @retval kErrorNone          Successfully generate PSKc.
+ * @retval kErrorInvalidArgs   If the length of passphrase is out of range.
  *
  */
-otError GeneratePskc(const char *              aPassPhrase,
-                     const Mac::NetworkName &  aNetworkName,
-                     const Mac::ExtendedPanId &aExtPanId,
-                     Pskc &                    aPskc);
+Error GeneratePskc(const char *              aPassPhrase,
+                   const Mac::NetworkName &  aNetworkName,
+                   const Mac::ExtendedPanId &aExtPanId,
+                   Pskc &                    aPskc);
 
 /**
  * This function computes the Joiner ID from a factory-assigned IEEE EUI-64.
@@ -487,26 +466,26 @@
  * @param[in]   aNetif  A reference to the thread interface.
  * @param[out]  aRloc   Border agent RLOC.
  *
- * @retval OT_ERROR_NONE        Successfully got the Border Agent Rloc.
- * @retval OT_ERROR_NOT_FOUND   Border agent is not available.
+ * @retval kErrorNone       Successfully got the Border Agent Rloc.
+ * @retval kErrorNotFound   Border agent is not available.
  *
  */
-otError GetBorderAgentRloc(ThreadNetif &aNetIf, uint16_t &aRloc);
+Error GetBorderAgentRloc(ThreadNetif &aNetIf, uint16_t &aRloc);
 
 #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_WARN) && (OPENTHREAD_CONFIG_LOG_MESHCOP == 1)
 /**
  * This function emits a log message indicating an error during a MeshCoP action.
  *
- * Note that log message is emitted only if there is an error, i.e. @p aError is not `OT_ERROR_NONE`. The log
+ * Note that log message is emitted only if there is an error, i.e. @p aError is not `kErrorNone`. The log
  * message will have the format "Failed to {aActionText} : {ErrorString}".
  *
  * @param[in] aActionText   A string representing the failed action.
  * @param[in] aError        The error in sending the message.
  *
  */
-void LogError(const char *aActionText, otError aError);
+void LogError(const char *aActionText, Error aError);
 #else
-inline void LogError(const char *, otError)
+inline void LogError(const char *, Error)
 {
 }
 #endif
diff --git a/src/core/meshcop/meshcop_leader.cpp b/src/core/meshcop/meshcop_leader.cpp
index 9822afd..8ef8731 100644
--- a/src/core/meshcop/meshcop_leader.cpp
+++ b/src/core/meshcop/meshcop_leader.cpp
@@ -56,7 +56,7 @@
     : InstanceLocator(aInstance)
     , mPetition(UriPath::kLeaderPetition, Leader::HandlePetition, this)
     , mKeepAlive(UriPath::kLeaderKeepAlive, Leader::HandleKeepAlive, this)
-    , mTimer(aInstance, HandleTimer, this)
+    , mTimer(aInstance, HandleTimer)
     , mDelayTimerMinimal(DelayTimerTlv::kDelayTimerMinimal)
     , mSessionId(Random::NonCrypto::GetUint16())
 {
@@ -123,10 +123,10 @@
                                   const Ip6::MessageInfo &aMessageInfo,
                                   StateTlv::State         aState)
 {
-    otError        error = OT_ERROR_NONE;
+    Error          error = kErrorNone;
     Coap::Message *message;
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->SetDefaultResponseHeader(aRequest));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -207,10 +207,10 @@
                                    const Ip6::MessageInfo &aMessageInfo,
                                    StateTlv::State         aState)
 {
-    otError        error = OT_ERROR_NONE;
+    Error          error = kErrorNone;
     Coap::Message *message;
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->SetDefaultResponseHeader(aRequest));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -228,11 +228,11 @@
 
 void Leader::SendDatasetChanged(const Ip6::Address &aAddress)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     Ip6::MessageInfo messageInfo;
     Coap::Message *  message;
 
-    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsConfirmablePost(UriPath::kDatasetChanged));
 
@@ -248,11 +248,11 @@
     LogError("send dataset changed", error);
 }
 
-otError Leader::SetDelayTimerMinimal(uint32_t aDelayTimerMinimal)
+Error Leader::SetDelayTimerMinimal(uint32_t aDelayTimerMinimal)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
     VerifyOrExit((aDelayTimerMinimal != 0 && aDelayTimerMinimal < DelayTimerTlv::kDelayTimerDefault),
-                 error = OT_ERROR_INVALID_ARGS);
+                 error = kErrorInvalidArgs);
     mDelayTimerMinimal = aDelayTimerMinimal;
 
 exit:
@@ -266,7 +266,7 @@
 
 void Leader::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<Leader>().HandleTimer();
+    aTimer.Get<Leader>().HandleTimer();
 }
 
 void Leader::HandleTimer(void)
diff --git a/src/core/meshcop/meshcop_leader.hpp b/src/core/meshcop/meshcop_leader.hpp
index d074039..47e2393 100644
--- a/src/core/meshcop/meshcop_leader.hpp
+++ b/src/core/meshcop/meshcop_leader.hpp
@@ -86,11 +86,11 @@
      *
      * @param[in]  aDelayTimerMinimal The value of minimal delay timer (in ms).
      *
-     * @retval  OT_ERROR_NONE         Successfully set the minimal delay timer.
-     * @retval  OT_ERROR_INVALID_ARGS If @p aDelayTimerMinimal is not valid.
+     * @retval  kErrorNone         Successfully set the minimal delay timer.
+     * @retval  kErrorInvalidArgs  If @p aDelayTimerMinimal is not valid.
      *
      */
-    otError SetDelayTimerMinimal(uint32_t aDelayTimerMinimal);
+    Error SetDelayTimerMinimal(uint32_t aDelayTimerMinimal);
 
     /**
      * This method gets minimal delay timer.
diff --git a/src/core/meshcop/meshcop_tlvs.hpp b/src/core/meshcop/meshcop_tlvs.hpp
index f85c351..96d6215 100644
--- a/src/core/meshcop/meshcop_tlvs.hpp
+++ b/src/core/meshcop/meshcop_tlvs.hpp
@@ -156,11 +156,11 @@
      * @param[in]   aMaxLength  Maximum number of bytes to read.
      * @param[out]  aTlv        A reference to the TLV that will be copied to.
      *
-     * @retval OT_ERROR_NONE       Successfully copied the TLV.
-     * @retval OT_ERROR_NOT_FOUND  Could not find the TLV with Type @p aType.
+     * @retval kErrorNone      Successfully copied the TLV.
+     * @retval kErrorNotFound  Could not find the TLV with Type @p aType.
      *
      */
-    static otError FindTlv(const Message &aMessage, Type aType, uint16_t aMaxLength, Tlv &aTlv)
+    static Error FindTlv(const Message &aMessage, Type aType, uint16_t aMaxLength, Tlv &aTlv)
     {
         return ot::Tlv::FindTlv(aMessage, static_cast<uint8_t>(aType), aMaxLength, aTlv);
     }
@@ -175,12 +175,12 @@
      * @param[in]   aMessage    A reference to the message.
      * @param[out]  aTlv        A reference to the TLV that will be copied to.
      *
-     * @retval OT_ERROR_NONE       Successfully copied the TLV.
-     * @retval OT_ERROR_NOT_FOUND  Could not find the TLV with Type @p aType.
+     * @retval kErrorNone      Successfully copied the TLV.
+     * @retval kErrorNotFound  Could not find the TLV with Type @p aType.
      *
      */
 
-    template <typename TlvType> static otError FindTlv(const Message &aMessage, TlvType &aTlv)
+    template <typename TlvType> static Error FindTlv(const Message &aMessage, TlvType &aTlv)
     {
         return ot::Tlv::FindTlv(aMessage, aTlv);
     }
diff --git a/src/core/meshcop/panid_query_client.cpp b/src/core/meshcop/panid_query_client.cpp
index 4598d0f..47b49b1 100644
--- a/src/core/meshcop/panid_query_client.cpp
+++ b/src/core/meshcop/panid_query_client.cpp
@@ -57,19 +57,19 @@
     Get<Tmf::TmfAgent>().AddResource(mPanIdQuery);
 }
 
-otError PanIdQueryClient::SendQuery(uint16_t                            aPanId,
-                                    uint32_t                            aChannelMask,
-                                    const Ip6::Address &                aAddress,
-                                    otCommissionerPanIdConflictCallback aCallback,
-                                    void *                              aContext)
+Error PanIdQueryClient::SendQuery(uint16_t                            aPanId,
+                                  uint32_t                            aChannelMask,
+                                  const Ip6::Address &                aAddress,
+                                  otCommissionerPanIdConflictCallback aCallback,
+                                  void *                              aContext)
 {
-    otError                 error = OT_ERROR_NONE;
+    Error                   error = kErrorNone;
     MeshCoP::ChannelMaskTlv channelMask;
     Ip6::MessageInfo        messageInfo;
     Coap::Message *         message = nullptr;
 
-    VerifyOrExit(Get<MeshCoP::Commissioner>().IsActive(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit((message = MeshCoP::NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(Get<MeshCoP::Commissioner>().IsActive(), error = kErrorInvalidState);
+    VerifyOrExit((message = MeshCoP::NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsPost(aAddress, UriPath::kPanIdQuery));
     SuccessOrExit(error = message->SetPayloadMarker());
diff --git a/src/core/meshcop/panid_query_client.hpp b/src/core/meshcop/panid_query_client.hpp
index 7fe58f2..4ad1b43 100644
--- a/src/core/meshcop/panid_query_client.hpp
+++ b/src/core/meshcop/panid_query_client.hpp
@@ -67,15 +67,15 @@
      * @param[in]  aCallback      A pointer to a function called on receiving an Energy Report message.
      * @param[in]  aContext       A pointer to application-specific context.
      *
-     * @retval OT_ERROR_NONE     Successfully enqueued the PAN ID Query message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers to generate a PAN ID Query message.
+     * @retval kErrorNone    Successfully enqueued the PAN ID Query message.
+     * @retval kErrorNoBufs  Insufficient buffers to generate a PAN ID Query message.
      *
      */
-    otError SendQuery(uint16_t                            aPanId,
-                      uint32_t                            aChannelMask,
-                      const Ip6::Address &                aAddress,
-                      otCommissionerPanIdConflictCallback aCallback,
-                      void *                              aContext);
+    Error SendQuery(uint16_t                            aPanId,
+                    uint32_t                            aChannelMask,
+                    const Ip6::Address &                aAddress,
+                    otCommissionerPanIdConflictCallback aCallback,
+                    void *                              aContext);
 
 private:
     static void HandleConflict(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
diff --git a/src/core/meshcop/timestamp.cpp b/src/core/meshcop/timestamp.cpp
index 47eef75..9a523d1 100644
--- a/src/core/meshcop/timestamp.cpp
+++ b/src/core/meshcop/timestamp.cpp
@@ -68,5 +68,19 @@
     return rval;
 }
 
+void Timestamp::AdvanceRandomTicks(void)
+{
+    uint16_t ticks = GetTicks();
+
+    ticks += Random::NonCrypto::GetUint32InRange(1, kMaxRandomTicks);
+
+    if (ticks & (kTicksMask >> kTicksOffset))
+    {
+        SetSeconds(GetSeconds() + 1);
+    }
+
+    SetTicks(ticks);
+}
+
 } // namespace MeshCoP
 } // namespace ot
diff --git a/src/core/meshcop/timestamp.hpp b/src/core/meshcop/timestamp.hpp
index 4149cd6..b8888b9 100644
--- a/src/core/meshcop/timestamp.hpp
+++ b/src/core/meshcop/timestamp.hpp
@@ -42,6 +42,7 @@
 #include <openthread/platform/toolchain.h>
 
 #include "common/encoding.hpp"
+#include "common/random.hpp"
 
 namespace ot {
 namespace MeshCoP {
@@ -137,11 +138,18 @@
                             ((aAuthoritative << kAuthoritativeOffset) & kAuthoritativeMask));
     }
 
+    /**
+     * This method increments the timestamp by a random number of ticks [0, 32767].
+     *
+     */
+    void AdvanceRandomTicks(void);
+
 private:
     enum
     {
         kTicksOffset         = 1,
         kTicksMask           = 0x7fff << kTicksOffset,
+        kMaxRandomTicks      = 0x7fff,
         kAuthoritativeOffset = 0,
         kAuthoritativeMask   = 1 << kAuthoritativeOffset,
     };
diff --git a/src/core/net/checksum.cpp b/src/core/net/checksum.cpp
index 8dfa694..db600e6 100644
--- a/src/core/net/checksum.cpp
+++ b/src/core/net/checksum.cpp
@@ -114,13 +114,13 @@
     }
 }
 
-otError Checksum::VerifyMessageChecksum(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, uint8_t aIpProto)
+Error Checksum::VerifyMessageChecksum(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, uint8_t aIpProto)
 {
     Checksum checksum;
 
     checksum.Calculate(aMessageInfo.GetPeerAddr(), aMessageInfo.GetSockAddr(), aIpProto, aMessage);
 
-    return (checksum.GetValue() == kValidRxChecksum) ? OT_ERROR_NONE : OT_ERROR_DROP;
+    return (checksum.GetValue() == kValidRxChecksum) ? kErrorNone : kErrorDrop;
 }
 
 void Checksum::UpdateMessageChecksum(Message &           aMessage,
diff --git a/src/core/net/checksum.hpp b/src/core/net/checksum.hpp
index f61cbaf..fa8e1ae 100644
--- a/src/core/net/checksum.hpp
+++ b/src/core/net/checksum.hpp
@@ -62,13 +62,11 @@
      * @param[in] aMessageInfo   The message info associated with @p aMessage.
      * @param[in] aIpProto       The Internet Protocol value.
      *
-     * @retval OT_ERROR_NONE    The checksum is valid if UDP/ICMP6 protocol, or not a UDP/ICMP6 protocol.
-     * @retval OT_ERROR_DROP    The check is not valid and message should be dropped.
+     * @retval kErrorNone    The checksum is valid if UDP/ICMP6 protocol, or not a UDP/ICMP6 protocol.
+     * @retval kErrorDrop    The check is not valid and message should be dropped.
      *
      */
-    static otError VerifyMessageChecksum(const Message &         aMessage,
-                                         const Ip6::MessageInfo &aMessageInfo,
-                                         uint8_t                 aIpProto);
+    static Error VerifyMessageChecksum(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, uint8_t aIpProto);
 
     /**
      * This static method calculates and then updates the checksum in a given message (if UDP/ICMP6).
diff --git a/src/core/net/dhcp6.hpp b/src/core/net/dhcp6.hpp
index 4988e58..ac4bf82 100644
--- a/src/core/net/dhcp6.hpp
+++ b/src/core/net/dhcp6.hpp
@@ -112,11 +112,11 @@
     /**
      * This method generates a cryptographically secure random sequence to populate the transaction identifier.
      *
-     * @retval OT_ERROR_NONE     Successfully generated a random transaction identifier.
-     * @retval OT_ERROR_FAILED   Failed to generate random sequence.
+     * @retval kErrorNone     Successfully generated a random transaction identifier.
+     * @retval kErrorFailed   Failed to generate random sequence.
      *
      */
-    otError GenerateRandom(void) { return Random::Crypto::FillBuffer(m8, kSize); }
+    Error GenerateRandom(void) { return Random::Crypto::FillBuffer(m8, kSize); }
 
 private:
     uint8_t m8[kSize];
diff --git a/src/core/net/dhcp6_client.cpp b/src/core/net/dhcp6_client.cpp
index 6fe9c83..4a61703 100644
--- a/src/core/net/dhcp6_client.cpp
+++ b/src/core/net/dhcp6_client.cpp
@@ -50,7 +50,7 @@
 Client::Client(Instance &aInstance)
     : InstanceLocator(aInstance)
     , mSocket(aInstance)
-    , mTrickleTimer(aInstance, Client::HandleTrickleTimer, nullptr, this)
+    , mTrickleTimer(aInstance, Client::HandleTrickleTimer)
     , mStartTime(0)
     , mIdentityAssociationCurrent(nullptr)
 {
@@ -80,7 +80,7 @@
         found    = false;
         iterator = NetworkData::kIteratorInit;
 
-        while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, config) == OT_ERROR_NONE)
+        while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, config) == kErrorNone)
         {
             if (!config.mDhcp)
             {
@@ -104,7 +104,7 @@
     // add IdentityAssociation for new configured prefix
     iterator = NetworkData::kIteratorInit;
 
-    while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, config) == OT_ERROR_NONE)
+    while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, config) == kErrorNone)
     {
         IdentityAssociation *idAssociation = nullptr;
 
@@ -178,6 +178,7 @@
 
 void Client::Stop(void)
 {
+    mTrickleTimer.Stop();
     IgnoreError(mSocket.Close());
 }
 
@@ -202,8 +203,8 @@
 
         mIdentityAssociationCurrent = &idAssociation;
 
-        mTrickleTimer.Start(Time::SecToMsec(kTrickleTimerImin), Time::SecToMsec(kTrickleTimerImax),
-                            TrickleTimer::kModeNormal);
+        mTrickleTimer.Start(TrickleTimer::kModeTrickle, Time::SecToMsec(kTrickleTimerImin),
+                            Time::SecToMsec(kTrickleTimerImax));
 
         mTrickleTimer.IndicateInconsistent();
 
@@ -214,16 +215,16 @@
     return rval;
 }
 
-bool Client::HandleTrickleTimer(TrickleTimer &aTrickleTimer)
+void Client::HandleTrickleTimer(TrickleTimer &aTrickleTimer)
 {
-    return aTrickleTimer.GetOwner<Client>().HandleTrickleTimer();
+    aTrickleTimer.Get<Client>().HandleTrickleTimer();
 }
 
-bool Client::HandleTrickleTimer(void)
+void Client::HandleTrickleTimer(void)
 {
-    bool rval = true;
+    OT_ASSERT(mSocket.IsBound());
 
-    VerifyOrExit(mIdentityAssociationCurrent != nullptr, rval = false);
+    VerifyOrExit(mIdentityAssociationCurrent != nullptr, mTrickleTimer.Stop());
 
     switch (mIdentityAssociationCurrent->mStatus)
     {
@@ -231,7 +232,7 @@
         mStartTime                           = TimerMilli::GetNow();
         mIdentityAssociationCurrent->mStatus = kIaStatusSoliciting;
 
-        // fall through
+        OT_FALL_THROUGH;
 
     case kIaStatusSoliciting:
         Solicit(mIdentityAssociationCurrent->mPrefixAgentRloc);
@@ -242,9 +243,8 @@
 
         if (!ProcessNextIdentityAssociation())
         {
-            mTrickleTimer.Stop();
             Stop();
-            rval = false;
+            mTrickleTimer.Stop();
         }
 
         break;
@@ -254,16 +254,16 @@
     }
 
 exit:
-    return rval;
+    return;
 }
 
 void Client::Solicit(uint16_t aRloc16)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     Message *        message;
     Ip6::MessageInfo messageInfo;
 
-    VerifyOrExit((message = mSocket.NewMessage(0)) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = mSocket.NewMessage(0)) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = AppendHeader(*message));
     SuccessOrExit(error = AppendElapsedTime(*message));
@@ -285,14 +285,14 @@
     otLogInfoIp6("solicit");
 
 exit:
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         FreeMessage(message);
-        otLogWarnIp6("Failed to send DHCPv6 Solicit: %s", otThreadErrorToString(error));
+        otLogWarnIp6("Failed to send DHCPv6 Solicit: %s", ErrorToString(error));
     }
 }
 
-otError Client::AppendHeader(Message &aMessage)
+Error Client::AppendHeader(Message &aMessage)
 {
     Header header;
 
@@ -302,7 +302,7 @@
     return aMessage.Append(header);
 }
 
-otError Client::AppendElapsedTime(Message &aMessage)
+Error Client::AppendElapsedTime(Message &aMessage)
 {
     ElapsedTime option;
 
@@ -311,7 +311,7 @@
     return aMessage.Append(option);
 }
 
-otError Client::AppendClientIdentifier(Message &aMessage)
+Error Client::AppendClientIdentifier(Message &aMessage)
 {
     ClientIdentifier option;
     Mac::ExtAddress  eui64;
@@ -326,14 +326,14 @@
     return aMessage.Append(option);
 }
 
-otError Client::AppendIaNa(Message &aMessage, uint16_t aRloc16)
+Error Client::AppendIaNa(Message &aMessage, uint16_t aRloc16)
 {
-    otError  error  = OT_ERROR_NONE;
+    Error    error  = kErrorNone;
     uint8_t  count  = 0;
     uint16_t length = 0;
     IaNa     option;
 
-    VerifyOrExit(mIdentityAssociationCurrent != nullptr, error = OT_ERROR_DROP);
+    VerifyOrExit(mIdentityAssociationCurrent != nullptr, error = kErrorDrop);
 
     for (IdentityAssociation &idAssociation : mIdentityAssociations)
     {
@@ -362,12 +362,12 @@
     return error;
 }
 
-otError Client::AppendIaAddress(Message &aMessage, uint16_t aRloc16)
+Error Client::AppendIaAddress(Message &aMessage, uint16_t aRloc16)
 {
-    otError   error = OT_ERROR_NONE;
+    Error     error = kErrorNone;
     IaAddress option;
 
-    VerifyOrExit(mIdentityAssociationCurrent, error = OT_ERROR_DROP);
+    VerifyOrExit(mIdentityAssociationCurrent, error = kErrorDrop);
 
     option.Init();
 
@@ -387,7 +387,7 @@
     return error;
 }
 
-otError Client::AppendRapidCommit(Message &aMessage)
+Error Client::AppendRapidCommit(Message &aMessage)
 {
     RapidCommit option;
 
@@ -475,9 +475,9 @@
     return rval;
 }
 
-otError Client::ProcessServerIdentifier(Message &aMessage, uint16_t aOffset)
+Error Client::ProcessServerIdentifier(Message &aMessage, uint16_t aOffset)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     ServerIdentifier option;
 
     SuccessOrExit(aMessage.Read(aOffset, option));
@@ -486,14 +486,14 @@
                      ((option.GetLength() == (sizeof(option) - sizeof(Option))) &&
                       (option.GetDuidType() == kDuidLinkLayerAddress) &&
                       (option.GetDuidHardwareType() == kHardwareTypeEui64)),
-                 error = OT_ERROR_PARSE);
+                 error = kErrorParse);
 exit:
     return error;
 }
 
-otError Client::ProcessClientIdentifier(Message &aMessage, uint16_t aOffset)
+Error Client::ProcessClientIdentifier(Message &aMessage, uint16_t aOffset)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     ClientIdentifier option;
     Mac::ExtAddress  eui64;
 
@@ -503,14 +503,14 @@
     VerifyOrExit(
         (option.GetLength() == (sizeof(option) - sizeof(Option))) && (option.GetDuidType() == kDuidLinkLayerAddress) &&
             (option.GetDuidHardwareType() == kHardwareTypeEui64) && (option.GetDuidLinkLayerAddress() == eui64),
-        error = OT_ERROR_PARSE);
+        error = kErrorParse);
 exit:
     return error;
 }
 
-otError Client::ProcessIaNa(Message &aMessage, uint16_t aOffset)
+Error Client::ProcessIaNa(Message &aMessage, uint16_t aOffset)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     IaNa     option;
     uint16_t optionOffset;
     uint16_t length;
@@ -520,7 +520,7 @@
     aOffset += sizeof(option);
     length = option.GetLength() - (sizeof(option) - sizeof(Option));
 
-    VerifyOrExit(length <= aMessage.GetLength() - aOffset, error = OT_ERROR_PARSE);
+    VerifyOrExit(length <= aMessage.GetLength() - aOffset, error = kErrorParse);
 
     if ((optionOffset = FindOption(aMessage, aOffset, length, kOptionStatusCode)) > 0)
     {
@@ -544,26 +544,26 @@
     return error;
 }
 
-otError Client::ProcessStatusCode(Message &aMessage, uint16_t aOffset)
+Error Client::ProcessStatusCode(Message &aMessage, uint16_t aOffset)
 {
-    otError    error = OT_ERROR_NONE;
+    Error      error = kErrorNone;
     StatusCode option;
 
     SuccessOrExit(error = aMessage.Read(aOffset, option));
     VerifyOrExit((option.GetLength() >= sizeof(option) - sizeof(Option)) && (option.GetStatusCode() == kStatusSuccess),
-                 error = OT_ERROR_PARSE);
+                 error = kErrorParse);
 
 exit:
     return error;
 }
 
-otError Client::ProcessIaAddress(Message &aMessage, uint16_t aOffset)
+Error Client::ProcessIaAddress(Message &aMessage, uint16_t aOffset)
 {
-    otError   error;
+    Error     error;
     IaAddress option;
 
     SuccessOrExit(error = aMessage.Read(aOffset, option));
-    VerifyOrExit(option.GetLength() == sizeof(option) - sizeof(Option), error = OT_ERROR_PARSE);
+    VerifyOrExit(option.GetLength() == sizeof(option) - sizeof(Option), error = kErrorParse);
 
     for (IdentityAssociation &idAssociation : mIdentityAssociations)
     {
@@ -583,11 +583,11 @@
             idAssociation.mNetifAddress.mValid         = option.GetValidLifetime() != 0;
             idAssociation.mStatus                      = kIaStatusSolicitReplied;
             Get<ThreadNetif>().AddUnicastAddress(idAssociation.mNetifAddress);
-            ExitNow(error = OT_ERROR_NONE);
+            ExitNow(error = kErrorNone);
         }
     }
 
-    error = OT_ERROR_NOT_FOUND;
+    error = kErrorNotFound;
 
 exit:
     return error;
diff --git a/src/core/net/dhcp6_client.hpp b/src/core/net/dhcp6_client.hpp
index 0f40b8e..cb29880 100644
--- a/src/core/net/dhcp6_client.hpp
+++ b/src/core/net/dhcp6_client.hpp
@@ -51,6 +51,8 @@
 
 namespace Dhcp6 {
 
+#if OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE
+
 /**
  * @addtogroup core-dhcp6
  *
@@ -120,26 +122,26 @@
 
     bool ProcessNextIdentityAssociation(void);
 
-    otError AppendHeader(Message &aMessage);
-    otError AppendClientIdentifier(Message &aMessage);
-    otError AppendIaNa(Message &aMessage, uint16_t aRloc16);
-    otError AppendIaAddress(Message &aMessage, uint16_t aRloc16);
-    otError AppendElapsedTime(Message &aMessage);
-    otError AppendRapidCommit(Message &aMessage);
+    Error AppendHeader(Message &aMessage);
+    Error AppendClientIdentifier(Message &aMessage);
+    Error AppendIaNa(Message &aMessage, uint16_t aRloc16);
+    Error AppendIaAddress(Message &aMessage, uint16_t aRloc16);
+    Error AppendElapsedTime(Message &aMessage);
+    Error AppendRapidCommit(Message &aMessage);
 
     static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
     void        HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
 
     void     ProcessReply(Message &aMessage);
     uint16_t FindOption(Message &aMessage, uint16_t aOffset, uint16_t aLength, Code aCode);
-    otError  ProcessServerIdentifier(Message &aMessage, uint16_t aOffset);
-    otError  ProcessClientIdentifier(Message &aMessage, uint16_t aOffset);
-    otError  ProcessIaNa(Message &aMessage, uint16_t aOffset);
-    otError  ProcessStatusCode(Message &aMessage, uint16_t aOffset);
-    otError  ProcessIaAddress(Message &aMessage, uint16_t aOffset);
+    Error    ProcessServerIdentifier(Message &aMessage, uint16_t aOffset);
+    Error    ProcessClientIdentifier(Message &aMessage, uint16_t aOffset);
+    Error    ProcessIaNa(Message &aMessage, uint16_t aOffset);
+    Error    ProcessStatusCode(Message &aMessage, uint16_t aOffset);
+    Error    ProcessIaAddress(Message &aMessage, uint16_t aOffset);
 
-    static bool HandleTrickleTimer(TrickleTimer &aTrickleTimer);
-    bool        HandleTrickleTimer(void);
+    static void HandleTrickleTimer(TrickleTimer &aTrickleTimer);
+    void        HandleTrickleTimer(void);
 
     Ip6::Udp::Socket mSocket;
 
@@ -152,6 +154,19 @@
     IdentityAssociation *mIdentityAssociationCurrent;
 };
 
+/**
+ * @}
+ *
+ */
+
+#else // OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE
+
+#if OPENTHREAD_ENABLE_DHCP6_MULTICAST_SOLICIT
+#error "OPENTHREAD_ENABLE_DHCP6_MULTICAST_SOLICIT requires OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE to be also set."
+#endif
+
+#endif // OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE
+
 } // namespace Dhcp6
 } // namespace ot
 
diff --git a/src/core/net/dhcp6_server.cpp b/src/core/net/dhcp6_server.cpp
index 1d24c97..f441ada 100644
--- a/src/core/net/dhcp6_server.cpp
+++ b/src/core/net/dhcp6_server.cpp
@@ -55,9 +55,9 @@
     memset(mPrefixAgents, 0, sizeof(mPrefixAgents));
 }
 
-otError Server::UpdateService(void)
+Error Server::UpdateService(void)
 {
-    otError                         error  = OT_ERROR_NONE;
+    Error                           error  = kErrorNone;
     uint16_t                        rloc16 = Get<Mle::MleRouter>().GetRloc16();
     NetworkData::Iterator           iterator;
     NetworkData::OnMeshPrefixConfig config;
@@ -75,7 +75,7 @@
 
         iterator = NetworkData::kIteratorInit;
 
-        while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, rloc16, config) == OT_ERROR_NONE)
+        while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, rloc16, config) == kErrorNone)
         {
             if (!config.mDhcp)
             {
@@ -84,7 +84,7 @@
 
             error = Get<NetworkData::Leader>().GetContext(prefixAgent.GetPrefixAsAddress(), lowpanContext);
 
-            if ((error == OT_ERROR_NONE) && (prefixAgent.GetContextId() == lowpanContext.mContextId))
+            if ((error == kErrorNone) && (prefixAgent.GetContextId() == lowpanContext.mContextId))
             {
                 // still in network data
                 found = true;
@@ -102,7 +102,7 @@
     // add dhcp agent aloc and prefix delegation
     iterator = NetworkData::kIteratorInit;
 
-    while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, rloc16, config) == OT_ERROR_NONE)
+    while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, rloc16, config) == kErrorNone)
     {
         if (!config.mDhcp)
         {
@@ -112,7 +112,7 @@
         error = Get<NetworkData::Leader>().GetContext(static_cast<const Ip6::Address &>(config.mPrefix.mPrefix),
                                                       lowpanContext);
 
-        if (error == OT_ERROR_NONE)
+        if (error == kErrorNone)
         {
             AddPrefixAgent(config.GetPrefix(), lowpanContext);
         }
@@ -143,7 +143,7 @@
 
 void Server::AddPrefixAgent(const Ip6::Prefix &aIp6Prefix, const Lowpan::Context &aContext)
 {
-    otError      error    = OT_ERROR_NONE;
+    Error        error    = kErrorNone;
     PrefixAgent *newEntry = nullptr;
 
     for (PrefixAgent &prefixAgent : mPrefixAgents)
@@ -159,7 +159,7 @@
         }
     }
 
-    VerifyOrExit(newEntry != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(newEntry != nullptr, error = kErrorNoBufs);
 
     newEntry->Set(aIp6Prefix, Get<Mle::MleRouter>().GetMeshLocalPrefix(), aContext.mContextId);
     Get<ThreadNetif>().AddUnicastAddress(newEntry->GetAloc());
@@ -167,9 +167,9 @@
 
 exit:
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogNoteIp6("Failed to add DHCPv6 prefix agent: %s", otThreadErrorToString(error));
+        otLogNoteIp6("Failed to add DHCPv6 prefix agent: %s", ErrorToString(error));
     }
 }
 
@@ -251,33 +251,33 @@
 exit:
     return rval;
 }
-otError Server::ProcessClientIdentifier(Message &aMessage, uint16_t aOffset, ClientIdentifier &aClientId)
+Error Server::ProcessClientIdentifier(Message &aMessage, uint16_t aOffset, ClientIdentifier &aClientId)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     SuccessOrExit(error = aMessage.Read(aOffset, aClientId));
     VerifyOrExit((aClientId.GetLength() == sizeof(aClientId) - sizeof(Option)) &&
                      (aClientId.GetDuidType() == kDuidLinkLayerAddress) &&
                      (aClientId.GetDuidHardwareType() == kHardwareTypeEui64),
-                 error = OT_ERROR_PARSE);
+                 error = kErrorParse);
 exit:
     return error;
 }
 
-otError Server::ProcessElapsedTime(Message &aMessage, uint16_t aOffset)
+Error Server::ProcessElapsedTime(Message &aMessage, uint16_t aOffset)
 {
-    otError     error = OT_ERROR_NONE;
+    Error       error = kErrorNone;
     ElapsedTime option;
 
     SuccessOrExit(error = aMessage.Read(aOffset, option));
-    VerifyOrExit(option.GetLength() == sizeof(option) - sizeof(Option), error = OT_ERROR_PARSE);
+    VerifyOrExit(option.GetLength() == sizeof(option) - sizeof(Option), error = kErrorParse);
 exit:
     return error;
 }
 
-otError Server::ProcessIaNa(Message &aMessage, uint16_t aOffset, IaNa &aIaNa)
+Error Server::ProcessIaNa(Message &aMessage, uint16_t aOffset, IaNa &aIaNa)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     uint16_t optionOffset;
     uint16_t length;
 
@@ -286,7 +286,7 @@
     aOffset += sizeof(aIaNa);
     length = aIaNa.GetLength() + sizeof(Option) - sizeof(IaNa);
 
-    VerifyOrExit(length <= aMessage.GetLength() - aOffset, error = OT_ERROR_PARSE);
+    VerifyOrExit(length <= aMessage.GetLength() - aOffset, error = kErrorParse);
 
     mPrefixAgentsMask = 0;
 
@@ -303,13 +303,13 @@
     return error;
 }
 
-otError Server::ProcessIaAddress(Message &aMessage, uint16_t aOffset)
+Error Server::ProcessIaAddress(Message &aMessage, uint16_t aOffset)
 {
-    otError   error = OT_ERROR_NONE;
+    Error     error = kErrorNone;
     IaAddress option;
 
     SuccessOrExit(error = aMessage.Read(aOffset, option));
-    VerifyOrExit(option.GetLength() == sizeof(option) - sizeof(Option), error = OT_ERROR_PARSE);
+    VerifyOrExit(option.GetLength() == sizeof(option) - sizeof(Option), error = kErrorParse);
 
     // mask matching prefix
     for (uint16_t i = 0; i < OT_ARRAY_LENGTH(mPrefixAgents); i++)
@@ -325,16 +325,16 @@
     return error;
 }
 
-otError Server::SendReply(const Ip6::Address & aDst,
-                          const TransactionId &aTransactionId,
-                          ClientIdentifier &   aClientId,
-                          IaNa &               aIaNa)
+Error Server::SendReply(const Ip6::Address & aDst,
+                        const TransactionId &aTransactionId,
+                        ClientIdentifier &   aClientId,
+                        IaNa &               aIaNa)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     Ip6::MessageInfo messageInfo;
     Message *        message;
 
-    VerifyOrExit((message = mSocket.NewMessage(0)) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = mSocket.NewMessage(0)) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = AppendHeader(*message, aTransactionId));
     SuccessOrExit(error = AppendServerIdentifier(*message));
     SuccessOrExit(error = AppendClientIdentifier(*message, aClientId));
@@ -352,7 +352,7 @@
     return error;
 }
 
-otError Server::AppendHeader(Message &aMessage, const TransactionId &aTransactionId)
+Error Server::AppendHeader(Message &aMessage, const TransactionId &aTransactionId)
 {
     Header header;
 
@@ -362,14 +362,14 @@
     return aMessage.Append(header);
 }
 
-otError Server::AppendClientIdentifier(Message &aMessage, ClientIdentifier &aClientId)
+Error Server::AppendClientIdentifier(Message &aMessage, ClientIdentifier &aClientId)
 {
     return aMessage.Append(aClientId);
 }
 
-otError Server::AppendServerIdentifier(Message &aMessage)
+Error Server::AppendServerIdentifier(Message &aMessage)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     ServerIdentifier option;
     Mac::ExtAddress  eui64;
 
@@ -385,9 +385,9 @@
     return error;
 }
 
-otError Server::AppendIaNa(Message &aMessage, IaNa &aIaNa)
+Error Server::AppendIaNa(Message &aMessage, IaNa &aIaNa)
 {
-    otError  error  = OT_ERROR_NONE;
+    Error    error  = kErrorNone;
     uint16_t length = 0;
 
     if (mPrefixAgentsMask)
@@ -416,7 +416,7 @@
     return error;
 }
 
-otError Server::AppendStatusCode(Message &aMessage, Status aStatusCode)
+Error Server::AppendStatusCode(Message &aMessage, Status aStatusCode)
 {
     StatusCode option;
 
@@ -425,9 +425,9 @@
     return aMessage.Append(option);
 }
 
-otError Server::AppendIaAddress(Message &aMessage, ClientIdentifier &aClientId)
+Error Server::AppendIaAddress(Message &aMessage, ClientIdentifier &aClientId)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     if (mPrefixAgentsMask)
     {
@@ -456,9 +456,9 @@
     return error;
 }
 
-otError Server::AddIaAddress(Message &aMessage, const Ip6::Address &aPrefix, ClientIdentifier &aClientId)
+Error Server::AddIaAddress(Message &aMessage, const Ip6::Address &aPrefix, ClientIdentifier &aClientId)
 {
-    otError   error = OT_ERROR_NONE;
+    Error     error = kErrorNone;
     IaAddress option;
 
     option.Init();
@@ -472,7 +472,7 @@
     return error;
 }
 
-otError Server::AppendRapidCommit(Message &aMessage)
+Error Server::AppendRapidCommit(Message &aMessage)
 {
     RapidCommit option;
 
diff --git a/src/core/net/dhcp6_server.hpp b/src/core/net/dhcp6_server.hpp
index 0890633..e8f36ea 100644
--- a/src/core/net/dhcp6_server.hpp
+++ b/src/core/net/dhcp6_server.hpp
@@ -45,9 +45,14 @@
 #include "thread/network_data_leader.hpp"
 
 namespace ot {
-
 namespace Dhcp6 {
 
+#if OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE
+
+#if OPENTHREAD_ENABLE_DHCP6_MULTICAST_SOLICIT
+#error "OPENTHREAD_ENABLE_DHCP6_MULTICAST_SOLICIT requires DHCPv6 server on Border Router side to be enabled."
+#endif
+
 /**
  * @addtogroup core-dhcp6
  *
@@ -73,7 +78,7 @@
      * This method updates DHCP Agents and DHCP Alocs.
      *
      */
-    otError UpdateService(void);
+    Error UpdateService(void);
 
     /**
      * This method applies the Mesh Local Prefix.
@@ -185,16 +190,16 @@
 
     void AddPrefixAgent(const Ip6::Prefix &aIp6Prefix, const Lowpan::Context &aContext);
 
-    otError AppendHeader(Message &aMessage, const TransactionId &aTransactionId);
-    otError AppendClientIdentifier(Message &aMessage, ClientIdentifier &aClientId);
-    otError AppendServerIdentifier(Message &aMessage);
-    otError AppendIaNa(Message &aMessage, IaNa &aIaNa);
-    otError AppendStatusCode(Message &aMessage, Status aStatusCode);
-    otError AppendIaAddress(Message &aMessage, ClientIdentifier &aClientId);
-    otError AppendRapidCommit(Message &aMessage);
-    otError AppendVendorSpecificInformation(Message &aMessage);
+    Error AppendHeader(Message &aMessage, const TransactionId &aTransactionId);
+    Error AppendClientIdentifier(Message &aMessage, ClientIdentifier &aClientId);
+    Error AppendServerIdentifier(Message &aMessage);
+    Error AppendIaNa(Message &aMessage, IaNa &aIaNa);
+    Error AppendStatusCode(Message &aMessage, Status aStatusCode);
+    Error AppendIaAddress(Message &aMessage, ClientIdentifier &aClientId);
+    Error AppendRapidCommit(Message &aMessage);
+    Error AppendVendorSpecificInformation(Message &aMessage);
 
-    otError AddIaAddress(Message &aMessage, const Ip6::Address &aPrefix, ClientIdentifier &aClientId);
+    Error AddIaAddress(Message &aMessage, const Ip6::Address &aPrefix, ClientIdentifier &aClientId);
 
     static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
     void        HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
@@ -202,15 +207,15 @@
     void ProcessSolicit(Message &aMessage, const Ip6::Address &aDst, const TransactionId &aTransactionId);
 
     uint16_t FindOption(Message &aMessage, uint16_t aOffset, uint16_t aLength, Code aCode);
-    otError  ProcessClientIdentifier(Message &aMessage, uint16_t aOffset, ClientIdentifier &aClientId);
-    otError  ProcessIaNa(Message &aMessage, uint16_t aOffset, IaNa &aIaNa);
-    otError  ProcessIaAddress(Message &aMessage, uint16_t aOffset);
-    otError  ProcessElapsedTime(Message &aMessage, uint16_t aOffset);
+    Error    ProcessClientIdentifier(Message &aMessage, uint16_t aOffset, ClientIdentifier &aClientId);
+    Error    ProcessIaNa(Message &aMessage, uint16_t aOffset, IaNa &aIaNa);
+    Error    ProcessIaAddress(Message &aMessage, uint16_t aOffset);
+    Error    ProcessElapsedTime(Message &aMessage, uint16_t aOffset);
 
-    otError SendReply(const Ip6::Address & aDst,
-                      const TransactionId &aTransactionId,
-                      ClientIdentifier &   aClientId,
-                      IaNa &               aIaNa);
+    Error SendReply(const Ip6::Address & aDst,
+                    const TransactionId &aTransactionId,
+                    ClientIdentifier &   aClientId,
+                    IaNa &               aIaNa);
 
     Ip6::Udp::Socket mSocket;
 
@@ -219,6 +224,13 @@
     uint8_t     mPrefixAgentsMask;
 };
 
+/**
+ * @}
+ *
+ */
+
+#endif // OPENTHREAD_CONFIG_DHCP6_SERVER_ENABLE
+
 } // namespace Dhcp6
 } // namespace ot
 
diff --git a/src/core/net/dns_client.cpp b/src/core/net/dns_client.cpp
index c59fc9b..769314e 100644
--- a/src/core/net/dns_client.cpp
+++ b/src/core/net/dns_client.cpp
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2017, The OpenThread Authors.
+ *  Copyright (c) 2017-2021, The OpenThread Authors.
  *  All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
@@ -43,20 +43,411 @@
  *   This file implements the DNS client.
  */
 
-using ot::Encoding::BigEndian::HostSwap16;
-
 namespace ot {
 namespace Dns {
 
-Client::Client(Instance &aInstance)
-    : mSocket(aInstance)
-    , mRetransmissionTimer(aInstance, Client::HandleRetransmissionTimer, this)
+//---------------------------------------------------------------------------------------------------------------------
+// Client::QueryConfig
+
+const char Client::QueryConfig::kDefaultServerAddressString[] = OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_IP6_ADDRESS;
+
+Client::QueryConfig::QueryConfig(InitMode aMode)
 {
+    OT_UNUSED_VARIABLE(aMode);
+
+    IgnoreError(GetServerSockAddr().GetAddress().FromString(kDefaultServerAddressString));
+    GetServerSockAddr().SetPort(kDefaultServerPort);
+    SetResponseTimeout(kDefaultResponseTimeout);
+    SetMaxTxAttempts(kDefaultMaxTxAttempts);
+    SetRecursionFlag(kDefaultRecursionDesired ? kFlagRecursionDesired : kFlagNoRecursion);
 }
 
-otError Client::Start(void)
+void Client::QueryConfig::SetFrom(const QueryConfig &aConfig, const QueryConfig &aDefaultConfig)
 {
-    otError error;
+    // This method sets the config from `aConfig` replacing any
+    // unspecified fields (value zero) with the fields from
+    // `aDefaultConfig`.
+
+    *this = aConfig;
+
+    if (GetServerSockAddr().GetAddress().IsUnspecified())
+    {
+        GetServerSockAddr().GetAddress() = aDefaultConfig.GetServerSockAddr().GetAddress();
+    }
+
+    if (GetServerSockAddr().GetPort() == 0)
+    {
+        GetServerSockAddr().SetPort(aDefaultConfig.GetServerSockAddr().GetPort());
+    }
+
+    if (GetResponseTimeout() == 0)
+    {
+        SetResponseTimeout(aDefaultConfig.GetResponseTimeout());
+    }
+
+    if (GetMaxTxAttempts() == 0)
+    {
+        SetMaxTxAttempts(aDefaultConfig.GetMaxTxAttempts());
+    }
+
+    if (GetRecursionFlag() == kFlagUnspecified)
+    {
+        SetRecursionFlag(aDefaultConfig.GetRecursionFlag());
+    }
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+// Client::Response
+
+void Client::Response::SelectSection(Section aSection, uint16_t &aOffset, uint16_t &aNumRecord) const
+{
+    switch (aSection)
+    {
+    case kAnswerSection:
+        aOffset    = mAnswerOffset;
+        aNumRecord = mAnswerRecordCount;
+        break;
+    case kAdditionalDataSection:
+    default:
+        aOffset    = mAdditionalOffset;
+        aNumRecord = mAdditionalRecordCount;
+        break;
+    }
+}
+
+Error Client::Response::GetName(char *aNameBuffer, uint16_t aNameBufferSize) const
+{
+    uint16_t offset = kNameOffsetInQuery;
+
+    return Name::ReadName(*mQuery, offset, aNameBuffer, aNameBufferSize);
+}
+
+Error Client::Response::FindHostAddress(Section       aSection,
+                                        const Name &  aHostName,
+                                        uint16_t      aIndex,
+                                        Ip6::Address &aAddress,
+                                        uint32_t &    aTtl) const
+{
+    Error       error;
+    uint16_t    offset;
+    uint16_t    numRecords;
+    Name        name = aHostName;
+    CnameRecord cnameRecord;
+    AaaaRecord  aaaaRecord;
+
+    VerifyOrExit(mMessage != nullptr, error = kErrorNotFound);
+
+    // If the response includes a CNAME record mapping the query host
+    // name to a canonical name, we then search for AAAA records
+    // matching the canonical name.
+
+    SelectSection(aSection, offset, numRecords);
+    error = ResourceRecord::FindRecord(*mMessage, offset, numRecords, /* aIndex */ 0, aHostName, cnameRecord);
+
+    if (error == kErrorNone)
+    {
+        name.SetFromMessage(*mMessage, offset);
+        SuccessOrExit(error = Name::ParseName(*mMessage, offset));
+    }
+    else
+    {
+        VerifyOrExit(error == kErrorNotFound);
+    }
+
+    SelectSection(aSection, offset, numRecords);
+    SuccessOrExit(error = ResourceRecord::FindRecord(*mMessage, offset, numRecords, aIndex, name, aaaaRecord));
+    aAddress = aaaaRecord.GetAddress();
+    aTtl     = aaaaRecord.GetTtl();
+
+exit:
+    return error;
+}
+
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+
+Error Client::Response::FindServiceInfo(Section aSection, const Name &aName, ServiceInfo &aServiceInfo) const
+{
+    // This method searches for SRV and TXT records in the given
+    // section matching the record name against `aName`, and updates
+    // the `aServiceInfo` accordingly. It also searches for AAAA
+    // record for host name associated with the service (from SRV
+    // record). The search for AAAA record is always performed in
+    // Additional Data section (independent of the value given in
+    // `aSection`).
+
+    Error     error;
+    uint16_t  offset;
+    uint16_t  numRecords;
+    Name      hostName;
+    SrvRecord srvRecord;
+    TxtRecord txtRecord;
+
+    VerifyOrExit(mMessage != nullptr, error = kErrorNotFound);
+
+    // Search for a matching SRV record
+    SelectSection(aSection, offset, numRecords);
+    SuccessOrExit(error = ResourceRecord::FindRecord(*mMessage, offset, numRecords, /* aIndex */ 0, aName, srvRecord));
+
+    aServiceInfo.mTtl      = srvRecord.GetTtl();
+    aServiceInfo.mPort     = srvRecord.GetPort();
+    aServiceInfo.mPriority = srvRecord.GetPriority();
+    aServiceInfo.mWeight   = srvRecord.GetWeight();
+
+    hostName.SetFromMessage(*mMessage, offset);
+
+    if (aServiceInfo.mHostNameBuffer != nullptr)
+    {
+        SuccessOrExit(error = srvRecord.ReadTargetHostName(*mMessage, offset, aServiceInfo.mHostNameBuffer,
+                                                           aServiceInfo.mHostNameBufferSize));
+    }
+    else
+    {
+        SuccessOrExit(error = Name::ParseName(*mMessage, offset));
+    }
+
+    // Search in additional section for AAAA record for the host name.
+
+    error = FindHostAddress(kAdditionalDataSection, hostName, /* aIndex */ 0,
+                            static_cast<Ip6::Address &>(aServiceInfo.mHostAddress), aServiceInfo.mHostAddressTtl);
+
+    if (error == kErrorNotFound)
+    {
+        static_cast<Ip6::Address &>(aServiceInfo.mHostAddress).Clear();
+        aServiceInfo.mHostAddressTtl = 0;
+    }
+    else
+    {
+        SuccessOrExit(error);
+    }
+
+    // A null `mTxtData` indicates that caller does not want to retrieve TXT data.
+    VerifyOrExit(aServiceInfo.mTxtData != nullptr);
+
+    // Search for a matching TXT record. If not found, indicate this by
+    // setting `aServiceInfo.mTxtDataSize` to zero.
+
+    SelectSection(aSection, offset, numRecords);
+    error = ResourceRecord::FindRecord(*mMessage, offset, numRecords, /* aIndex */ 0, aName, txtRecord);
+
+    switch (error)
+    {
+    case kErrorNone:
+        SuccessOrExit(error =
+                          txtRecord.ReadTxtData(*mMessage, offset, aServiceInfo.mTxtData, aServiceInfo.mTxtDataSize));
+        aServiceInfo.mTxtDataTtl = txtRecord.GetTtl();
+        break;
+
+    case kErrorNotFound:
+        aServiceInfo.mTxtDataSize = 0;
+        aServiceInfo.mTxtDataTtl  = 0;
+        break;
+
+    default:
+        ExitNow();
+    }
+
+exit:
+    return error;
+}
+
+#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+
+//---------------------------------------------------------------------------------------------------------------------
+// Client::AddressResponse
+
+Error Client::AddressResponse::GetAddress(uint16_t aIndex, Ip6::Address &aAddress, uint32_t &aTtl) const
+{
+    return FindHostAddress(kAnswerSection, Name(*mQuery, kNameOffsetInQuery), aIndex, aAddress, aTtl);
+}
+
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+
+//---------------------------------------------------------------------------------------------------------------------
+// Client::BrowseResponse
+
+Error Client::BrowseResponse::GetServiceInstance(uint16_t aIndex, char *aLabelBuffer, uint8_t aLabelBufferSize) const
+{
+    Error     error;
+    uint16_t  offset;
+    uint16_t  numRecords;
+    Name      serviceName(*mQuery, kNameOffsetInQuery);
+    PtrRecord ptrRecord;
+
+    VerifyOrExit(mMessage != nullptr, error = kErrorNotFound);
+
+    SelectSection(kAnswerSection, offset, numRecords);
+    SuccessOrExit(error = ResourceRecord::FindRecord(*mMessage, offset, numRecords, aIndex, serviceName, ptrRecord));
+    error = ptrRecord.ReadPtrName(*mMessage, offset, aLabelBuffer, aLabelBufferSize, nullptr, 0);
+
+exit:
+    return error;
+}
+
+Error Client::BrowseResponse::GetServiceInfo(const char *aInstanceLabel, ServiceInfo &aServiceInfo) const
+{
+    Error error;
+    Name  instanceName;
+
+    // Find a matching PTR record for the service instance label.
+    // Then search and read SRV, TXT and AAAA records in Additional Data section
+    // matching the same name to populate `aServiceInfo`.
+
+    SuccessOrExit(error = FindPtrRecord(aInstanceLabel, instanceName));
+    error = FindServiceInfo(kAdditionalDataSection, instanceName, aServiceInfo);
+
+exit:
+    return error;
+}
+
+Error Client::BrowseResponse::GetHostAddress(const char *  aHostName,
+                                             uint16_t      aIndex,
+                                             Ip6::Address &aAddress,
+                                             uint32_t &    aTtl) const
+{
+    return FindHostAddress(kAdditionalDataSection, Name(aHostName), aIndex, aAddress, aTtl);
+}
+
+Error Client::BrowseResponse::FindPtrRecord(const char *aInstanceLabel, Name &aInstanceName) const
+{
+    // This method searches within the Answer Section for a PTR record
+    // matching a given instance label @aInstanceLabel. If found, the
+    // `aName` is updated to return the name in the message.
+
+    Error     error;
+    uint16_t  offset;
+    Name      serviceName(*mQuery, kNameOffsetInQuery);
+    uint16_t  numRecords;
+    uint16_t  labelOffset;
+    PtrRecord ptrRecord;
+
+    VerifyOrExit(mMessage != nullptr, error = kErrorNotFound);
+
+    SelectSection(kAnswerSection, offset, numRecords);
+
+    for (; numRecords > 0; numRecords--)
+    {
+        SuccessOrExit(error = Name::CompareName(*mMessage, offset, serviceName));
+
+        error = ResourceRecord::ReadRecord(*mMessage, offset, ptrRecord);
+
+        if (error == kErrorNotFound)
+        {
+            // `ReadRecord()` updates `offset` to skip over a
+            // non-matching record.
+            continue;
+        }
+
+        SuccessOrExit(error);
+
+        // It is a PTR record. Check the first label to match the
+        // instance label and the rest of the name to match the service
+        // name from `mQuery`.
+
+        labelOffset = offset;
+        error       = Name::CompareLabel(*mMessage, labelOffset, aInstanceLabel);
+
+        if (error == kErrorNone)
+        {
+            error = Name::CompareName(*mMessage, labelOffset, serviceName);
+
+            if (error == kErrorNone)
+            {
+                aInstanceName.SetFromMessage(*mMessage, offset);
+                ExitNow();
+            }
+        }
+
+        VerifyOrExit(error == kErrorNotFound);
+
+        // Update offset to skip over the PTR record.
+        offset += static_cast<uint16_t>(ptrRecord.GetSize()) - sizeof(ptrRecord);
+    }
+
+    error = kErrorNotFound;
+
+exit:
+    return error;
+}
+
+//---------------------------------------------------------------------------------------------------------------------
+// Client::ServiceResponse
+
+Error Client::ServiceResponse::GetServiceName(char *   aLabelBuffer,
+                                              uint8_t  aLabelBufferSize,
+                                              char *   aNameBuffer,
+                                              uint16_t aNameBufferSize) const
+{
+    Error    error;
+    uint16_t offset = kNameOffsetInQuery;
+
+    SuccessOrExit(error = Name::ReadLabel(*mQuery, offset, aLabelBuffer, aLabelBufferSize));
+
+    VerifyOrExit(aNameBuffer != nullptr);
+    SuccessOrExit(error = Name::ReadName(*mQuery, offset, aNameBuffer, aNameBufferSize));
+
+exit:
+    return error;
+}
+
+Error Client::ServiceResponse::GetServiceInfo(ServiceInfo &aServiceInfo) const
+{
+    // Search and read SRV, TXT records in Answer Section
+    // matching name from query.
+
+    return FindServiceInfo(kAnswerSection, Name(*mQuery, kNameOffsetInQuery), aServiceInfo);
+}
+
+Error Client::ServiceResponse::GetHostAddress(const char *  aHostName,
+                                              uint16_t      aIndex,
+                                              Ip6::Address &aAddress,
+                                              uint32_t &    aTtl) const
+{
+    return FindHostAddress(kAdditionalDataSection, Name(aHostName), aIndex, aAddress, aTtl);
+}
+
+#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+
+//---------------------------------------------------------------------------------------------------------------------
+// Client
+
+const uint16_t Client::kAddressQueryRecordTypes[] = {ResourceRecord::kTypeAaaa};
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+const uint16_t Client::kBrowseQueryRecordTypes[]  = {ResourceRecord::kTypePtr};
+const uint16_t Client::kServiceQueryRecordTypes[] = {ResourceRecord::kTypeSrv, ResourceRecord::kTypeTxt};
+#endif
+
+const uint8_t Client::kQuestionCount[] = {
+    /* (0) kAddressQuery -> */ OT_ARRAY_LENGTH(kAddressQueryRecordTypes), // AAAA records
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+    /* (1) kBrowseQuery  -> */ OT_ARRAY_LENGTH(kBrowseQueryRecordTypes),  // PTR records
+    /* (2) kServiceQuery -> */ OT_ARRAY_LENGTH(kServiceQueryRecordTypes), // SRV and TXT records
+#endif
+};
+
+const uint16_t *Client::kQuestionRecordTypes[] = {
+    /* (0) kAddressQuery -> */ kAddressQueryRecordTypes,
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+    /* (1) kBrowseQuery  -> */ kBrowseQueryRecordTypes,
+    /* (2) kServiceQuery -> */ kServiceQueryRecordTypes,
+#endif
+};
+
+Client::Client(Instance &aInstance)
+    : InstanceLocator(aInstance)
+    , mSocket(aInstance)
+    , mTimer(aInstance, Client::HandleTimer)
+    , mDefaultConfig(QueryConfig::kInitFromDefaults)
+{
+    static_assert(kAddressQuery == 0, "kAddressQuery value is not correct");
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+    static_assert(kBrowseQuery == 1, "kBrowseQuery value is not correct");
+    static_assert(kServiceQuery == 2, "kServiceQuery value is not correct");
+#endif
+}
+
+Error Client::Start(void)
+{
+    Error error;
 
     SuccessOrExit(error = mSocket.Open(&Client::HandleUdpReceive, this));
     SuccessOrExit(error = mSocket.Bind());
@@ -65,351 +456,442 @@
     return error;
 }
 
-otError Client::Stop(void)
+void Client::Stop(void)
 {
-    Message *     message;
-    QueryMetadata queryMetadata;
+    Query *query;
 
-    // Remove all pending queries.
-    while ((message = mPendingQueries.GetHead()) != nullptr)
+    while ((query = mQueries.GetHead()) != nullptr)
     {
-        queryMetadata.ReadFrom(*message);
-        FinalizeDnsTransaction(*message, queryMetadata, nullptr, 0, OT_ERROR_ABORT);
+        FinalizeQuery(*query, kErrorAbort);
     }
 
-    return mSocket.Close();
+    IgnoreError(mSocket.Close());
 }
 
-otError Client::Query(const QueryInfo &aQuery, ResponseHandler aHandler, void *aContext)
+void Client::SetDefaultConfig(const QueryConfig &aQueryConfig)
 {
-    otError       error;
-    QueryMetadata queryMetadata;
-    Message *     message     = nullptr;
-    Message *     messageCopy = nullptr;
-    Header        header;
-    QuestionAaaa  question;
-    uint16_t      messageId;
+    QueryConfig startingDefault(QueryConfig::kInitFromDefaults);
 
-    VerifyOrExit(aQuery.IsValid(), error = OT_ERROR_INVALID_ARGS);
+    mDefaultConfig.SetFrom(aQueryConfig, startingDefault);
+}
 
-    SuccessOrExit(error = GenerateUniqueRandomId(messageId));
+void Client::ResetDefaultConfig(void)
+{
+    mDefaultConfig = QueryConfig(QueryConfig::kInitFromDefaults);
+}
 
-    header.SetMessageId(messageId);
+Error Client::ResolveAddress(const char *       aHostName,
+                             AddressCallback    aCallback,
+                             void *             aContext,
+                             const QueryConfig *aConfig)
+{
+    QueryInfo info;
+
+    info.Clear();
+    info.mQueryType                 = kAddressQuery;
+    info.mCallback.mAddressCallback = aCallback;
+
+    return StartQuery(info, aConfig, nullptr, aHostName, aContext);
+}
+
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+
+Error Client::Browse(const char *aServiceName, BrowseCallback aCallback, void *aContext, const QueryConfig *aConfig)
+{
+    QueryInfo info;
+
+    info.Clear();
+    info.mQueryType                = kBrowseQuery;
+    info.mCallback.mBrowseCallback = aCallback;
+
+    return StartQuery(info, aConfig, nullptr, aServiceName, aContext);
+}
+
+Error Client::ResolveService(const char *       aInstanceLabel,
+                             const char *       aServiceName,
+                             ServiceCallback    aCallback,
+                             void *             aContext,
+                             const QueryConfig *aConfig)
+{
+    QueryInfo info;
+    Error     error;
+
+    VerifyOrExit(aInstanceLabel != nullptr, error = kErrorInvalidArgs);
+
+    info.Clear();
+    info.mQueryType                 = kServiceQuery;
+    info.mCallback.mServiceCallback = aCallback;
+
+    error = StartQuery(info, aConfig, aInstanceLabel, aServiceName, aContext);
+
+exit:
+    return error;
+}
+
+#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+
+Error Client::StartQuery(QueryInfo &        aInfo,
+                         const QueryConfig *aConfig,
+                         const char *       aLabel,
+                         const char *       aName,
+                         void *             aContext)
+{
+    // This method assumes that `mQueryType` and `mCallback` to be
+    // already set by caller on `aInfo`. The `aLabel` can be `nullptr`
+    // and then `aName` provides the full name, otherwise the name is
+    // appended as `{aLabel}.{aName}`.
+
+    Error  error;
+    Query *query;
+
+    VerifyOrExit(mSocket.IsBound(), error = kErrorInvalidState);
+
+    if (aConfig == nullptr)
+    {
+        aInfo.mConfig = mDefaultConfig;
+    }
+    else
+    {
+        // To form the config for this query, replace any unspecified
+        // fields (zero value) in the given `aConfig` with the fields
+        // from `mDefaultConfig`.
+
+        aInfo.mConfig.SetFrom(*aConfig, mDefaultConfig);
+    }
+
+    aInfo.mCallbackContext = aContext;
+
+    SuccessOrExit(error = AllocateQuery(aInfo, aLabel, aName, query));
+    mQueries.Enqueue(*query);
+
+    SendQuery(*query);
+
+exit:
+    return error;
+}
+
+Error Client::AllocateQuery(const QueryInfo &aInfo, const char *aLabel, const char *aName, Query *&aQuery)
+{
+    Error error = kErrorNone;
+
+    aQuery = Get<MessagePool>().New(Message::kTypeOther, /* aReserveHeader */ 0);
+    VerifyOrExit(aQuery != nullptr, error = kErrorNoBufs);
+
+    SuccessOrExit(error = aQuery->Append(aInfo));
+
+    if (aLabel != nullptr)
+    {
+        SuccessOrExit(error = Name::AppendLabel(aLabel, *aQuery));
+    }
+
+    SuccessOrExit(error = Name::AppendName(aName, *aQuery));
+
+exit:
+    FreeAndNullMessageOnError(aQuery, error);
+    return error;
+}
+
+void Client::FreeQuery(Query &aQuery)
+{
+    mQueries.Dequeue(aQuery);
+    aQuery.Free();
+}
+
+void Client::SendQuery(Query &aQuery)
+{
+    QueryInfo info;
+
+    info.ReadFrom(aQuery);
+
+    SendQuery(aQuery, info, /* aUpdateTimer */ true);
+}
+
+void Client::SendQuery(Query &aQuery, QueryInfo &aInfo, bool aUpdateTimer)
+{
+    // This method prepares and sends a query message represented by
+    // `aQuery` and `aInfo`. This method updates `aInfo` (e.g., sets
+    // the new `mRetransmissionTime`) and updates it in `aQuery` as
+    // well. `aUpdateTimer` indicates whether the timer should be
+    // updated when query is sent or not (used in the case where timer
+    // is handled by caller).
+
+    Error            error   = kErrorNone;
+    Message *        message = nullptr;
+    Header           header;
+    Ip6::MessageInfo messageInfo;
+
+    aInfo.mTransmissionCount++;
+    aInfo.mRetransmissionTime = TimerMilli::GetNow() + aInfo.mConfig.GetResponseTimeout();
+
+    if (aInfo.mMessageId == 0)
+    {
+        do
+        {
+            SuccessOrExit(error = header.SetRandomMessageId());
+        } while ((header.GetMessageId() == 0) || (FindQueryById(header.GetMessageId()) != nullptr));
+
+        aInfo.mMessageId = header.GetMessageId();
+    }
+    else
+    {
+        header.SetMessageId(aInfo.mMessageId);
+    }
+
     header.SetType(Header::kTypeQuery);
     header.SetQueryType(Header::kQueryTypeStandard);
 
-    if (!aQuery.IsNoRecursion())
+    if (aInfo.mConfig.GetRecursionFlag() == QueryConfig::kFlagRecursionDesired)
     {
         header.SetRecursionDesiredFlag();
     }
 
-    header.SetQuestionCount(1);
+    header.SetQuestionCount(kQuestionCount[aInfo.mQueryType]);
 
-    VerifyOrExit((message = NewMessage(header)) != nullptr, error = OT_ERROR_NO_BUFS);
+    message = mSocket.NewMessage(0);
+    VerifyOrExit(message != nullptr, error = kErrorNoBufs);
 
-    SuccessOrExit(error = Name::AppendName(aQuery.GetHostname(), *message));
-    SuccessOrExit(error = question.AppendTo(*message));
+    SuccessOrExit(error = message->Append(header));
 
-    queryMetadata.mHostname            = aQuery.GetHostname();
-    queryMetadata.mResponseHandler     = aHandler;
-    queryMetadata.mResponseContext     = aContext;
-    queryMetadata.mTransmissionTime    = TimerMilli::GetNow() + kResponseTimeout;
-    queryMetadata.mSourceAddress       = aQuery.GetMessageInfo().GetSockAddr();
-    queryMetadata.mDestinationAddress  = aQuery.GetMessageInfo().GetPeerAddr();
-    queryMetadata.mDestinationPort     = aQuery.GetMessageInfo().GetPeerPort();
-    queryMetadata.mRetransmissionCount = 0;
+    // Prepare the question section.
 
-    VerifyOrExit((messageCopy = CopyAndEnqueueMessage(*message, queryMetadata)) != nullptr, error = OT_ERROR_NO_BUFS);
-    SuccessOrExit(error = SendMessage(*message, aQuery.GetMessageInfo()));
+    for (uint8_t num = 0; num < kQuestionCount[aInfo.mQueryType]; num++)
+    {
+        SuccessOrExit(error = AppendNameFromQuery(aQuery, *message));
+        SuccessOrExit(error = message->Append(Question(kQuestionRecordTypes[aInfo.mQueryType][num])));
+    }
+
+    messageInfo.SetPeerAddr(aInfo.mConfig.GetServerSockAddr().GetAddress());
+    messageInfo.SetPeerPort(aInfo.mConfig.GetServerSockAddr().GetPort());
+
+    SuccessOrExit(error = mSocket.SendTo(*message, messageInfo));
 
 exit:
+    FreeMessageOnError(message, error);
 
-    if (error != OT_ERROR_NONE)
+    UpdateQuery(aQuery, aInfo);
+
+    if (aUpdateTimer)
     {
-        FreeMessage(message);
+        mTimer.FireAtIfEarlier(aInfo.mRetransmissionTime);
+    }
+}
 
-        if (messageCopy)
+Error Client::AppendNameFromQuery(const Query &aQuery, Message &aMessage)
+{
+    Error    error = kErrorNone;
+    uint16_t offset;
+    uint16_t length;
+
+    // The name is encoded and included after the `Info` in `aQuery`. We
+    // first calculate the encoded length of the name, then grow the
+    // message, and finally copy the encoded name bytes from `aQuery`
+    // into `aMessage`.
+
+    length = aQuery.GetLength() - kNameOffsetInQuery;
+
+    offset = aMessage.GetLength();
+    SuccessOrExit(error = aMessage.SetLength(offset + length));
+
+    aQuery.CopyTo(/* aSourceOffset */ kNameOffsetInQuery, /* aDestOffset */ offset, length, aMessage);
+
+exit:
+    return error;
+}
+
+void Client::FinalizeQuery(Query &aQuery, Error aError)
+{
+    Response  response;
+    QueryInfo info;
+
+    response.mQuery = &aQuery;
+    info.ReadFrom(aQuery);
+
+    FinalizeQuery(response, info.mQueryType, aError);
+}
+
+void Client::FinalizeQuery(Response &aResponse, QueryType aType, Error aError)
+{
+    Callback callback;
+    void *   context;
+
+    GetCallback(*aResponse.mQuery, callback, context);
+
+    switch (aType)
+    {
+    case kAddressQuery:
+        if (callback.mAddressCallback != nullptr)
         {
-            DequeueMessage(*messageCopy);
+            callback.mAddressCallback(aError, &aResponse, context);
         }
-    }
+        break;
 
-    return error;
-}
-
-Message *Client::NewMessage(const Header &aHeader)
-{
-    Message *message = mSocket.NewMessage(sizeof(aHeader));
-
-    VerifyOrExit(message != nullptr);
-    IgnoreError(message->Prepend(aHeader));
-    message->SetOffset(0);
-
-exit:
-    return message;
-}
-
-Message *Client::CopyAndEnqueueMessage(const Message &aMessage, const QueryMetadata &aQueryMetadata)
-{
-    otError  error       = OT_ERROR_NONE;
-    Message *messageCopy = aMessage.Clone();
-
-    VerifyOrExit(messageCopy != nullptr, error = OT_ERROR_NO_BUFS);
-
-    SuccessOrExit(error = aQueryMetadata.AppendTo(*messageCopy));
-    mPendingQueries.Enqueue(*messageCopy);
-
-    mRetransmissionTimer.FireAtIfEarlier(aQueryMetadata.mTransmissionTime);
-
-exit:
-    FreeAndNullMessageOnError(messageCopy, error);
-    return messageCopy;
-}
-
-void Client::DequeueMessage(Message &aMessage)
-{
-    mPendingQueries.Dequeue(aMessage);
-
-    if (mPendingQueries.GetHead() == nullptr)
-    {
-        mRetransmissionTimer.Stop();
-    }
-
-    aMessage.Free();
-}
-
-otError Client::SendMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
-{
-    return mSocket.SendTo(aMessage, aMessageInfo);
-}
-
-void Client::SendCopy(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
-{
-    otError  error;
-    Message *messageCopy = aMessage.Clone(aMessage.GetLength() - sizeof(QueryMetadata));
-
-    VerifyOrExit(messageCopy != nullptr, error = OT_ERROR_NO_BUFS);
-
-    error = SendMessage(*messageCopy, aMessageInfo);
-
-exit:
-
-    if (error != OT_ERROR_NONE)
-    {
-        FreeMessage(messageCopy);
-        otLogWarnIp6("Failed to send DNS request: %s", otThreadErrorToString(error));
-    }
-}
-
-otError Client::GenerateUniqueRandomId(uint16_t &aRandomId)
-{
-    otError error;
-
-    do
-    {
-        SuccessOrExit(error = Random::Crypto::FillBuffer(reinterpret_cast<uint8_t *>(&aRandomId), sizeof(aRandomId)));
-    } while (FindQueryById(aRandomId) != nullptr);
-
-exit:
-    return error;
-}
-
-otError Client::CompareQuestions(Message &aMessageResponse, Message &aMessageQuery, uint16_t &aOffset)
-{
-    otError  error = OT_ERROR_NONE;
-    uint8_t  bufQuery[kBufSize];
-    uint8_t  bufResponse[kBufSize];
-    uint16_t read = 0;
-
-    // Compare question section of the query with the response.
-    uint16_t length = aMessageQuery.GetLength() - aMessageQuery.GetOffset() - sizeof(Header) - sizeof(QueryMetadata);
-    uint16_t offset = aMessageQuery.GetOffset() + sizeof(Header);
-
-    while (length > 0)
-    {
-        VerifyOrExit((read = aMessageQuery.ReadBytes(offset, bufQuery,
-                                                     length < sizeof(bufQuery) ? length : sizeof(bufQuery))) > 0,
-                     error = OT_ERROR_PARSE);
-        SuccessOrExit(error = aMessageResponse.Read(aOffset, bufResponse, read));
-
-        VerifyOrExit(memcmp(bufResponse, bufQuery, read) == 0, error = OT_ERROR_NOT_FOUND);
-
-        aOffset += read;
-        offset += read;
-        length -= read;
-    }
-
-exit:
-    return error;
-}
-
-Message *Client::FindQueryById(uint16_t aMessageId)
-{
-    uint16_t messageId;
-    Message *message;
-
-    for (message = mPendingQueries.GetHead(); message != nullptr; message = message->GetNext())
-    {
-        // Partially read DNS header to obtain message ID only.
-        if (message->Read(message->GetOffset(), messageId) != OT_ERROR_NONE)
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+    case kBrowseQuery:
+        if (callback.mBrowseCallback != nullptr)
         {
-            OT_ASSERT(false);
+            callback.mBrowseCallback(aError, &aResponse, context);
         }
+        break;
 
-        if (HostSwap16(messageId) == aMessageId)
+    case kServiceQuery:
+        if (callback.mServiceCallback != nullptr)
+        {
+            callback.mServiceCallback(aError, &aResponse, context);
+        }
+        break;
+#endif
+    }
+
+    FreeQuery(*aResponse.mQuery);
+}
+
+void Client::GetCallback(const Query &aQuery, Callback &aCallback, void *&aContext)
+{
+    QueryInfo info;
+
+    info.ReadFrom(aQuery);
+
+    aCallback = info.mCallback;
+    aContext  = info.mCallbackContext;
+}
+
+Client::Query *Client::FindQueryById(uint16_t aMessageId)
+{
+    Query *   query;
+    QueryInfo info;
+
+    for (query = mQueries.GetHead(); query != nullptr; query = query->GetNext())
+    {
+        info.ReadFrom(*query);
+
+        if (info.mMessageId == aMessageId)
         {
             break;
         }
     }
 
-    return message;
+    return query;
 }
 
-void Client::FinalizeDnsTransaction(Message &            aQuery,
-                                    const QueryMetadata &aQueryMetadata,
-                                    const Ip6::Address * aAddress,
-                                    uint32_t             aTtl,
-                                    otError              aResult)
+void Client::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMsgInfo)
 {
-    DequeueMessage(aQuery);
+    OT_UNUSED_VARIABLE(aMsgInfo);
 
-    if (aQueryMetadata.mResponseHandler != nullptr)
+    static_cast<Client *>(aContext)->ProcessResponse(*static_cast<Message *>(aMessage));
+}
+
+void Client::ProcessResponse(const Message &aMessage)
+{
+    Response  response;
+    QueryType type;
+    Error     responseError;
+
+    response.mMessage = &aMessage;
+
+    SuccessOrExit(ParseResponse(response, type, responseError));
+    FinalizeQuery(response, type, responseError);
+
+exit:
+    return;
+}
+
+Error Client::ParseResponse(Response &aResponse, QueryType &aType, Error &aResponseError)
+{
+    Error          error   = kErrorNone;
+    const Message &message = *aResponse.mMessage;
+    uint16_t       offset  = message.GetOffset();
+    Header         header;
+    QueryInfo      info;
+
+    SuccessOrExit(error = message.Read(offset, header));
+    offset += sizeof(Header);
+
+    VerifyOrExit((header.GetType() == Header::kTypeResponse) && (header.GetQueryType() == Header::kQueryTypeStandard) &&
+                     !header.IsTruncationFlagSet(),
+                 error = kErrorDrop);
+
+    aResponse.mQuery = FindQueryById(header.GetMessageId());
+    VerifyOrExit(aResponse.mQuery != nullptr, error = kErrorNotFound);
+
+    info.ReadFrom(*aResponse.mQuery);
+    aType = info.mQueryType;
+
+    // Check the Question Section
+
+    VerifyOrExit(header.GetQuestionCount() == kQuestionCount[aType], error = kErrorParse);
+
+    for (uint8_t num = 0; num < kQuestionCount[aType]; num++)
     {
-        aQueryMetadata.mResponseHandler(aQueryMetadata.mResponseContext, aQueryMetadata.mHostname, aAddress, aTtl,
-                                        aResult);
+        // The name is encoded after `Info` struct in `query`.
+        SuccessOrExit(error = Name::CompareName(message, offset, *aResponse.mQuery, kNameOffsetInQuery));
+        offset += sizeof(Question);
     }
-}
 
-void Client::HandleRetransmissionTimer(Timer &aTimer)
-{
-    aTimer.GetOwner<Client>().HandleRetransmissionTimer();
-}
+    // Check the answer, authority and additional record sections
 
-void Client::HandleRetransmissionTimer(void)
-{
-    TimeMilli        now      = TimerMilli::GetNow();
-    TimeMilli        nextTime = now.GetDistantFuture();
-    QueryMetadata    queryMetadata;
-    Message *        message;
-    Message *        nextMessage;
-    Ip6::MessageInfo messageInfo;
+    aResponse.mAnswerOffset = offset;
+    SuccessOrExit(error = ResourceRecord::ParseRecords(message, offset, header.GetAnswerCount()));
+    SuccessOrExit(error = ResourceRecord::ParseRecords(message, offset, header.GetAuthorityRecordCount()));
+    aResponse.mAdditionalOffset = offset;
+    SuccessOrExit(error = ResourceRecord::ParseRecords(message, offset, header.GetAdditionalRecordCount()));
 
-    for (message = mPendingQueries.GetHead(); message != nullptr; message = nextMessage)
+    aResponse.mAnswerRecordCount     = header.GetAnswerCount();
+    aResponse.mAdditionalRecordCount = header.GetAdditionalRecordCount();
+
+    // Check the response code from server
+
+    aResponseError = Header::ResponseCodeToError(header.GetResponseCode());
+
+exit:
+    if (error != kErrorNone)
     {
-        nextMessage = message->GetNext();
+        otLogInfoDns("Failed to parse response %s", ErrorToString(error));
+    }
 
-        queryMetadata.ReadFrom(*message);
+    return error;
+}
 
-        if (now >= queryMetadata.mTransmissionTime)
+void Client::HandleTimer(Timer &aTimer)
+{
+    aTimer.Get<Client>().HandleTimer();
+}
+
+void Client::HandleTimer(void)
+{
+    TimeMilli now      = TimerMilli::GetNow();
+    TimeMilli nextTime = now.GetDistantFuture();
+    Query *   nextQuery;
+    QueryInfo info;
+
+    for (Query *query = mQueries.GetHead(); query != nullptr; query = nextQuery)
+    {
+        nextQuery = query->GetNext();
+
+        info.ReadFrom(*query);
+
+        if (now >= info.mRetransmissionTime)
         {
-            if (queryMetadata.mRetransmissionCount >= kMaxRetransmit)
+            if (info.mTransmissionCount >= info.mConfig.GetMaxTxAttempts())
             {
-                FinalizeDnsTransaction(*message, queryMetadata, nullptr, 0, OT_ERROR_RESPONSE_TIMEOUT);
-
+                FinalizeQuery(*query, kErrorResponseTimeout);
                 continue;
             }
 
-            // Increment retransmission counter and timer.
-            queryMetadata.mRetransmissionCount++;
-            queryMetadata.mTransmissionTime = now + kResponseTimeout;
-            queryMetadata.UpdateIn(*message);
-
-            // Retransmit
-            messageInfo.SetPeerAddr(queryMetadata.mDestinationAddress);
-            messageInfo.SetPeerPort(queryMetadata.mDestinationPort);
-            messageInfo.SetSockAddr(queryMetadata.mSourceAddress);
-
-            SendCopy(*message, messageInfo);
+            SendQuery(*query, info, /* aUpdateTimer */ false);
         }
 
-        if (nextTime > queryMetadata.mTransmissionTime)
+        if (nextTime > info.mRetransmissionTime)
         {
-            nextTime = queryMetadata.mTransmissionTime;
+            nextTime = info.mRetransmissionTime;
         }
     }
 
     if (nextTime < now.GetDistantFuture())
     {
-        mRetransmissionTimer.FireAt(nextTime);
+        mTimer.FireAt(nextTime);
     }
 }
 
-void Client::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
-{
-    static_cast<Client *>(aContext)->HandleUdpReceive(*static_cast<Message *>(aMessage),
-                                                      *static_cast<const Ip6::MessageInfo *>(aMessageInfo));
-}
-
-void Client::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
-{
-    // RFC1035 7.3. Resolver cannot rely that a response will come from the same address
-    // which it sent the corresponding query to.
-    OT_UNUSED_VARIABLE(aMessageInfo);
-
-    otError            error = OT_ERROR_NOT_FOUND;
-    Header             responseHeader;
-    QueryMetadata      queryMetadata;
-    ResourceRecordAaaa record;
-    Message *          message = nullptr;
-    uint16_t           offset;
-
-    SuccessOrExit(aMessage.Read(aMessage.GetOffset(), responseHeader));
-    VerifyOrExit(responseHeader.GetType() == Header::kTypeResponse && responseHeader.GetQuestionCount() == 1 &&
-                 !responseHeader.IsTruncationFlagSet());
-
-    aMessage.MoveOffset(sizeof(responseHeader));
-    offset = aMessage.GetOffset();
-
-    VerifyOrExit((message = FindQueryById(responseHeader.GetMessageId())) != nullptr);
-    queryMetadata.ReadFrom(*message);
-
-    VerifyOrExit(responseHeader.GetResponseCode() == Header::kResponseSuccess, error = OT_ERROR_FAILED);
-
-    // Parse and check the question section.
-    SuccessOrExit(error = CompareQuestions(aMessage, *message, offset));
-
-    // Parse and check the answer section.
-    for (uint32_t index = 0; index < responseHeader.GetAnswerCount(); index++)
-    {
-        uint32_t newOffset;
-
-        SuccessOrExit(error = Name::ParseName(aMessage, offset));
-
-        SuccessOrExit(error = aMessage.Read(offset, record));
-
-        if (record.Matches(ResourceRecord::kTypeAaaa))
-        {
-            // Return the first found IPv6 address.
-            FinalizeDnsTransaction(*message, queryMetadata, &record.GetAddress(), record.GetTtl(), OT_ERROR_NONE);
-            ExitNow(error = OT_ERROR_NONE);
-        }
-
-        newOffset = offset + record.GetSize();
-        VerifyOrExit(newOffset <= aMessage.GetLength(), error = OT_ERROR_PARSE);
-        offset = static_cast<uint16_t>(newOffset);
-    }
-
-exit:
-
-    if (message != nullptr && error != OT_ERROR_NONE)
-    {
-        FinalizeDnsTransaction(*message, queryMetadata, nullptr, 0, error);
-    }
-}
-
-void Client::QueryMetadata::ReadFrom(const Message &aMessage)
-{
-    uint16_t length = aMessage.GetLength();
-
-    OT_ASSERT(length >= sizeof(*this));
-    IgnoreError(aMessage.Read(length - sizeof(*this), *this));
-}
-
-void Client::QueryMetadata::UpdateIn(Message &aMessage) const
-{
-    aMessage.Write(aMessage.GetLength() - sizeof(*this), *this);
-}
-
 } // namespace Dns
 } // namespace ot
 
diff --git a/src/core/net/dns_client.hpp b/src/core/net/dns_client.hpp
index 656f5c4..23eea18 100644
--- a/src/core/net/dns_client.hpp
+++ b/src/core/net/dns_client.hpp
@@ -1,5 +1,5 @@
 /*
- *  Copyright (c) 2017, The OpenThread Authors.
+ *  Copyright (c) 2017-2021, The OpenThread Authors.
  *  All rights reserved.
  *
  *  Redistribution and use in source and binary forms, with or without
@@ -31,12 +31,13 @@
 
 #include "openthread-core-config.h"
 
-#include <openthread/dns.h>
+#include <openthread/dns_client.h>
 
+#include "common/clearable.hpp"
 #include "common/message.hpp"
 #include "common/non_copyable.hpp"
 #include "common/timer.hpp"
-#include "net/dns_headers.hpp"
+#include "net/dns_types.hpp"
 #include "net/ip6.hpp"
 #include "net/netif.hpp"
 
@@ -45,6 +46,34 @@
  *   This file includes definitions for the DNS client.
  */
 
+/**
+ * This struct represents an opaque (and empty) type for a response to an address resolution DNS query.
+ *
+ */
+struct otDnsAddressResponse
+{
+};
+
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+
+/**
+ * This struct represents an opaque (and empty) type for a response to browse (service instance enumeration) DNS query.
+ *
+ */
+struct otDnsBrowseResponse
+{
+};
+
+/**
+ * This struct represents an opaque (and empty) type for a response to service inst resolution DNS query.
+ *
+ */
+struct otDnsServiceResponse
+{
+};
+
+#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+
 namespace ot {
 namespace Dns {
 
@@ -52,57 +81,409 @@
  * This class implements DNS client.
  *
  */
-class Client : private NonCopyable
+class Client : public InstanceLocator, private NonCopyable
 {
+    typedef Message Query; // `Message` is used to save `Query` related info.
+
 public:
     /**
-     * This type represents a DNS Query info/parameters.
+     * This type represents a DNS query configuration (e.g., server address, response wait timeout, etc).
      *
      */
-    class QueryInfo : public otDnsQuery
+    class QueryConfig : public otDnsQueryConfig, public Clearable<QueryConfig>
     {
+        friend class Client;
+
     public:
         /**
-         * This method indicates whether the `QueryInfo` object is valid or not.
-         *
-         * @returns TRUE if the `QueryInfo` is valid, FALSE otherwise.
+         * This enumeration type represents the "Recursion Desired" (RD) flag in a `otDnsQueryConfig`.
          *
          */
-        bool IsValid(void) const { return (mHostname != nullptr) && (mMessageInfo != nullptr); }
-
-        /**
-         * This method gets the host name in a DNS query.
-         *
-         * @return The host name.
-         *
-         */
-        const char *GetHostname(void) const { return mHostname; }
-
-        /**
-         * This method gets the `MessageInfo` related to DNS Server.
-         *
-         * @returns The `MessageInfo` of DNS Server.
-         *
-         */
-        const Ip6::MessageInfo &GetMessageInfo(void) const
+        enum RecursionFlag
         {
-            return *static_cast<const Ip6::MessageInfo *>(mMessageInfo);
+            kFlagUnspecified      = OT_DNS_FLAG_UNSPECIFIED,       ///< The flag is not specified.
+            kFlagRecursionDesired = OT_DNS_FLAG_RECURSION_DESIRED, ///< Server can resolve the query recursively.
+            kFlagNoRecursion      = OT_DNS_FLAG_NO_RECURSION,      ///< Server can not resolve the query recursively.
+        };
+
+        /**
+         * This is the default constructor for `QueryConfig` object.
+         *
+         */
+        QueryConfig(void) = default;
+
+        /**
+         * This method gets the server socket address (IPv6 address and port number).
+         *
+         * @returns The server socket address.
+         *
+         */
+        const Ip6::SockAddr &GetServerSockAddr(void) const
+        {
+            return static_cast<const Ip6::SockAddr &>(mServerSockAddr);
         }
 
         /**
-         * This method indicates whether or not the name server can pursue the query recursively.
+         * This method gets the wait time to receive response from server (in msec).
          *
-         * @returns TRUE if no recursion is allowed, FALSE otherwise.
+         * @returns The timeout interval in msec.
          *
          */
-        bool IsNoRecursion(void) const { return mNoRecursion; }
+        uint32_t GetResponseTimeout(void) const { return mResponseTimeout; }
+
+        /**
+         * This method gets the maximum number of query transmit attempts before reporting failure.
+         *
+         * @returns The maximum number of query transmit attempts.
+         *
+         */
+        uint8_t GetMaxTxAttempts(void) const { return mMaxTxAttempts; }
+
+        /**
+         * This method gets the recursion flag indicating whether the server can resolve the query recursively or not.
+         *
+         * @returns The recursion flag.
+         *
+         */
+        RecursionFlag GetRecursionFlag(void) const { return static_cast<RecursionFlag>(mRecursionFlag); }
+
+    private:
+        enum : uint32_t
+        {
+            kDefaultResponseTimeout = OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_RESPONSE_TIMEOUT, // in msec
+        };
+
+        enum : uint16_t
+        {
+            kDefaultServerPort = OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_PORT,
+        };
+
+        enum : uint8_t
+        {
+            kDefaultMaxTxAttempts = OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_MAX_TX_ATTEMPTS,
+        };
+
+        enum : bool
+        {
+            kDefaultRecursionDesired = OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_RECURSION_DESIRED_FLAG,
+        };
+
+        enum InitMode : uint8_t
+        {
+            kInitFromDefaults,
+        };
+
+        static const char kDefaultServerAddressString[];
+
+        explicit QueryConfig(InitMode aMode);
+
+        Ip6::SockAddr &GetServerSockAddr(void) { return static_cast<Ip6::SockAddr &>(mServerSockAddr); }
+
+        void SetResponseTimeout(uint32_t aResponseTimeout) { mResponseTimeout = aResponseTimeout; }
+        void SetMaxTxAttempts(uint8_t aMaxTxAttempts) { mMaxTxAttempts = aMaxTxAttempts; }
+        void SetRecursionFlag(RecursionFlag aFlag) { mRecursionFlag = static_cast<otDnsRecursionFlag>(aFlag); }
+
+        void SetFrom(const QueryConfig &aConfig, const QueryConfig &aDefaultConfig);
+    };
+
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+    /**
+     * This structure provides info for a DNS service instance.
+     *
+     */
+    typedef otDnsServiceInfo ServiceInfo;
+#endif
+
+    /**
+     * This class represents a DNS query response.
+     *
+     */
+    class Response : public Clearable<Response>,
+                     public otDnsAddressResponse
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+        ,
+                     public otDnsBrowseResponse,
+                     public otDnsServiceResponse
+#endif
+
+    {
+        friend class Client;
+
+    protected:
+        enum Section : uint8_t
+        {
+            kAnswerSection,
+            kAdditionalDataSection,
+        };
+
+        Response(void) { Clear(); }
+
+        Error GetName(char *aNameBuffer, uint16_t aNameBufferSize) const;
+        void  SelectSection(Section aSection, uint16_t &aOffset, uint16_t &aNumRecord) const;
+        Error FindHostAddress(Section       aSection,
+                              const Name &  aHostName,
+                              uint16_t      aIndex,
+                              Ip6::Address &aAddress,
+                              uint32_t &    aTtl) const;
+
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+        Error FindServiceInfo(Section aSection, const Name &aName, ServiceInfo &aServiceInfo) const;
+#endif
+
+        Query *        mQuery;                 // The associated query.
+        const Message *mMessage;               // The response message.
+        uint16_t       mAnswerOffset;          // Answer section offset in `mMessage`.
+        uint16_t       mAnswerRecordCount;     // Number of records in answer section.
+        uint16_t       mAdditionalOffset;      // Additional data section offset in `mMessage`.
+        uint16_t       mAdditionalRecordCount; // Number of records in additional data section.
     };
 
     /**
-     * This type represents the function pointer type which is called when a DNS response is received.
+     * This type represents the function pointer callback which is called when a DNS response for an address resolution
+     * query is received.
      *
      */
-    typedef otDnsResponseHandler ResponseHandler;
+    typedef otDnsAddressCallback AddressCallback;
+
+    /**
+     * This type represents an address resolution query DNS response.
+     *
+     */
+    class AddressResponse : public Response
+    {
+        friend class Client;
+
+    public:
+        /**
+         * This method gets the host name associated with an address resolution DNS response.
+         *
+         * This method MUST only be used from `AddressCallback`.
+         *
+         * @param[out] aNameBuffer       A buffer to char array to output the host name.
+         * @param[in]  aNameBufferSize   The size of @p aNameBuffer.
+         *
+         * @retval kErrorNone    The host name was read successfully.
+         * @retval kErrorNoBufs  The name does not fit in @p aNameBuffer.
+         *
+         */
+        Error GetHostName(char *aNameBuffer, uint16_t aNameBufferSize) const
+        {
+            return GetName(aNameBuffer, aNameBufferSize);
+        }
+
+        /**
+         * This method gets the IPv6 address associated with an address resolution DNS response.
+         *
+         * This method MUST only be used from `AddressCallback`.
+         *
+         * The response may include multiple IPv6 address records. @p aIndex can be used to iterate through the list of
+         * addresses. Index zero gets the the first address and so on. When we reach end of the list, this method
+         * returns `kErrorNotFound`.
+         *
+         * @param[in]  aIndex        The address record index to retrieve.
+         * @param[out] aAddress      A reference to an IPv6 address to output the address.
+         * @param[out] aTtl          A reference to a `uint32_t` to output TTL for the address.
+         *
+         * @retval kErrorNone       The address was read successfully.
+         * @retval kErrorNotFound   No address record at @p aIndex.
+         * @retval kErrorParse      Could not parse the records.
+         *
+         */
+        Error GetAddress(uint16_t aIndex, Ip6::Address &aAddress, uint32_t &aTtl) const;
+    };
+
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+
+    /**
+     * This type represents the function pointer callback which is called when a response for a browse (service
+     * instance enumeration) DNS query is received.
+     *
+     */
+    typedef otDnsBrowseCallback BrowseCallback;
+
+    /**
+     * This type represents a browse (service instance enumeration) DNS response.
+     *
+     */
+    class BrowseResponse : public Response
+    {
+        friend class Client;
+
+    public:
+        /**
+         * This method gets the service name associated with a DNS browse response.
+         *
+         * This method MUST only be used from `BrowseCallback`.
+         *
+         * @param[out] aNameBuffer       A buffer to char array to output the host name.
+         * @param[in]  aNameBufferSize   The size of @p aNameBuffer.
+         *
+         * @retval kErrorNone    The host name was read successfully.
+         * @retval kErrorNoBufs  The name does not fit in @p aNameBuffer.
+         *
+         */
+        Error GetServiceName(char *aNameBuffer, uint16_t aNameBufferSize) const
+        {
+            return GetName(aNameBuffer, aNameBufferSize);
+        }
+
+        /**
+         * This method gets a service instance associated with a DNS browse (service instance enumeration) response.
+         *
+         * This method MUST only be used from `BrowseCallback`.
+         *
+         * A response may include multiple service instance records. @p aIndex can be used to iterate through the list.
+         * Index zero gives the the first record. When we reach end of the list, `kErrorNotFound` is returned.
+         *
+         * Note that this method gets the service instance label and not the full service instance name which is of the
+         * form `<Instance>.<Service>.<Domain>`.
+         *
+         * @param[in]  aResponse          A pointer to a response.
+         * @param[in]  aIndex             The service instance record index to retrieve.
+         * @param[out] aLabelBuffer       A char array to output the service instance label (MUST NOT be NULL).
+         * @param[in]  aLabelBufferSize   The size of @p aLabelBuffer.
+         *
+         * @retval kErrorNone         The service instance was read successfully.
+         * @retval kErrorNoBufs       The name does not fit in @p aNameBuffer.
+         * @retval kErrorNotFound     No service instance record at @p aIndex.
+         * @retval kErrorParse        Could not parse the records.
+         *
+         */
+        Error GetServiceInstance(uint16_t aIndex, char *aLabelBuffer, uint8_t aLabelBufferSize) const;
+
+        /**
+         * This method gets info for a service instance from a DNS browse (service instance enumeration) response.
+         *
+         * This method MUST only be used from `BrowseCallback`.
+         *
+         * A browse DNS response should include the SRV, TXT, and AAAA records for the service instances that are
+         * enumerated (note that it is a SHOULD and not a MUST requirement). This method tries to retrieve this info
+         * for a given service instance.
+         *
+         * - If no matching SRV record is found, `kErrorNotFound` is returned.
+         * - If a matching SRV record is found, @p aServiceInfo is updated returning `kErrorNone`.
+         * - If no matching TXT record is found, `mTxtDataSize` in @p aServiceInfo is set to zero.
+         * - If no matching AAAA record is found, `mHostAddress is set to all zero or unspecified address.
+         * - If there are multiple AAAA records for the host name `mHostAddress` is set to the first one. The other
+         *   addresses can be retrieved using `GetHostAddress()` method.
+         *
+         * @param[in]  aInstanceLabel     The service instance label (MUST NOT be `nullptr`).
+         * @param[out] aServiceInfo       A `ServiceInfo` to output the service instance information.
+         *
+         * @retval kErrorNone         The service instance info was read. @p aServiceInfo is updated.
+         * @retval kErrorNotFound     Could not find a matching SRV record for @p aInstanceLabel.
+         * @retval kErrorNoBufs       The host name and/or the TXT data could not fit in given buffers.
+         * @retval kErrorParse        Could not parse the records.
+         *
+         */
+        Error GetServiceInfo(const char *aInstanceLabel, ServiceInfo &aServiceInfo) const;
+
+        /**
+         * This method gets the host IPv6 address from a DNS browse (service instance enumeration) response.
+         *
+         * This method MUST only be used from `BrowseCallback`.
+         *
+         * The response can include zero or more IPv6 address records. @p aIndex can be used to iterate through the
+         * list of addresses. Index zero gets the first address and so on. When we reach end of the list, this method
+         * returns `kErrorNotFound`.
+         *
+         * @param[in]  aHostName     The host name to get the address (MUST NOT be `nullptr`).
+         * @param[in]  aIndex        The address record index to retrieve.
+         * @param[out] aAddress      A reference to an IPv6 address to output the address.
+         * @param[out] aTtl          A reference to a `uint32_t` to output TTL for the address.
+         *
+         * @retval kErrorNone       The address was read successfully.
+         * @retval kErrorNotFound   No address record for @p aHostname at @p aIndex.
+         * @retval kErrorParse      Could not parse the records.
+         *
+         */
+        Error GetHostAddress(const char *aHostName, uint16_t aIndex, Ip6::Address &aAddress, uint32_t &aTtl) const;
+
+    private:
+        Error FindPtrRecord(const char *aInstanceLabel, Name &aInstanceName) const;
+    };
+
+    /**
+     * This type represents the function pointer callback which is called when a response for a service instance
+     * resolution DNS query is received.
+     *
+     */
+    typedef otDnsServiceCallback ServiceCallback;
+
+    /**
+     * This type represents a service instance resolution DNS response.
+     *
+     */
+    class ServiceResponse : public Response
+    {
+        friend class Client;
+
+    public:
+        /**
+         * This method gets the service instance name associated with a DNS service instance resolution response.
+         *
+         * This method MUST only be used from `ServiceCallback`.
+         *
+         * @param[out] aLabelBuffer      A buffer to char array to output the service instance label (MUST NOT be NULL).
+         * @param[in]  aLabelBufferSize  The size of @p aLabelBuffer.
+         * @param[out] aNameBuffer       A buffer to char array to output the rest of service name (can be NULL if user
+         *                               is not interested in getting the name).
+         * @param[in]  aNameBufferSize   The size of @p aNameBuffer.
+         *
+         * @retval kErrorNone    The service instance name was read successfully.
+         * @retval kErrorNoBufs  Either the label or name does not fit in the given buffers.
+         *
+         */
+        Error GetServiceName(char *   aLabelBuffer,
+                             uint8_t  aLabelBufferSize,
+                             char *   aNameBuffer,
+                             uint16_t aNameBufferSize) const;
+
+        /**
+         * This method gets info for a service instance from a DNS service instance resolution response.
+         *
+         * This method MUST only be used from `ServiceCallback`.
+         *
+         * - If no matching SRV record is found, `kErrorNotFound` is returned.
+         * - If a matching SRV record is found, @p aServiceInfo is updated and `kErrorNone` is returned.
+         * - If no matching TXT record is found, `mTxtDataSize` in @p aServiceInfo is set to zero.
+         * - If no matching AAAA record is found, `mHostAddress is set to all zero or unspecified address.
+         * - If there are multiple AAAA records for the host name, `mHostAddress` is set to the first one. The other
+         *   addresses can be retrieved using `GetHostAddress()` method.
+         *
+         * @param[out] aServiceInfo       A `ServiceInfo` to output the service instance information
+         *
+         * @retval kErrorNone         The service instance info was read. @p aServiceInfo is updated.
+         * @retval kErrorNotFound     Could not find a matching SRV record.
+         * @retval kErrorNoBufs       The host name and/or TXT data could not fit in the given buffers.
+         * @retval kErrorParse        Could not parse the records in the @p aResponse.
+         *
+         */
+        Error GetServiceInfo(ServiceInfo &aServiceInfo) const;
+
+        /**
+         * This method gets the host IPv6 address from a DNS service instance resolution response.
+         *
+         * This method MUST only be used from `ServiceCallback`.
+         *
+         * The response can include zero or more IPv6 address records. @p aIndex can be used to iterate through the
+         * list of addresses. Index zero gets the first address and so on. When we reach end of the list, this method
+         * returns `kErrorNotFound`.
+         *
+         * @param[in]  aHostName     The host name to get the address (MUST NOT be `nullptr`).
+         * @param[in]  aIndex        The address record index to retrieve.
+         * @param[out] aAddress      A reference to an IPv6 address to output the address.
+         * @param[out] aTtl          A reference to a `uint32_t` to output TTL for the address.
+         *
+         * @retval kErrorNone       The address was read successfully.
+         * @retval kErrorNotFound   No address record for @p aHostname at @p aIndex.
+         * @retval kErrorParse      Could not parse the records.
+         *
+         */
+        Error GetHostAddress(const char *aHostName, uint16_t aIndex, Ip6::Address &aAddress, uint32_t &aTtl) const;
+    };
+
+#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
 
     /**
      * This constructor initializes the object.
@@ -115,93 +496,191 @@
     /**
      * This method starts the DNS client.
      *
-     * @retval OT_ERROR_NONE     Successfully started the DNS client.
-     * @retval OT_ERROR_ALREADY  The socket is already open.
+     * @retval kErrorNone     Successfully started the DNS client.
+     * @retval kErrorAlready  The socket is already open.
      *
      */
-    otError Start(void);
+    Error Start(void);
 
     /**
      * This method stops the DNS client.
      *
-     * @retval OT_ERROR_NONE  Successfully stopped the DNS client.
-     *
      */
-    otError Stop(void);
+    void Stop(void);
 
     /**
-     * This method sends a DNS query.
+     * This method gets the current default query config being used by DNS client.
      *
-     * @param[in]  aQuery    A pointer to specify DNS query parameters.
-     * @param[in]  aHandler  A function pointer that shall be called on response reception or time-out.
-     * @param[in]  aContext  A pointer to arbitrary context information.
-     *
-     * @retval OT_ERROR_NONE          Successfully sent DNS query.
-     * @retval OT_ERROR_NO_BUFS       Failed to allocate retransmission data.
-     * @retval OT_ERROR_INVALID_ARGS  Invalid arguments supplied.
+     * @returns The current default query config.
      *
      */
-    otError Query(const QueryInfo &aQuery, ResponseHandler aHandler, void *aContext);
+    const QueryConfig &GetDefaultConfig(void) const { return mDefaultConfig; }
+
+    /**
+     * This method sets the default query config.
+     *
+     * @param[in] aQueryConfig   The new default query config.
+     *
+     */
+    void SetDefaultConfig(const QueryConfig &aQueryConfig);
+
+    /**
+     * This method resets the default config to the config used when the OpenThread stack starts.
+     *
+     * When OpenThread stack starts, the default DNS query config is determined from a set of OT config options such as
+     * `OPENTHREAD_CONFIG_DNS_CLIENT_DEFAULT_SERVER_IP6_ADDRESS`, `_DEFAULT_SERVER_PORT`, or `_DEFAULT_RESPONSE_TIMEOUT`
+     * etc. (see `config/dns_clinet.h` for all related config options).
+     *
+     */
+    void ResetDefaultConfig(void);
+
+    /**
+     * This method sends an address resolution DNS query for AAAA (IPv6) record for a given host name.
+     *
+     * The @p aConfig can be nullptr. In this case the default config (from `GetDefaultConfig()`) will be used as
+     * the config for this query. In a non-nullptr @p aConfig, some of the fields can be left unspecified (value zero).
+     * The unspecified fields are then replaced by the values from the default config.
+     *
+     * @param[in]  aHostName        The host name for which to query the address (MUST NOT be `nullptr`).
+     * @param[in]  aCallback        A callback function pointer to report the result of query.
+     * @param[in]  aContext         A pointer to arbitrary context information passed to @p aCallback.
+     * @param[in]  aConfig          The config to use for this query.
+     *
+     * @retval kErrorNone           Successfully sent DNS query.
+     * @retval kErrorNoBufs         Failed to allocate retransmission data.
+     * @retval kErrorInvalidArgs    The host name is not valid format.
+     * @retval kErrorInvalidState   Cannot send query since Thread interface is not up.
+     *
+     */
+    Error ResolveAddress(const char *       aHostName,
+                         AddressCallback    aCallback,
+                         void *             aContext,
+                         const QueryConfig *aConfig = nullptr);
+
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+
+    /**
+     * This method sends a browse (service instance enumeration) DNS query for a given service name.
+     *
+     * The @p aConfig can be nullptr. In this case the default config (from `GetDefaultConfig()`) will be used as
+     * the config for this query. In a non-nullptr @p aConfig, some of the fields can be left unspecified (value zero).
+     * The unspecified fields are then replaced by the values from the default config.
+     *
+     * @param[in]  aServiceName     The service name to query for (MUST NOT be `nullptr`).
+     * @param[in]  aCallback        The callback to report the response or errors (such as time-out).
+     * @param[in]  aContext         A pointer to arbitrary context information.
+     * @param[in]  aConfig          The config to use for this query.
+     *
+     * @retval kErrorNone       Query sent successfully. @p aCallback will be invoked to report the status.
+     * @retval kErrorNoBufs     Insufficient buffer to prepare and send query.
+     *
+     */
+    Error Browse(const char *       aServiceName,
+                 BrowseCallback     aCallback,
+                 void *             aContext,
+                 const QueryConfig *aConfig = nullptr);
+
+    /**
+     * This method sends a DNS service instance resolution query for a given service instance.
+     *
+     * The @p aConfig can be nullptr. In this case the default config (from `GetDefaultConfig()`) will be used as
+     * the config for this query. In a non-nullptr @p aConfig, some of the fields can be left unspecified (value zero).
+     * The unspecified fields are then replaced by the values from the default config.
+     *
+     * @param[in]  aServerSockAddr    The server socket address.
+     * @param[in]  aInstanceLabel     The service instance label.
+     * @param[in]  aServiceName       The service name (together with @p aInstanceLabel form full instance name).
+     * @param[in]  aCallback          A function pointer that shall be called on response reception or time-out.
+     * @param[in]  aContext           A pointer to arbitrary context information.
+     * @param[in]  aConfig            The config to use for this query.
+     *
+     * @retval kErrorNone         Query sent successfully. @p aCallback will be invoked to report the status.
+     * @retval kErrorNoBufs       Insufficient buffer to prepare and send query.
+     * @retval kErrorInvalidArgs  @p aInstanceLabel is `nullptr`.
+     *
+     */
+    Error ResolveService(const char *         aInstanceLabel,
+                         const char *         aServiceName,
+                         otDnsServiceCallback aCallback,
+                         void *               aContext,
+                         const QueryConfig *  aConfig = nullptr);
+
+#endif // OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
 
 private:
-    /**
-     * Retransmission parameters.
-     *
-     */
-    enum
+    enum QueryType : uint8_t
     {
-        kResponseTimeout = OPENTHREAD_CONFIG_DNS_RESPONSE_TIMEOUT,
-        kMaxRetransmit   = OPENTHREAD_CONFIG_DNS_MAX_RETRANSMIT,
+        kAddressQuery, // Address resolution.
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+        kBrowseQuery,  // Browse (service instance enumeration).
+        kServiceQuery, // Service instance resolution.
+#endif
     };
 
-    enum
+    union Callback
     {
-        kBufSize = 16
+        AddressCallback mAddressCallback;
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+        BrowseCallback  mBrowseCallback;
+        ServiceCallback mServiceCallback;
+#endif
     };
 
-    struct QueryMetadata
-    {
-        otError AppendTo(Message &aMessage) const { return aMessage.Append(*this); }
-        void    ReadFrom(const Message &aMessage);
-        void    UpdateIn(Message &aMessage) const;
+    typedef MessageQueue QueryList; // List of queries.
 
-        const char *    mHostname;
-        ResponseHandler mResponseHandler;
-        void *          mResponseContext;
-        TimeMilli       mTransmissionTime;
-        Ip6::Address    mSourceAddress;
-        Ip6::Address    mDestinationAddress;
-        uint16_t        mDestinationPort;
-        uint8_t         mRetransmissionCount;
+    struct QueryInfo : public Clearable<QueryInfo> // Query related Info
+    {
+        void ReadFrom(const Query &aQuery) { IgnoreError(aQuery.Read(0, *this)); }
+
+        QueryType   mQueryType;
+        uint16_t    mMessageId;
+        Callback    mCallback;
+        void *      mCallbackContext;
+        TimeMilli   mRetransmissionTime;
+        QueryConfig mConfig;
+        uint8_t     mTransmissionCount;
+        // Followed by the name (service, host, instance) encoded as a `Dns::Name`.
     };
 
-    Message *NewMessage(const Header &aHeader);
-    Message *CopyAndEnqueueMessage(const Message &aMessage, const QueryMetadata &aQueryMetadata);
-    void     DequeueMessage(Message &aMessage);
-    otError  SendMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
-    void     SendCopy(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    enum : uint16_t
+    {
+        kNameOffsetInQuery = sizeof(QueryInfo),
+    };
 
-    otError GenerateUniqueRandomId(uint16_t &aRandomId);
+    Error       StartQuery(QueryInfo &        aInfo,
+                           const QueryConfig *aConfig,
+                           const char *       aLabel,
+                           const char *       aName,
+                           void *             aContext);
+    Error       AllocateQuery(const QueryInfo &aInfo, const char *aLabel, const char *aName, Query *&aQuery);
+    void        FreeQuery(Query &aQuery);
+    void        UpdateQuery(Query &aQuery, const QueryInfo &aInfo) { aQuery.Write(0, aInfo); }
+    void        SendQuery(Query &aQuery);
+    void        SendQuery(Query &aQuery, QueryInfo &aInfo, bool aUpdateTimer);
+    void        FinalizeQuery(Query &aQuery, Error aError);
+    void        FinalizeQuery(Response &Response, QueryType aType, Error aError);
+    static void GetCallback(const Query &aQuery, Callback &aCallback, void *&aContext);
+    Error       AppendNameFromQuery(const Query &aQuery, Message &aMessage);
+    Query *     FindQueryById(uint16_t aMessageId);
+    static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMsgInfo);
+    void        ProcessResponse(const Message &aMessage);
+    Error       ParseResponse(Response &aResponse, QueryType &aType, Error &aResponseError);
+    static void HandleTimer(Timer &aTimer);
+    void        HandleTimer(void);
 
-    otError CompareQuestions(Message &aMessageResponse, Message &aMessageQuery, uint16_t &aOffset);
+    static const uint8_t   kQuestionCount[];
+    static const uint16_t *kQuestionRecordTypes[];
 
-    Message *FindQueryById(uint16_t aMessageId);
-    void     FinalizeDnsTransaction(Message &            aQuery,
-                                    const QueryMetadata &aQueryMetadata,
-                                    const Ip6::Address * aAddress,
-                                    uint32_t             aTtl,
-                                    otError              aResult);
-
-    static void HandleRetransmissionTimer(Timer &aTimer);
-    void        HandleRetransmissionTimer(void);
-
-    static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
-    void        HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    static const uint16_t kAddressQueryRecordTypes[];
+#if OPENTHREAD_CONFIG_DNS_CLIENT_SERVICE_DISCOVERY_ENABLE
+    static const uint16_t kBrowseQueryRecordTypes[];
+    static const uint16_t kServiceQueryRecordTypes[];
+#endif
 
     Ip6::Udp::Socket mSocket;
-
-    MessageQueue mPendingQueries;
-    TimerMilli   mRetransmissionTimer;
+    QueryList        mQueries;
+    TimerMilli       mTimer;
+    QueryConfig      mDefaultConfig;
 };
 
 } // namespace Dns
diff --git a/src/core/net/dns_headers.cpp b/src/core/net/dns_headers.cpp
deleted file mode 100644
index 21ff0db..0000000
--- a/src/core/net/dns_headers.cpp
+++ /dev/null
@@ -1,335 +0,0 @@
-/*
- *  Copyright (c) 2020, The OpenThread Authors.
- *  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
- *   This file implements generating and processing of DNS headers and helper functions/methods.
- */
-
-#include "dns_headers.hpp"
-
-#include "common/code_utils.hpp"
-#include "common/debug.hpp"
-#include "common/string.hpp"
-
-namespace ot {
-namespace Dns {
-
-using ot::Encoding::BigEndian::HostSwap16;
-
-otError Name::AppendLabel(const char *aLabel, Message &aMessage)
-{
-    return AppendLabel(aLabel, static_cast<uint8_t>(StringLength(aLabel, kMaxLabelLength + 1)), aMessage);
-}
-
-otError Name::AppendLabel(const char *aLabel, uint8_t aLabelLength, Message &aMessage)
-{
-    otError error = OT_ERROR_NONE;
-
-    VerifyOrExit((0 < aLabelLength) && (aLabelLength <= kMaxLabelLength), error = OT_ERROR_INVALID_ARGS);
-
-    SuccessOrExit(error = aMessage.Append(aLabelLength));
-    error = aMessage.AppendBytes(aLabel, aLabelLength);
-
-exit:
-    return error;
-}
-
-otError Name::AppendMultipleLabels(const char *aLabels, Message &aMessage)
-{
-    otError  error           = OT_ERROR_NONE;
-    uint16_t index           = 0;
-    uint16_t labelStartIndex = 0;
-    char     ch;
-
-    VerifyOrExit(aLabels != nullptr);
-
-    do
-    {
-        VerifyOrExit(index < kMaxLength, error = OT_ERROR_INVALID_ARGS);
-
-        ch = aLabels[index];
-
-        if ((ch == kNullChar) || (ch == kLabelSeperatorChar))
-        {
-            uint8_t labelLength = static_cast<uint8_t>(index - labelStartIndex);
-
-            if (labelLength == 0)
-            {
-                // Empty label (e.g., consecutive dots) is invalid, but we
-                // allow for two cases: (1) where `aLabels` ends with a dot
-                // (`labelLength` is zero but we are at end of `aLabels` string
-                // and `ch` is null char. (2) if `aLabels` is just "." (we
-                // see a dot at index 0, and index 1 is null char).
-
-                error = ((ch == kNullChar) || ((index == 0) && (aLabels[1] == kNullChar))) ? OT_ERROR_NONE
-                                                                                           : OT_ERROR_INVALID_ARGS;
-                ExitNow();
-            }
-
-            SuccessOrExit(error = AppendLabel(&aLabels[labelStartIndex], labelLength, aMessage));
-
-            labelStartIndex = index + 1;
-        }
-
-        index++;
-
-    } while (ch != kNullChar);
-
-exit:
-    return error;
-}
-
-otError Name::AppendTerminator(Message &aMessage)
-{
-    uint8_t terminator = 0;
-
-    return aMessage.Append(terminator);
-}
-
-otError Name::AppendPointerLabel(uint16_t aOffset, Message &aMessage)
-{
-    // A pointer label takes the form of a two byte sequence as a
-    // `uint16_t` value. The first two bits are ones. This allows a
-    // pointer to be distinguished from a text label, since the text
-    // label must begin with two zero bits (note that labels are
-    // restricted to 63 octets or less). The next 14-bits specify
-    // an offset value relative to start of DNS header.
-
-    uint16_t value;
-
-    OT_ASSERT(aOffset < kPointerLabelTypeUint16);
-
-    value = HostSwap16(aOffset | kPointerLabelTypeUint16);
-
-    return aMessage.Append(value);
-}
-
-otError Name::AppendName(const char *aName, Message &aMessage)
-{
-    otError error;
-
-    SuccessOrExit(error = AppendMultipleLabels(aName, aMessage));
-    error = AppendTerminator(aMessage);
-
-exit:
-    return error;
-}
-
-otError Name::ParseName(const Message &aMessage, uint16_t &aOffset)
-{
-    otError       error;
-    LabelIterator iterator(aMessage, aOffset);
-
-    while (true)
-    {
-        error = iterator.GetNextLabel();
-
-        VerifyOrExit((error == OT_ERROR_NONE) || (error == OT_ERROR_NOT_FOUND));
-
-        if (iterator.IsEndOffsetSet())
-        {
-            aOffset = iterator.mNameEndOffset;
-            ExitNow(error = OT_ERROR_NONE);
-        }
-    }
-
-exit:
-    return error;
-}
-
-otError Name::ReadLabel(const Message &aMessage,
-                        uint16_t &     aOffset,
-                        uint16_t       aHeaderOffset,
-                        char *         aLabelBuffer,
-                        uint8_t &      aLabelLength)
-{
-    otError       error;
-    LabelIterator iterator(aMessage, aOffset, aHeaderOffset);
-
-    SuccessOrExit(error = iterator.GetNextLabel());
-    SuccessOrExit(error = iterator.ReadLabel(aLabelBuffer, aLabelLength, /* aAllowDotCharInLabel */ true));
-    aOffset = iterator.mNextLabelOffset;
-
-exit:
-    return error;
-}
-
-otError Name::ReadName(const Message &aMessage,
-                       uint16_t &     aOffset,
-                       uint16_t       aHeaderOffset,
-                       char *         aNameBuffer,
-                       uint16_t       aNameBufferSize)
-{
-    otError       error;
-    LabelIterator iterator(aMessage, aOffset, aHeaderOffset);
-    bool          firstLabel = true;
-    uint8_t       labelLength;
-
-    while (true)
-    {
-        error = iterator.GetNextLabel();
-
-        switch (error)
-        {
-        case OT_ERROR_NONE:
-
-            if (!firstLabel)
-            {
-                *aNameBuffer++ = kLabelSeperatorChar;
-                aNameBufferSize--;
-
-                // No need to check if we have reached end of the name buffer
-                // here since `iterator.ReadLabel()` would verify it.
-            }
-
-            labelLength = static_cast<uint8_t>(OT_MIN(kMaxLabelLength + 1, aNameBufferSize));
-            SuccessOrExit(error = iterator.ReadLabel(aNameBuffer, labelLength, /* aAllowDotCharInLabel */ false));
-            aNameBuffer += labelLength;
-            aNameBufferSize -= labelLength;
-            firstLabel = false;
-            break;
-
-        case OT_ERROR_NOT_FOUND:
-            // We reach the end of name successfully. Always add a terminating dot
-            // at the end.
-            *aNameBuffer++ = kLabelSeperatorChar;
-            aNameBufferSize--;
-            VerifyOrExit(aNameBufferSize >= sizeof(uint8_t), error = OT_ERROR_NO_BUFS);
-            *aNameBuffer = kNullChar;
-            aOffset      = iterator.mNameEndOffset;
-            error        = OT_ERROR_NONE;
-
-            // Fall through
-
-        default:
-            ExitNow();
-        }
-    }
-
-exit:
-    return error;
-}
-
-otError Name::LabelIterator::GetNextLabel(void)
-{
-    otError error;
-
-    while (true)
-    {
-        uint8_t labelLength;
-        uint8_t labelType;
-
-        SuccessOrExit(error = mMessage.Read(mNextLabelOffset, labelLength));
-
-        labelType = labelLength & kLabelTypeMask;
-
-        if (labelType == kTextLabelType)
-        {
-            if (labelLength == 0)
-            {
-                // Zero label length indicates end of a name.
-
-                if (!IsEndOffsetSet())
-                {
-                    mNameEndOffset = mNextLabelOffset + sizeof(uint8_t);
-                }
-
-                ExitNow(error = OT_ERROR_NOT_FOUND);
-            }
-
-            mLabelStartOffset = mNextLabelOffset + sizeof(uint8_t);
-            mLabelLength      = labelLength;
-            mNextLabelOffset  = mLabelStartOffset + labelLength;
-            ExitNow();
-        }
-        else if (labelType == kPointerLabelType)
-        {
-            // A pointer label takes the form of a two byte sequence as a
-            // `uint16_t` value. The first two bits are ones. The next 14 bits
-            // specify an offset value from the start of the DNS header.
-
-            uint16_t pointerValue;
-
-            SuccessOrExit(error = mMessage.Read(mNextLabelOffset, pointerValue));
-
-            if (!IsEndOffsetSet())
-            {
-                mNameEndOffset = mNextLabelOffset + sizeof(uint16_t);
-            }
-
-            mNextLabelOffset = mHeaderOffset + (HostSwap16(pointerValue) & kPointerLabelOffsetMask);
-
-            // Go back through the `while(true)` loop to get the next label.
-        }
-        else
-        {
-            ExitNow(error = OT_ERROR_PARSE);
-        }
-    }
-
-exit:
-    return error;
-}
-
-otError Name::LabelIterator::ReadLabel(char *aLabelBuffer, uint8_t &aLabelLength, bool aAllowDotCharInLabel) const
-{
-    otError error;
-
-    VerifyOrExit(mLabelLength < aLabelLength, error = OT_ERROR_NO_BUFS);
-
-    SuccessOrExit(error = mMessage.Read(mLabelStartOffset, aLabelBuffer, mLabelLength));
-    aLabelBuffer[mLabelLength] = kNullChar;
-    aLabelLength               = mLabelLength;
-
-    if (!aAllowDotCharInLabel)
-    {
-        VerifyOrExit(StringFind(aLabelBuffer, kLabelSeperatorChar) == nullptr, error = OT_ERROR_PARSE);
-    }
-
-exit:
-    return error;
-}
-
-void ResourceRecord::Init(uint16_t aType, uint16_t aClass, uint32_t aTtl)
-{
-    SetType(aType);
-    SetClass(aClass);
-    SetTtl(aTtl);
-    SetLength(0);
-}
-
-void ResourceRecordAaaa::Init(void)
-{
-    ResourceRecord::Init(kTypeAaaa);
-    SetLength(sizeof(mAddress));
-    mAddress.Clear();
-}
-
-} // namespace Dns
-} // namespace ot
diff --git a/src/core/net/dns_headers.hpp b/src/core/net/dns_headers.hpp
deleted file mode 100644
index 9d35d1f..0000000
--- a/src/core/net/dns_headers.hpp
+++ /dev/null
@@ -1,881 +0,0 @@
-/*
- *  Copyright (c) 2017, The OpenThread Authors.
- *  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
- *   This file includes definitions for generating and processing DNS headers.
- */
-
-#ifndef DNS_HEADER_HPP_
-#define DNS_HEADER_HPP_
-
-#include "openthread-core-config.h"
-
-#include "common/clearable.hpp"
-#include "common/encoding.hpp"
-#include "common/message.hpp"
-#include "net/ip6_address.hpp"
-
-namespace ot {
-
-/**
- * @namespace ot::Dns
- * @brief
- *   This namespace includes definitions for DNS.
- *
- */
-namespace Dns {
-
-using ot::Encoding::BigEndian::HostSwap16;
-using ot::Encoding::BigEndian::HostSwap32;
-
-/**
- * @addtogroup core-dns
- *
- * @brief
- *   This module includes definitions for DNS.
- *
- * @{
- *
- */
-
-/**
- * This class implements DNS header generation and parsing.
- *
- */
-OT_TOOL_PACKED_BEGIN
-class Header : public Clearable<Header>
-{
-public:
-    /**
-     * Default constructor for DNS Header.
-     *
-     */
-    Header(void) { Clear(); }
-
-    /**
-     * This method returns the Message ID.
-     *
-     * @returns The Message ID value.
-     *
-     */
-    uint16_t GetMessageId(void) const { return HostSwap16(mMessageId); }
-
-    /**
-     * This method sets the Message ID.
-     *
-     * @param[in]  aMessageId The Message ID value.
-     *
-     */
-    void SetMessageId(uint16_t aMessageId) { mMessageId = HostSwap16(aMessageId); }
-
-    /**
-     * Defines types of DNS message.
-     *
-     */
-    enum Type
-    {
-        kTypeQuery    = 0,
-        kTypeResponse = 1,
-    };
-
-    /**
-     * This method returns the type of the message.
-     *
-     * @returns The type of the message.
-     *
-     */
-    Type GetType(void) const { return static_cast<Type>((mFlags[0] & kQrFlagMask) >> kQrFlagOffset); }
-
-    /**
-     * This method sets the type of the message.
-     *
-     * @param[in]  aType The type of the message.
-     *
-     */
-    void SetType(Type aType)
-    {
-        mFlags[0] &= ~kQrFlagMask;
-        mFlags[0] |= static_cast<uint8_t>(aType) << kQrFlagOffset;
-    }
-
-    /**
-     * Defines types of query.
-     *
-     */
-    enum QueryType
-    {
-        kQueryTypeStandard = 0,
-        kQueryTypeInverse  = 1,
-        kQueryTypeStatus   = 2,
-        kQueryTypeNotify   = 4,
-        kQueryTypeUpdate   = 5
-    };
-
-    /**
-     * This method returns the type of the query.
-     *
-     * @returns The type of the query.
-     *
-     */
-    QueryType GetQueryType(void) const { return static_cast<QueryType>((mFlags[0] & kOpCodeMask) >> kOpCodeOffset); }
-
-    /**
-     * This method sets the type of the query.
-     *
-     * @param[in]  aType The type of the query.
-     *
-     */
-    void SetQueryType(QueryType aType)
-    {
-        mFlags[0] &= ~kOpCodeMask;
-        mFlags[0] |= static_cast<uint8_t>(aType) << kOpCodeOffset;
-    }
-
-    /**
-     * This method specifies in response message if the responding name server is an
-     * authority for the domain name in question section.
-     *
-     * @returns True if Authoritative Answer flag (AA) is set in the header, false otherwise.
-     *
-     */
-    bool IsAuthoritativeAnswerFlagSet(void) const { return (mFlags[0] & kAaFlagMask) == kAaFlagMask; }
-
-    /**
-     * This method clears the Authoritative Answer flag (AA) in the header.
-     *
-     */
-    void ClearAuthoritativeAnswerFlag(void) { mFlags[0] &= ~kAaFlagMask; }
-
-    /**
-     * This method sets the Authoritative Answer flag (AA) in the header.
-     *
-     */
-    void SetAuthoritativeAnswerFlag(void) { mFlags[0] |= kAaFlagMask; }
-
-    /**
-     * This method specifies if message is truncated.
-     *
-     * @returns True if Truncation flag (TC) is set in the header, false otherwise.
-     *
-     */
-    bool IsTruncationFlagSet(void) const { return (mFlags[0] & kTcFlagMask) == kTcFlagMask; }
-
-    /**
-     * This method clears the Truncation flag (TC) in the header.
-     *
-     */
-    void ClearTruncationFlag(void) { mFlags[0] &= ~kTcFlagMask; }
-
-    /**
-     * This method sets the Truncation flag (TC) in the header.
-     *
-     */
-    void SetTruncationFlag(void) { mFlags[0] |= kTcFlagMask; }
-
-    /**
-     * This method specifies if resolver wants to direct the name server to pursue
-     * the query recursively.
-     *
-     * @returns True if Recursion Desired flag (RD) is set in the header, false otherwise.
-     *
-     */
-    bool IsRecursionDesiredFlagSet(void) const { return (mFlags[0] & kRdFlagMask) == kRdFlagMask; }
-
-    /**
-     * This method clears the Recursion Desired flag (RD) in the header.
-     *
-     */
-    void ClearRecursionDesiredFlag(void) { mFlags[0] &= ~kRdFlagMask; }
-
-    /**
-     * This method sets the Recursion Desired flag (RD) in the header.
-     *
-     */
-    void SetRecursionDesiredFlag(void) { mFlags[0] |= kRdFlagMask; }
-
-    /**
-     * This method denotes whether recursive query support is available in the name server.
-     *
-     * @returns True if Recursion Available flag (RA) is set in the header, false otherwise.
-     */
-    bool IsRecursionAvailableFlagSet(void) const { return (mFlags[1] & kRaFlagMask) == kRaFlagMask; }
-
-    /**
-     * This method clears the Recursion Available flag (RA) in the header.
-     *
-     */
-    void ClearRecursionAvailableFlag(void) { mFlags[1] &= ~kRaFlagMask; }
-
-    /**
-     * This method sets the Recursion Available flag (RA) in the header.
-     *
-     */
-    void SetRecursionAvailableFlag(void) { mFlags[1] |= kRaFlagMask; }
-
-    /**
-     * Defines response codes.
-     *
-     */
-    enum Response
-    {
-        kResponseSuccess        = 0,
-        kResponseFormatError    = 1,
-        kResponseServerFailure  = 2,
-        kResponseNameError      = 3,
-        kResponseNotImplemented = 4,
-        kResponseRefused        = 5,
-        kResponseNotAuth        = 9,
-        kResponseNotZone        = 10,
-        kResponseBadName        = 20,
-        kResponseBadAlg         = 21,
-        kResponseBadTruncation  = 22,
-    };
-
-    /**
-     * This method returns the response code.
-     *
-     * @returns The response code from the header.
-     *
-     */
-    Response GetResponseCode(void) const { return static_cast<Response>((mFlags[1] & kRCodeMask) >> kRCodeOffset); }
-
-    /**
-     * This method sets the response code.
-     *
-     * @param[in]  aResponse The type of the response.
-     *
-     */
-    void SetResponseCode(Response aResponse)
-    {
-        mFlags[1] &= ~kRCodeMask;
-        mFlags[1] |= static_cast<uint8_t>(aResponse) << kRCodeOffset;
-    }
-
-    /**
-     * This method returns the number of entries in question section.
-     *
-     * @returns The number of entries in question section.
-     *
-     */
-    uint16_t GetQuestionCount(void) const { return HostSwap16(mQdCount); }
-
-    /**
-     * This method sets the number of entries in question section.
-     *
-     * @param[in]  aCount The number of entries in question section.
-     *
-     */
-    void SetQuestionCount(uint16_t aCount) { mQdCount = HostSwap16(aCount); }
-
-    /**
-     * This method returns the number of entries in answer section.
-     *
-     * @returns The number of entries in answer section.
-     *
-     */
-    uint16_t GetAnswerCount(void) const { return HostSwap16(mAnCount); }
-
-    /**
-     * This method sets the number of entries in answer section.
-     *
-     * @param[in]  aCount The number of entries in answer section.
-     *
-     */
-    void SetAnswerCount(uint16_t aCount) { mAnCount = HostSwap16(aCount); }
-
-    /**
-     * This method returns the number of entries in authority records section.
-     *
-     * @returns The number of entries in authority records section.
-     *
-     */
-    uint16_t GetAuthorityRecordsCount(void) const { return HostSwap16(mNsCount); }
-
-    /**
-     * This method sets the number of entries in authority records section.
-     *
-     * @param[in]  aCount The number of entries in authority records section.
-     *
-     */
-    void SetAuthorityRecordsCount(uint16_t aCount) { mNsCount = HostSwap16(aCount); }
-
-    /**
-     * This method returns the number of entries in additional records section.
-     *
-     * @returns The number of entries in additional records section.
-     *
-     */
-    uint16_t GetAdditionalRecordsCount(void) const { return HostSwap16(mArCount); }
-
-    /**
-     * This method sets the number of entries in additional records section.
-     *
-     * @param[in]  aCount The number of entries in additional records section.
-     *
-     */
-    void SetAdditionalRecordsCount(uint16_t aCount) { mArCount = HostSwap16(aCount); }
-
-private:
-    /**
-     * Protocol Constants (RFC 1035).
-     *
-     */
-    enum
-    {
-        kQrFlagOffset = 7,                     // QR Flag offset.
-        kQrFlagMask   = 0x01 << kQrFlagOffset, // QR Flag mask.
-        kOpCodeOffset = 3,                     // OpCode field offset.
-        kOpCodeMask   = 0x0f << kOpCodeOffset, // OpCode field mask.
-        kAaFlagOffset = 2,                     // AA Flag offset.
-        kAaFlagMask   = 0x01 << kAaFlagOffset, // AA Flag mask.
-        kTcFlagOffset = 1,                     // TC Flag offset.
-        kTcFlagMask   = 0x01 << kTcFlagOffset, // TC Flag mask.
-        kRdFlagOffset = 0,                     // RD Flag offset.
-        kRdFlagMask   = 0x01 << kRdFlagOffset, // RD Flag mask.
-
-        kRaFlagOffset = 7,                     // RA Flag offset.
-        kRaFlagMask   = 0x01 << kRaFlagOffset, // RA Flag mask.
-        kRCodeOffset  = 0,                     // RCODE field offset.
-        kRCodeMask    = 0x0f << kRCodeOffset,  // RCODE field mask.
-    };
-
-    uint16_t mMessageId; // Message identifier for requester to match up replies to outstanding queries.
-    uint8_t  mFlags[2];  // DNS header flags.
-    uint16_t mQdCount;   // Number of entries in the question section.
-    uint16_t mAnCount;   // Number of entries in the answer section.
-    uint16_t mNsCount;   // Number of entries in the authority records section.
-    uint16_t mArCount;   // Number of entries in the additional records section.
-
-} OT_TOOL_PACKED_END;
-
-/**
- * This class implement helper methods for encoding/decoding of DNS Names.
- *
- */
-class Name
-{
-public:
-    enum : uint8_t
-    {
-        kMaxLabelLength = 63,  ///< Max number of characters in a label.
-        kMaxLength      = 255, ///< Max number of characters in a name.
-    };
-
-    /**
-     * This static method encodes and appends a single name label to a message.
-     *
-     * The @p aLabel is assumed to contain a single name label as a C string (null-terminated). Unlike
-     * `AppendMultipleLabels()` which parses the label string and treats it as sequence of multiple (dot-separated)
-     * labels, this method always appends @p aLabel as a single whole label. This allows the label string to even
-     * contain dot '.' character, which, for example, is useful for "Service Instance Names" where <Instance> portion
-     * is a user-friendly name and can contain dot characters.
-     *
-     * @param[in] aLabel              The label string to append. MUST NOT be nullptr.
-     * @param[in] aMessage            The message to append to.
-     *
-     * @retval OT_ERROR_NONE          Successfully encoded and appended the name label to @p aMessage.
-     * @retval OT_ERROR_INVALID_ARGS  @p aLabel is not valid (e.g., label length is not within valid range).
-     * @retval OT_ERROR_NO_BUFS       Insufficient available buffers to grow the message.
-     *
-     */
-    static otError AppendLabel(const char *aLabel, Message &aMessage);
-
-    /**
-     * This static method encodes and appends a sequence of name labels to a given message.
-     *
-     * The @p aLabels must follow  "<label1>.<label2>.<label3>", i.e., a sequence of labels separated by dot '.' char.
-     * E.g., "_http._tcp", "_http._tcp." (same as previous one), "host-1.test".
-     *
-     * This method validates that the @p aLabels is a valid name format, i.e., no empty label, and labels are
-     * `kMaxLabelLength` (63) characters or less.
-     *
-     * @note This method NEVER adds a label terminator (empty label) to the message, even in the case where @p aLabels
-     * ends with a dot character, e.g., "host-1.test." is treated same as "host-1.test".
-     *
-     * @param[in]  aLabels            A name label string. Can be nullptr (then treated as "").
-     * @param[in]  aMessage           The message to which to append the encoded name.
-     *
-     * @retval OT_ERROR_NONE          Successfully encoded and appended the name label(s) to @p aMessage.
-     * @retval OT_ERROR_INVALID_ARGS  Name label @p aLabels is not valid.
-     * @retval OT_ERROR_NO_BUFS       Insufficient available buffers to grow the message.
-     *
-     */
-    static otError AppendMultipleLabels(const char *aLabels, Message &aMessage);
-
-    /**
-     * This static method appends a name label terminator to a message.
-     *
-     * An encoded name is terminated by an empty label (a zero byte).
-     *
-     * @param[in] aMessage            The message to append to.
-     *
-     * @retval OT_ERROR_NONE          Successfully encoded and appended the terminator label to @p aMessage.
-     * @retval OT_ERROR_NO_BUFS       Insufficient available buffers to grow the message.
-     *
-     */
-    static otError AppendTerminator(Message &aMessage);
-
-    /**
-     * This static method appends a pointer type name label to a message.
-     *
-     * Pointer label is used for name compression. It allows an entire name or a list of labels at the end of an
-     * encoded name to be replaced with a pointer to a prior occurrence of the same name within the message.
-     *
-     * @param[in] aOffset             The offset from the start of DNS header to use for pointer value.
-     * @param[in] aMessage            The message to append to.
-     *
-     * @retval OT_ERROR_NONE          Successfully encoded and appended the pointer label to @p aMessage.
-     * @retval OT_ERROR_NO_BUFS       Insufficient available buffers to grow the message.
-     *
-     */
-    static otError AppendPointerLabel(uint16_t aOffset, Message &aMessage);
-
-    /**
-     * This static method encodes and appends a full name to a message.
-     *
-     * The @p aName must follow  "<label1>.<label2>.<label3>", i.e., a sequence of labels separated by dot '.' char.
-     * E.g., "example.com", "example.com." (same as previous one), local.", "default.service.arpa", "." or "" (root).
-     *
-     * This method validates that the @p aName is a valid name format, i.e. no empty labels, and labels are
-     * `kMaxLabelLength` (63) characters or less, and the name is `kMaxLength` (255) characters or less.
-     *
-     * @param[in]  aName              A name string. Can be nullptr (then treated as "." or root).
-     * @param[in]  aMessage           The message to append to.
-     *
-     * @retval OT_ERROR_NONE          Successfully encoded and appended the name to @p aMessage.
-     * @retval OT_ERROR_INVALID_ARGS  Name @p aName is not valid.
-     * @retval OT_ERROR_NO_BUFS       Insufficient available buffers to grow the message.
-     *
-     */
-    static otError AppendName(const char *aName, Message &aMessage);
-
-    /**
-     * This static method parses and skips over a full name in a message.
-     *
-     * @param[in]    aMessage         The message to parse the name from.
-     * @param[inout] aOffset          On input the offset in @p aMessage pointing to the start of the name field.
-     *                                On exit (when parsed successfully), @p aOffset is updated to point to the byte
-     *                                after the end of name field.
-     *
-     * @retval OT_ERROR_NONE          Successfully parsed and skipped over name, @p Offset is updated.
-     * @retval OT_ERROR_PARSE         Name could not be parsed (invalid format).
-     *
-     */
-    static otError ParseName(const Message &aMessage, uint16_t &aOffset);
-
-    /**
-     * This static method reads a name label from a message.
-     *
-     * This method can be used to read labels one by one in a name. After a successful label read, @p aOffset is
-     * updated to point to the start of the next label. When we reach the end of the name, OT_ERROR_NOT_FOUND is
-     * returned. This method handles compressed names which use pointer labels. So as the labels in a name are read,
-     * the @p aOffset may jump back in the message and at the end the @p aOffset does not necessarily point to the end
-     * of the original name field.
-     *
-     * Unlike `ReadName()` which requires and verifies that the read label to contain no dot '.' character, this method
-     * allows the read label to include any character.
-     *
-     * @param[in]    aMessage         The message to read the label from.
-     * @param[inout] aOffset          On input, the offset in @p aMessage pointing to the start of the label to read.
-     *                                On exit, when successfully read, @p aOffset is updated to point to the start of
-     *                                the next label.
-     * @param[in]    aHeaderOffset    The offset in @p aMessage to the start of the DNS header.
-     * @param[out]   aLabelBuffer     A pointer to a char array to output the read label as a null-terminated C string.
-     * @param[inout] aLabelLength     On input, the maximum number chars in @p aLabelBuffer array.
-     *                                On output, when label is successfully read, @aLabelLength is updated to return
-     *                                the label's length (number of chars in the label string, excluding the null char).
-     *
-     * @retval OT_ERROR_NONE          Successfully read the label and updated @p aLabelBuffer, @p aLabelLength, and
-     *                                @p aOffset.
-     * @retval OT_ERROR_NOT_FOUND     Reached the end of name and no more label to read.
-     * @retval OT_ERROR_PARSE         Name could not be parsed (invalid format).
-     * @retval OT_ERROR_NO_BUFS       Label could not fit in @p aLabelLength chars.
-     *
-     */
-    static otError ReadLabel(const Message &aMessage,
-                             uint16_t &     aOffset,
-                             uint16_t       aHeaderOffset,
-                             char *         aLabelBuffer,
-                             uint8_t &      aLabelLength);
-
-    /**
-     * This static method reads a full name from a message.
-     *
-     * On successful read, the read name follows  "<label1>.<label2>.<label3>.", i.e., a sequence of labels separated by
-     * dot '.' character. The read name will ALWAYS end with a dot.
-     *
-     * This method verifies that the read labels in message do not contain any dot character, otherwise it returns
-     * `OT_ERROR_PARSE`).
-     *
-     * @param[in]    aMessage         The message to read the name from.
-     * @param[inout] aOffset          On input, the offset in @p aMessage pointing to the start of the name field.
-     *                                On exit (when parsed successfully), @p aOffset is updated to point to the byte
-     *                                after the end of name field.
-     * @param[in]    aHeaderOffset    The offset in @p aMessage to the start of the DNS header.
-     * @param[out]   aNameBuffer      A pointer to a char array to output the read name as a null-terminated C string.
-     * @param[inout] aNameBufferSize  The maximum number chars in @p aNameBuffer array.
-     *
-     * @retval OT_ERROR_NONE          Successfully read the name, @p aNameBuffer and @p Offset are updated.
-     * @retval OT_ERROR_PARSE         Name could not be parsed (invalid format).
-     * @retval OT_ERROR_NO_BUFS       Name could not fit in @p aNameBufferSize chars.
-     *
-     */
-    static otError ReadName(const Message &aMessage,
-                            uint16_t &     aOffset,
-                            uint16_t       aHeaderOffset,
-                            char *         aNameBuffer,
-                            uint16_t       aNameBufferSize);
-
-private:
-    enum : char
-    {
-        kNullChar           = '\0',
-        kLabelSeperatorChar = '.',
-    };
-
-    enum : uint8_t
-    {
-        // The first 2 bits of the encoded label specifies label type.
-        //
-        // - Value 00 indicates normal text label (lower 6-bits indicates the label length).
-        // - Value 11 indicates pointer label type (lower 14-bits indicates the pointer offset).
-        // - Values 01,10 are reserved (RFC 6891 recommends to not use)
-
-        kLabelTypeMask    = 0xc0, // 0b1100_0000 (first two bits)
-        kTextLabelType    = 0x00, // Text label type (00)
-        kPointerLabelType = 0xc0, // Pointer label type - compressed name (11)
-    };
-
-    enum : uint16_t
-    {
-        kPointerLabelTypeUint16 = 0xc000, // Pointer label type as `uint16_t` mask (first 2 bits).
-        kPointerLabelOffsetMask = 0x3fff, // Mask to get the offset field in a pointer label (lower 14 bits).
-    };
-
-    struct LabelIterator
-    {
-        enum : uint16_t
-        {
-            kUnsetNameEndOffset = 0, // Special value indicating `mNameEndOffset` is not yet set.
-        };
-
-        LabelIterator(const Message &aMessage, uint16_t aLabelOffset, uint16_t aHeaderOffset = 0)
-            : mMessage(aMessage)
-            , mHeaderOffset(aHeaderOffset)
-            , mNextLabelOffset(aLabelOffset)
-            , mNameEndOffset(kUnsetNameEndOffset)
-        {
-        }
-
-        bool    IsEndOffsetSet(void) const { return (mNameEndOffset != kUnsetNameEndOffset); }
-        otError GetNextLabel(void);
-        otError ReadLabel(char *aLabelBuffer, uint8_t &aLabelLength, bool aAllowDotCharInLabel) const;
-
-        const Message &mMessage;          // Message to read labels from.
-        const uint16_t mHeaderOffset;     // Offset in `mMessage` to the start of DNS header.
-        uint16_t       mLabelStartOffset; // Offset in `mMessage` to the first char of current label text.
-        uint8_t        mLabelLength;      // Length of current label (number of chars).
-        uint16_t       mNextLabelOffset;  // Offset in `mMessage` to the start of the next label.
-        uint16_t       mNameEndOffset;    // Offset in `mMessage` to the byte after the end of domain name field.
-    };
-
-    Name(void) = default;
-
-    static otError AppendLabel(const char *aLabel, uint8_t aLabelLength, Message &aMessage);
-};
-
-/**
- * This class implements Resource Record body format (RR).
- *
- */
-OT_TOOL_PACKED_BEGIN
-class ResourceRecord
-{
-public:
-    /**
-     * Resource Record Types.
-     *
-     */
-    enum : uint16_t
-    {
-        kTypeA    = 1,  ///< Address record (IPv4).
-        kTypePtr  = 12, ///< PTR record.
-        kTypeTxt  = 16, ///< TXT record.
-        kTypeSrv  = 33, ///< SRV locator record.
-        kTypeAaaa = 28, ///< IPv6 address record.
-        kTypeKey  = 25, ///< Key record.
-        kTypeOpt  = 41, ///< Option record.
-    };
-
-    /**
-     * Resource Record Class Codes.
-     *
-     */
-    enum : uint16_t
-    {
-        kClassInternet = 1, ///< Class code Internet (IN).
-    };
-
-    /**
-     * This method initializes the resource record.
-     *
-     * @param[in] aType   The type of the resource record.
-     * @param[in] aClass  The class of the resource record (default is `kClassInternet`).
-     * @param[in] aTtl    The time to live field of the resource record (default is zero).
-     *
-     */
-    void Init(uint16_t aType, uint16_t aClass = kClassInternet, uint32_t aTtl = 0);
-
-    /**
-     * This method indicates whether the resources records matches a given type and class code.
-     *
-     * @param[in] aType   The resource record type to compare with.
-     * @param[in] aClass  The resource record class code to compare with (default is `kClassInternet`).
-     *
-     * @returns TRUE if the resources records matches @p aType and @p aClass, FALSE otherwise.
-     *
-     */
-    bool Matches(uint16_t aType, uint16_t aClass = kClassInternet)
-    {
-        return (mType == HostSwap16(aType)) && (mClass == HostSwap16(aClass));
-    }
-
-    /**
-     * This method returns the type of the resource record.
-     *
-     * @returns The type of the resource record.
-     *
-     */
-    uint16_t GetType(void) const { return HostSwap16(mType); }
-
-    /**
-     * This method sets the type of the resource record.
-     *
-     * @param[in]  aType The type of the resource record.
-     *
-     */
-    void SetType(uint16_t aType) { mType = HostSwap16(aType); }
-
-    /**
-     * This method returns the class of the resource record.
-     *
-     * @returns The class of the resource record.
-     */
-    uint16_t GetClass(void) const { return HostSwap16(mClass); }
-
-    /**
-     * This method sets the class of the resource record.
-     *
-     * @param[in]  aClass The class of the resource record.
-     *
-     */
-    void SetClass(uint16_t aClass) { mClass = HostSwap16(aClass); }
-
-    /**
-     * This method returns the time to live field of the resource record.
-     *
-     * @returns The time to live field of the resource record.
-     *
-     */
-    uint32_t GetTtl(void) const { return HostSwap32(mTtl); }
-
-    /**
-     * This method sets the time to live field of the resource record.
-     *
-     * @param[in]  aTtl The time to live field of the resource record.
-     *
-     */
-    void SetTtl(uint32_t aTtl) { mTtl = HostSwap32(aTtl); }
-
-    /**
-     * This method returns the length of the resource record.
-     *
-     * @returns The length of the resource record.
-     */
-    uint16_t GetLength(void) const { return HostSwap16(mLength); }
-
-    /**
-     * This method sets the length of the resource record.
-     *
-     * @param[in]  aLength The length of the resource record.
-     *
-     */
-    void SetLength(uint16_t aLength) { mLength = HostSwap16(aLength); }
-
-    /**
-     * This method returns the size of (number of bytes) in resource record and its data RDATA section (excluding the
-     * name field).
-     *
-     * @returns Size (number of bytes) of resource record and its data section (excluding the name field)
-     *
-     */
-    uint32_t GetSize(void) const { return sizeof(ResourceRecord) + GetLength(); }
-
-private:
-    uint16_t mType;   // The type of the data in RDATA section.
-    uint16_t mClass;  // The class of the data in RDATA section.
-    uint32_t mTtl;    // Specifies the maximum time that the resource record may be cached.
-    uint16_t mLength; // The length of RDATA section in bytes.
-
-} OT_TOOL_PACKED_END;
-
-/**
- * This class implements Resource Record body format of AAAA type.
- *
- */
-OT_TOOL_PACKED_BEGIN
-class ResourceRecordAaaa : public ResourceRecord
-{
-public:
-    /**
-     * This method initializes the AAAA Resource Record.
-     *
-     */
-    void Init(void);
-
-    /**
-     * This method sets the IPv6 address of the resource record.
-     *
-     * @param[in]  aAddress The IPv6 address of the resource record.
-     *
-     */
-    void SetAddress(Ip6::Address &aAddress) { mAddress = aAddress; }
-
-    /**
-     * This method returns the reference to IPv6 address of the resource record.
-     *
-     * @returns The reference to IPv6 address of the resource record.
-     *
-     */
-    Ip6::Address &GetAddress(void) { return mAddress; }
-
-private:
-    Ip6::Address mAddress; // IPv6 Address of AAAA Resource Record.
-} OT_TOOL_PACKED_END;
-
-/**
- * This class implements Question format.
- *
- */
-OT_TOOL_PACKED_BEGIN
-class Question
-{
-public:
-    /**
-     * Constructor for Question.
-     *
-     */
-    Question(uint16_t aType, uint16_t aClass)
-    {
-        SetType(aType);
-        SetClass(aClass);
-    }
-
-    /**
-     * This method returns the type of the question.
-     *
-     * @returns The type of the question.
-     *
-     */
-    uint16_t GetType(void) const { return HostSwap16(mType); }
-
-    /**
-     * This method sets the type of the question.
-     *
-     * @param[in]  aType The type of the question.
-     *
-     */
-    void SetType(uint16_t aType) { mType = HostSwap16(aType); }
-
-    /**
-     * This method returns the class of the question.
-     *
-     * @returns The class of the question.
-     *
-     */
-    uint16_t GetClass(void) const { return HostSwap16(mClass); }
-
-    /**
-     * This method sets the class of the question.
-     *
-     * @param[in]  aClass The class of the question.
-     *
-     */
-    void SetClass(uint16_t aClass) { mClass = HostSwap16(aClass); }
-
-private:
-    uint16_t mType;  // The type of the data in question section.
-    uint16_t mClass; // The class of the data in question section.
-
-} OT_TOOL_PACKED_END;
-
-/**
- * This class implements Question format of AAAA type.
- *
- */
-class QuestionAaaa : public Question
-{
-public:
-    /**
-     * Default constructor for AAAA Question.
-     *
-     */
-    QuestionAaaa(void)
-        : Question(kType, kClass)
-    {
-    }
-
-    /**
-     * This method appends request data to the message.
-     *
-     * @param[in]  aMessage  A reference to the message.
-     *
-     * @retval OT_ERROR_NONE     Successfully appended the bytes.
-     * @retval OT_ERROR_NO_BUFS  Insufficient available buffers to grow the message.
-     *
-     */
-    otError AppendTo(Message &aMessage) const { return aMessage.Append(*this); }
-
-private:
-    enum
-    {
-        kType  = 0x1C, // AAAA Resource Record type.
-        kClass = 0x01, // The value of the Internet class.
-    };
-};
-
-/**
- * @}
- *
- */
-
-} // namespace Dns
-} // namespace ot
-
-#endif // DNS_HEADER_HPP_
diff --git a/src/core/net/dns_types.cpp b/src/core/net/dns_types.cpp
new file mode 100644
index 0000000..631051b
--- /dev/null
+++ b/src/core/net/dns_types.cpp
@@ -0,0 +1,1042 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file implements generating and processing of DNS headers and helper functions/methods.
+ */
+
+#include "dns_types.hpp"
+
+#include "common/code_utils.hpp"
+#include "common/debug.hpp"
+#include "common/logging.hpp"
+#include "common/random.hpp"
+#include "common/string.hpp"
+
+namespace ot {
+namespace Dns {
+
+using ot::Encoding::BigEndian::HostSwap16;
+
+Error Header::SetRandomMessageId(void)
+{
+    return Random::Crypto::FillBuffer(reinterpret_cast<uint8_t *>(&mMessageId), sizeof(mMessageId));
+}
+
+Error Header::ResponseCodeToError(Response aResponse)
+{
+    Error error = kErrorFailed;
+
+    switch (aResponse)
+    {
+    case kResponseSuccess:
+        error = kErrorNone;
+        break;
+
+    case kResponseFormatError:   // Server unable to interpret request due to format error.
+    case kResponseBadName:       // Bad name.
+    case kResponseBadTruncation: // Bad truncation.
+    case kResponseNotZone:       // A name is not in the zone.
+        error = kErrorParse;
+        break;
+
+    case kResponseServerFailure: // Server encountered an internal failure.
+        error = kErrorFailed;
+        break;
+
+    case kResponseNameError:       // Name that ought to exist, does not exists.
+    case kResponseRecordNotExists: // Some RRset that ought to exist, does not exist.
+        error = kErrorNotFound;
+        break;
+
+    case kResponseNotImplemented: // Server does not support the query type (OpCode).
+        error = kErrorNotImplemented;
+        break;
+
+    case kResponseBadAlg: // Bad algorithm.
+        error = kErrorNotCapable;
+        break;
+
+    case kResponseNameExists:   // Some name that ought not to exist, does exist.
+    case kResponseRecordExists: // Some RRset that ought not to exist, does exist.
+        error = kErrorDuplicated;
+        break;
+
+    case kResponseRefused: // Server refused to perform operation for policy or security reasons.
+    case kResponseNotAuth: // Service is not authoritative for zone.
+        error = kErrorSecurity;
+        break;
+
+    default:
+        break;
+    }
+
+    return error;
+}
+
+Error Name::AppendLabel(const char *aLabel, Message &aMessage)
+{
+    return AppendLabel(aLabel, static_cast<uint8_t>(StringLength(aLabel, kMaxLabelSize)), aMessage);
+}
+
+Error Name::AppendLabel(const char *aLabel, uint8_t aLength, Message &aMessage)
+{
+    Error error = kErrorNone;
+
+    VerifyOrExit((0 < aLength) && (aLength <= kMaxLabelLength), error = kErrorInvalidArgs);
+
+    SuccessOrExit(error = aMessage.Append(aLength));
+    error = aMessage.AppendBytes(aLabel, aLength);
+
+exit:
+    return error;
+}
+
+Error Name::AppendMultipleLabels(const char *aLabels, Message &aMessage)
+{
+    return AppendMultipleLabels(aLabels, kMaxNameLength, aMessage);
+}
+
+Error Name::AppendMultipleLabels(const char *aLabels, uint8_t aLength, Message &aMessage)
+{
+    Error    error           = kErrorNone;
+    uint16_t index           = 0;
+    uint16_t labelStartIndex = 0;
+    char     ch;
+
+    VerifyOrExit(aLabels != nullptr);
+
+    do
+    {
+        ch = index < aLength ? aLabels[index] : static_cast<char>(kNullChar);
+
+        if ((ch == kNullChar) || (ch == kLabelSeperatorChar))
+        {
+            uint8_t labelLength = static_cast<uint8_t>(index - labelStartIndex);
+
+            if (labelLength == 0)
+            {
+                // Empty label (e.g., consecutive dots) is invalid, but we
+                // allow for two cases: (1) where `aLabels` ends with a dot
+                // (`labelLength` is zero but we are at end of `aLabels` string
+                // and `ch` is null char. (2) if `aLabels` is just "." (we
+                // see a dot at index 0, and index 1 is null char).
+
+                error =
+                    ((ch == kNullChar) || ((index == 0) && (aLabels[1] == kNullChar))) ? kErrorNone : kErrorInvalidArgs;
+                ExitNow();
+            }
+
+            VerifyOrExit(index + 1 < kMaxEncodedLength, error = kErrorInvalidArgs);
+            SuccessOrExit(error = AppendLabel(&aLabels[labelStartIndex], labelLength, aMessage));
+
+            labelStartIndex = index + 1;
+        }
+
+        index++;
+
+    } while (ch != kNullChar);
+
+exit:
+    return error;
+}
+
+Error Name::AppendTerminator(Message &aMessage)
+{
+    uint8_t terminator = 0;
+
+    return aMessage.Append(terminator);
+}
+
+Error Name::AppendPointerLabel(uint16_t aOffset, Message &aMessage)
+{
+    // A pointer label takes the form of a two byte sequence as a
+    // `uint16_t` value. The first two bits are ones. This allows a
+    // pointer to be distinguished from a text label, since the text
+    // label must begin with two zero bits (note that labels are
+    // restricted to 63 octets or less). The next 14-bits specify
+    // an offset value relative to start of DNS header.
+
+    uint16_t value;
+
+    OT_ASSERT(aOffset < kPointerLabelTypeUint16);
+
+    value = HostSwap16(aOffset | kPointerLabelTypeUint16);
+
+    return aMessage.Append(value);
+}
+
+Error Name::AppendName(const char *aName, Message &aMessage)
+{
+    Error error;
+
+    SuccessOrExit(error = AppendMultipleLabels(aName, aMessage));
+    error = AppendTerminator(aMessage);
+
+exit:
+    return error;
+}
+
+Error Name::ParseName(const Message &aMessage, uint16_t &aOffset)
+{
+    Error         error;
+    LabelIterator iterator(aMessage, aOffset);
+
+    while (true)
+    {
+        error = iterator.GetNextLabel();
+
+        switch (error)
+        {
+        case kErrorNone:
+            break;
+
+        case kErrorNotFound:
+            // We reached the end of name successfully.
+            aOffset = iterator.mNameEndOffset;
+            error   = kErrorNone;
+
+            OT_FALL_THROUGH;
+
+        default:
+            ExitNow();
+        }
+    }
+
+exit:
+    return error;
+}
+
+Error Name::ReadLabel(const Message &aMessage, uint16_t &aOffset, char *aLabelBuffer, uint8_t &aLabelLength)
+{
+    Error         error;
+    LabelIterator iterator(aMessage, aOffset);
+
+    SuccessOrExit(error = iterator.GetNextLabel());
+    SuccessOrExit(error = iterator.ReadLabel(aLabelBuffer, aLabelLength, /* aAllowDotCharInLabel */ true));
+    aOffset = iterator.mNextLabelOffset;
+
+exit:
+    return error;
+}
+
+Error Name::ReadName(const Message &aMessage, uint16_t &aOffset, char *aNameBuffer, uint16_t aNameBufferSize)
+{
+    Error         error;
+    LabelIterator iterator(aMessage, aOffset);
+    bool          firstLabel = true;
+    uint8_t       labelLength;
+
+    while (true)
+    {
+        error = iterator.GetNextLabel();
+
+        switch (error)
+        {
+        case kErrorNone:
+
+            if (!firstLabel)
+            {
+                *aNameBuffer++ = kLabelSeperatorChar;
+                aNameBufferSize--;
+
+                // No need to check if we have reached end of the name buffer
+                // here since `iterator.ReadLabel()` would verify it.
+            }
+
+            labelLength = static_cast<uint8_t>(OT_MIN(static_cast<uint8_t>(kMaxLabelSize), aNameBufferSize));
+            SuccessOrExit(error = iterator.ReadLabel(aNameBuffer, labelLength, /* aAllowDotCharInLabel */ false));
+            aNameBuffer += labelLength;
+            aNameBufferSize -= labelLength;
+            firstLabel = false;
+            break;
+
+        case kErrorNotFound:
+            // We reach the end of name successfully. Always add a terminating dot
+            // at the end.
+            *aNameBuffer++ = kLabelSeperatorChar;
+            aNameBufferSize--;
+            VerifyOrExit(aNameBufferSize >= sizeof(uint8_t), error = kErrorNoBufs);
+            *aNameBuffer = kNullChar;
+            aOffset      = iterator.mNameEndOffset;
+            error        = kErrorNone;
+
+            OT_FALL_THROUGH;
+
+        default:
+            ExitNow();
+        }
+    }
+
+exit:
+    return error;
+}
+
+Error Name::CompareLabel(const Message &aMessage, uint16_t &aOffset, const char *aLabel)
+{
+    Error         error;
+    LabelIterator iterator(aMessage, aOffset);
+
+    SuccessOrExit(error = iterator.GetNextLabel());
+    VerifyOrExit(iterator.CompareLabel(aLabel, /* aIsSingleLabel */ true), error = kErrorNotFound);
+    aOffset = iterator.mNextLabelOffset;
+
+exit:
+    return error;
+}
+
+Error Name::CompareName(const Message &aMessage, uint16_t &aOffset, const char *aName)
+{
+    Error         error;
+    LabelIterator iterator(aMessage, aOffset);
+    bool          matches = true;
+
+    if (*aName == kLabelSeperatorChar)
+    {
+        aName++;
+        VerifyOrExit(*aName == kNullChar, error = kErrorInvalidArgs);
+    }
+
+    while (true)
+    {
+        error = iterator.GetNextLabel();
+
+        switch (error)
+        {
+        case kErrorNone:
+            if (matches && !iterator.CompareLabel(aName, /* aIsSingleLabel */ false))
+            {
+                matches = false;
+            }
+
+            break;
+
+        case kErrorNotFound:
+            // We reached the end of the name in `aMessage`. We check if
+            // all the previous labels matched so far, and we are also
+            // at the end of `aName` string (see null char), then we
+            // return `kErrorNone` indicating a successful comparison
+            // (full match). Otherwise we return `kErrorNotFound` to
+            // indicate failed comparison.
+
+            if (matches && (*aName == kNullChar))
+            {
+                error = kErrorNone;
+            }
+
+            aOffset = iterator.mNameEndOffset;
+
+            OT_FALL_THROUGH;
+
+        default:
+            ExitNow();
+        }
+    }
+
+exit:
+    return error;
+}
+
+Error Name::CompareName(const Message &aMessage, uint16_t &aOffset, const Message &aMessage2, uint16_t aOffset2)
+{
+    Error         error;
+    LabelIterator iterator(aMessage, aOffset);
+    LabelIterator iterator2(aMessage2, aOffset2);
+    bool          matches = true;
+
+    while (true)
+    {
+        error = iterator.GetNextLabel();
+
+        switch (error)
+        {
+        case kErrorNone:
+            // If all the previous labels matched so far, then verify
+            // that we can get the next label on `iterator2` and that it
+            // matches the label from `iterator`.
+            if (matches && (iterator2.GetNextLabel() != kErrorNone || !iterator.CompareLabel(iterator2)))
+            {
+                matches = false;
+            }
+
+            break;
+
+        case kErrorNotFound:
+            // We reached the end of the name in `aMessage`. We check
+            // that `iterator2` is also at its end, and if all previous
+            // labels matched we return `kErrorNone`.
+
+            if (matches && (iterator2.GetNextLabel() == kErrorNotFound))
+            {
+                error = kErrorNone;
+            }
+
+            aOffset = iterator.mNameEndOffset;
+
+            OT_FALL_THROUGH;
+
+        default:
+            ExitNow();
+        }
+    }
+
+exit:
+    return error;
+}
+
+Error Name::CompareName(const Message &aMessage, uint16_t &aOffset, const Name &aName)
+{
+    return aName.IsFromCString()
+               ? CompareName(aMessage, aOffset, aName.mString)
+               : (aName.IsFromMessage() ? CompareName(aMessage, aOffset, *aName.mMessage, aName.mOffset)
+                                        : ParseName(aMessage, aOffset));
+}
+
+Error Name::LabelIterator::GetNextLabel(void)
+{
+    Error error;
+
+    while (true)
+    {
+        uint8_t labelLength;
+        uint8_t labelType;
+
+        SuccessOrExit(error = mMessage.Read(mNextLabelOffset, labelLength));
+
+        labelType = labelLength & kLabelTypeMask;
+
+        if (labelType == kTextLabelType)
+        {
+            if (labelLength == 0)
+            {
+                // Zero label length indicates end of a name.
+
+                if (!IsEndOffsetSet())
+                {
+                    mNameEndOffset = mNextLabelOffset + sizeof(uint8_t);
+                }
+
+                ExitNow(error = kErrorNotFound);
+            }
+
+            mLabelStartOffset = mNextLabelOffset + sizeof(uint8_t);
+            mLabelLength      = labelLength;
+            mNextLabelOffset  = mLabelStartOffset + labelLength;
+            ExitNow();
+        }
+        else if (labelType == kPointerLabelType)
+        {
+            // A pointer label takes the form of a two byte sequence as a
+            // `uint16_t` value. The first two bits are ones. The next 14 bits
+            // specify an offset value from the start of the DNS header.
+
+            uint16_t pointerValue;
+
+            SuccessOrExit(error = mMessage.Read(mNextLabelOffset, pointerValue));
+
+            if (!IsEndOffsetSet())
+            {
+                mNameEndOffset = mNextLabelOffset + sizeof(uint16_t);
+            }
+
+            // `mMessage.GetOffset()` must point to the start of the
+            // DNS header.
+            mNextLabelOffset = mMessage.GetOffset() + (HostSwap16(pointerValue) & kPointerLabelOffsetMask);
+
+            // Go back through the `while(true)` loop to get the next label.
+        }
+        else
+        {
+            ExitNow(error = kErrorParse);
+        }
+    }
+
+exit:
+    return error;
+}
+
+Error Name::LabelIterator::ReadLabel(char *aLabelBuffer, uint8_t &aLabelLength, bool aAllowDotCharInLabel) const
+{
+    Error error;
+
+    VerifyOrExit(mLabelLength < aLabelLength, error = kErrorNoBufs);
+
+    SuccessOrExit(error = mMessage.Read(mLabelStartOffset, aLabelBuffer, mLabelLength));
+    aLabelBuffer[mLabelLength] = kNullChar;
+    aLabelLength               = mLabelLength;
+
+    if (!aAllowDotCharInLabel)
+    {
+        VerifyOrExit(StringFind(aLabelBuffer, kLabelSeperatorChar) == nullptr, error = kErrorParse);
+    }
+
+exit:
+    return error;
+}
+
+bool Name::LabelIterator::CompareLabel(const char *&aName, bool aIsSingleLabel) const
+{
+    // This method compares the current label in the iterator with the
+    // `aName` string. `aIsSingleLabel` indicates whether `aName` is a
+    // single label, or a sequence of labels separated by dot '.' char.
+    // If the label matches `aName`, then `aName` pointer is moved
+    // forward to the start of the next label (skipping over the `.`
+    // char). This method returns `true` when the labels match, `false`
+    // otherwise.
+
+    bool matches = false;
+
+    VerifyOrExit(StringLength(aName, mLabelLength) == mLabelLength);
+    matches = mMessage.CompareBytes(mLabelStartOffset, aName, mLabelLength);
+
+    VerifyOrExit(matches);
+
+    aName += mLabelLength;
+
+    // If `aName` is a single label, we should be also at the end of the
+    // `aName` string. Otherwise, we should see either null or dot '.'
+    // character (in case `aName` contains multiple labels).
+
+    matches = (*aName == kNullChar);
+
+    if (!aIsSingleLabel && (*aName == kLabelSeperatorChar))
+    {
+        matches = true;
+        aName++;
+    }
+
+exit:
+    return matches;
+}
+
+bool Name::LabelIterator::CompareLabel(const LabelIterator &aOtherIterator) const
+{
+    // This method compares the current label in the iterator with the
+    // label from another iterator.
+
+    return (mLabelLength == aOtherIterator.mLabelLength) &&
+           mMessage.CompareBytes(mLabelStartOffset, aOtherIterator.mMessage, aOtherIterator.mLabelStartOffset,
+                                 mLabelLength);
+}
+
+bool Name::IsSubDomainOf(const char *aName, const char *aDomain)
+{
+    bool     match        = false;
+    uint16_t nameLength   = StringLength(aName, kMaxNameLength);
+    uint16_t domainLength = StringLength(aDomain, kMaxNameLength);
+
+    if (nameLength > 0 && aName[nameLength - 1] == kLabelSeperatorChar)
+    {
+        --nameLength;
+    }
+
+    if (domainLength > 0 && aDomain[domainLength - 1] == kLabelSeperatorChar)
+    {
+        --domainLength;
+    }
+
+    VerifyOrExit(nameLength >= domainLength);
+    aName += nameLength - domainLength;
+
+    if (nameLength > domainLength)
+    {
+        VerifyOrExit(aName[-1] == kLabelSeperatorChar);
+    }
+    VerifyOrExit(memcmp(aName, aDomain, domainLength) == 0);
+
+    match = true;
+
+exit:
+    return match;
+}
+
+Error ResourceRecord::ParseRecords(const Message &aMessage, uint16_t &aOffset, uint16_t aNumRecords)
+{
+    Error error = kErrorNone;
+
+    while (aNumRecords > 0)
+    {
+        ResourceRecord record;
+
+        SuccessOrExit(error = Name::ParseName(aMessage, aOffset));
+        SuccessOrExit(error = record.ReadFrom(aMessage, aOffset));
+        aOffset += static_cast<uint16_t>(record.GetSize());
+        aNumRecords--;
+    }
+
+exit:
+    return error;
+}
+
+Error ResourceRecord::FindRecord(const Message &aMessage, uint16_t &aOffset, uint16_t &aNumRecords, const Name &aName)
+{
+    Error error;
+
+    while (aNumRecords > 0)
+    {
+        bool           matches = true;
+        ResourceRecord record;
+
+        error = Name::CompareName(aMessage, aOffset, aName);
+
+        switch (error)
+        {
+        case kErrorNone:
+            break;
+        case kErrorNotFound:
+            matches = false;
+            break;
+        default:
+            ExitNow();
+        }
+
+        SuccessOrExit(error = record.ReadFrom(aMessage, aOffset));
+        aNumRecords--;
+        VerifyOrExit(!matches);
+        aOffset += static_cast<uint16_t>(record.GetSize());
+    }
+
+    error = kErrorNotFound;
+
+exit:
+    return error;
+}
+
+Error ResourceRecord::FindRecord(const Message & aMessage,
+                                 uint16_t &      aOffset,
+                                 uint16_t        aNumRecords,
+                                 uint16_t        aIndex,
+                                 const Name &    aName,
+                                 uint16_t        aType,
+                                 ResourceRecord &aRecord,
+                                 uint16_t        aMinRecordSize)
+{
+    // This static method searches in `aMessage` starting from `aOffset`
+    // up to maximum of `aNumRecords`, for the `(aIndex+1)`th
+    // occurrence of a resource record of type `aType` with record name
+    // matching `aName`. It also verifies that the record size is larger
+    // than `aMinRecordSize`. If found, `aMinRecordSize` bytes from the
+    // record are read and copied into `aRecord`. In this case `aOffset`
+    // is updated to point to the last record byte read from the message
+    // (so that the caller can read any remaining fields in the record
+    // data).
+
+    Error    error;
+    uint16_t offset = aOffset;
+    uint16_t recordOffset;
+
+    while (aNumRecords > 0)
+    {
+        SuccessOrExit(error = FindRecord(aMessage, offset, aNumRecords, aName));
+
+        // Save the offset to start of `ResourceRecord` fields.
+        recordOffset = offset;
+
+        error = ReadRecord(aMessage, offset, aType, aRecord, aMinRecordSize);
+
+        if (error == kErrorNotFound)
+        {
+            // `ReadRecord()` already updates the `offset` to skip
+            // over a non-matching record.
+            continue;
+        }
+
+        SuccessOrExit(error);
+
+        if (aIndex == 0)
+        {
+            aOffset = offset;
+            ExitNow();
+        }
+
+        aIndex--;
+
+        // Skip over the record.
+        offset = static_cast<uint16_t>(recordOffset + aRecord.GetSize());
+    }
+
+    error = kErrorNotFound;
+
+exit:
+    return error;
+}
+
+Error ResourceRecord::ReadRecord(const Message & aMessage,
+                                 uint16_t &      aOffset,
+                                 uint16_t        aType,
+                                 ResourceRecord &aRecord,
+                                 uint16_t        aMinRecordSize)
+{
+    // This static method tries to read a matching resource record of a
+    // given type and a minimum record size from a message. The `aType`
+    // value of `kTypeAny` matches any type.  If the record in the
+    // message does not match, it skips over the record. Please see
+    // `ReadRecord<RecordType>()` for more details.
+
+    Error          error;
+    ResourceRecord record;
+
+    SuccessOrExit(error = record.ReadFrom(aMessage, aOffset));
+
+    if (((aType == kTypeAny) || (record.GetType() == aType)) && (record.GetSize() >= aMinRecordSize))
+    {
+        IgnoreError(aMessage.Read(aOffset, &aRecord, aMinRecordSize));
+        aOffset += aMinRecordSize;
+    }
+    else
+    {
+        // Skip over the entire record.
+        aOffset += static_cast<uint16_t>(record.GetSize());
+        error = kErrorNotFound;
+    }
+
+exit:
+    return error;
+}
+
+Error ResourceRecord::ReadName(const Message &aMessage,
+                               uint16_t &     aOffset,
+                               uint16_t       aStartOffset,
+                               char *         aNameBuffer,
+                               uint16_t       aNameBufferSize,
+                               bool           aSkipRecord) const
+{
+    // This protected method parses and reads a name field in a record
+    // from a message. It is intended only for sub-classes of
+    // `ResourceRecord`.
+    //
+    // On input `aOffset` gives the offset in `aMessage` to the start of
+    // name field. `aStartOffset` gives the offset to the start of the
+    // `ResourceRecord`. `aSkipRecord` indicates whether to skip over
+    // the entire resource record or just the read name. On exit, when
+    // successfully read, `aOffset` is updated to either point after the
+    // end of record or after the the name field.
+    //
+    // When read successfully, this method returns `kErrorNone`. On a
+    // parse error (invalid format) returns `kErrorParse`. If the
+    // name does not fit in the given name buffer it returns
+    // `kErrorNoBufs`
+
+    Error error = kErrorNone;
+
+    SuccessOrExit(error = Name::ReadName(aMessage, aOffset, aNameBuffer, aNameBufferSize));
+    VerifyOrExit(aOffset <= aStartOffset + GetSize(), error = kErrorParse);
+
+    VerifyOrExit(aSkipRecord);
+    aOffset = aStartOffset;
+    error   = SkipRecord(aMessage, aOffset);
+
+exit:
+    return error;
+}
+
+Error ResourceRecord::SkipRecord(const Message &aMessage, uint16_t &aOffset) const
+{
+    // This protected method parses and skips over a resource record
+    // in a message.
+    //
+    // On input `aOffset` gives the offset in `aMessage` to the start of
+    // the `ResourceRecord`. On exit, when successfully parsed, `aOffset`
+    // is updated to point to byte after the entire record.
+
+    Error error;
+
+    SuccessOrExit(error = CheckRecord(aMessage, aOffset));
+    aOffset += static_cast<uint16_t>(GetSize());
+
+exit:
+    return error;
+}
+
+Error ResourceRecord::CheckRecord(const Message &aMessage, uint16_t aOffset) const
+{
+    // This method checks that the entire record (including record data)
+    // is present in `aMessage` at `aOffset` (pointing to the start of
+    // the `ResourceRecord` in `aMessage`).
+
+    return (aOffset + GetSize() <= aMessage.GetLength()) ? kErrorNone : kErrorParse;
+}
+
+Error ResourceRecord::ReadFrom(const Message &aMessage, uint16_t aOffset)
+{
+    // This method reads the `ResourceRecord` from `aMessage` at
+    // `aOffset`. It verifies that the entire record (including record
+    // data) is present in the message.
+
+    Error error;
+
+    SuccessOrExit(error = aMessage.Read(aOffset, *this));
+    error = CheckRecord(aMessage, aOffset);
+
+exit:
+    return error;
+}
+
+void TxtEntry::Iterator::Init(const uint8_t *aTxtData, uint16_t aTxtDataLength)
+{
+    SetTxtData(aTxtData);
+    SetTxtDataLength(aTxtDataLength);
+    SetTxtDataPosition(0);
+}
+
+Error TxtEntry::Iterator::GetNextEntry(TxtEntry &aEntry)
+{
+    Error       error = kErrorNone;
+    uint8_t     length;
+    uint8_t     index;
+    const char *cur;
+    char *      keyBuffer = GetKeyBuffer();
+
+    static_assert(sizeof(mChar) == TxtEntry::kMaxKeyLength + 1, "KeyBuffer cannot fit the max key length");
+
+    VerifyOrExit(GetTxtData() != nullptr, error = kErrorParse);
+
+    aEntry.mKey = keyBuffer;
+
+    while ((cur = GetTxtData() + GetTxtDataPosition()) < GetTxtDataEnd())
+    {
+        length = static_cast<uint8_t>(*cur);
+
+        cur++;
+        VerifyOrExit(cur + length <= GetTxtDataEnd(), error = kErrorParse);
+        IncreaseTxtDataPosition(sizeof(uint8_t) + length);
+
+        // Silently skip over an empty string or if the string starts with
+        // a `=` character (i.e., missing key) - RFC 6763 - section 6.4.
+
+        if ((length == 0) || (cur[0] == kKeyValueSeparator))
+        {
+            continue;
+        }
+
+        for (index = 0; index < length; index++)
+        {
+            if (cur[index] == kKeyValueSeparator)
+            {
+                keyBuffer[index++]  = kNullChar; // Increment index to skip over `=`.
+                aEntry.mValue       = reinterpret_cast<const uint8_t *>(&cur[index]);
+                aEntry.mValueLength = length - index;
+                ExitNow();
+            }
+
+            if (index >= kMaxKeyLength)
+            {
+                // The key is larger than recommended max key length.
+                // In this case, we return the full encoded string in
+                // `mValue` and `mValueLength` and set `mKey` to
+                // `nullptr`.
+
+                aEntry.mKey         = nullptr;
+                aEntry.mValue       = reinterpret_cast<const uint8_t *>(cur);
+                aEntry.mValueLength = length;
+                ExitNow();
+            }
+
+            keyBuffer[index] = cur[index];
+        }
+
+        // If we reach the end of the string without finding `=` then
+        // it is a boolean key attribute (encoded as "key").
+
+        keyBuffer[index]    = kNullChar;
+        aEntry.mValue       = nullptr;
+        aEntry.mValueLength = 0;
+        ExitNow();
+    }
+
+    error = kErrorNotFound;
+
+exit:
+    return error;
+}
+
+Error TxtEntry::AppendTo(Message &aMessage) const
+{
+    Error    error = kErrorNone;
+    uint16_t keyLength;
+
+    if (mKey == nullptr)
+    {
+        VerifyOrExit((mValue != nullptr) && (mValueLength != 0));
+        error = aMessage.AppendBytes(mValue, mValueLength);
+        ExitNow();
+    }
+
+    keyLength = StringLength(mKey, static_cast<uint16_t>(kMaxKeyValueEncodedSize) + 1);
+
+    VerifyOrExit(kMinKeyLength <= keyLength, error = kErrorInvalidArgs);
+
+    if (mValue == nullptr)
+    {
+        // Treat as a boolean attribute and encoded as "key" (with no `=`).
+        SuccessOrExit(error = aMessage.Append<uint8_t>(static_cast<uint8_t>(keyLength)));
+        error = aMessage.AppendBytes(mKey, keyLength);
+        ExitNow();
+    }
+
+    // Treat as key/value and encode as "key=value", value may be empty.
+
+    VerifyOrExit(mValueLength + keyLength + sizeof(char) <= kMaxKeyValueEncodedSize, error = kErrorInvalidArgs);
+
+    SuccessOrExit(error = aMessage.Append<uint8_t>(static_cast<uint8_t>(keyLength + mValueLength + sizeof(char))));
+    SuccessOrExit(error = aMessage.AppendBytes(mKey, keyLength));
+    SuccessOrExit(error = aMessage.Append<char>(kKeyValueSeparator));
+    error = aMessage.AppendBytes(mValue, mValueLength);
+
+exit:
+    return error;
+}
+
+Error TxtEntry::AppendEntries(const TxtEntry *aEntries, uint8_t aNumEntries, Message &aMessage)
+{
+    Error    error       = kErrorNone;
+    uint16_t startOffset = aMessage.GetLength();
+
+    for (uint8_t index = 0; index < aNumEntries; index++)
+    {
+        SuccessOrExit(error = aEntries[index].AppendTo(aMessage));
+    }
+
+    if (aMessage.GetLength() == startOffset)
+    {
+        error = aMessage.Append<uint8_t>(0);
+    }
+
+exit:
+    return error;
+}
+
+bool AaaaRecord::IsValid(void) const
+{
+    return GetType() == Dns::ResourceRecord::kTypeAaaa && GetSize() == sizeof(*this);
+}
+
+bool KeyRecord::IsValid(void) const
+{
+    return GetType() == Dns::ResourceRecord::kTypeKey;
+}
+
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+void Ecdsa256KeyRecord::Init(void)
+{
+    KeyRecord::Init();
+    SetAlgorithm(kAlgorithmEcdsaP256Sha256);
+}
+
+bool Ecdsa256KeyRecord::IsValid(void) const
+{
+    return KeyRecord::IsValid() && GetLength() == sizeof(*this) - sizeof(ResourceRecord) &&
+           GetAlgorithm() == kAlgorithmEcdsaP256Sha256;
+}
+#endif
+
+bool SigRecord::IsValid(void) const
+{
+    return GetType() == Dns::ResourceRecord::kTypeSig && GetLength() >= sizeof(*this) - sizeof(ResourceRecord);
+}
+
+bool LeaseOption::IsValid(void) const
+{
+    return GetLeaseInterval() <= GetKeyLeaseInterval();
+}
+
+Error PtrRecord::ReadPtrName(const Message &aMessage,
+                             uint16_t &     aOffset,
+                             char *         aLabelBuffer,
+                             uint8_t        aLabelBufferSize,
+                             char *         aNameBuffer,
+                             uint16_t       aNameBufferSize) const
+{
+    Error    error       = kErrorNone;
+    uint16_t startOffset = aOffset - sizeof(PtrRecord); // start of `PtrRecord`.
+
+    // Verify that the name is within the record data length.
+    SuccessOrExit(error = Name::ParseName(aMessage, aOffset));
+    VerifyOrExit(aOffset <= startOffset + GetSize(), error = kErrorParse);
+
+    aOffset = startOffset + sizeof(PtrRecord);
+    SuccessOrExit(error = Name::ReadLabel(aMessage, aOffset, aLabelBuffer, aLabelBufferSize));
+
+    if (aNameBuffer != nullptr)
+    {
+        SuccessOrExit(error = Name::ReadName(aMessage, aOffset, aNameBuffer, aNameBufferSize));
+    }
+
+    aOffset = startOffset;
+    error   = SkipRecord(aMessage, aOffset);
+
+exit:
+    return error;
+}
+
+Error TxtRecord::ReadTxtData(const Message &aMessage,
+                             uint16_t &     aOffset,
+                             uint8_t *      aTxtBuffer,
+                             uint16_t &     aTxtBufferSize) const
+{
+    Error error = kErrorNone;
+
+    VerifyOrExit(GetLength() <= aTxtBufferSize, error = kErrorNoBufs);
+    SuccessOrExit(error = aMessage.Read(aOffset, aTxtBuffer, GetLength()));
+    VerifyOrExit(VerifyTxtData(aTxtBuffer, GetLength()), error = kErrorParse);
+    aTxtBufferSize = GetLength();
+    aOffset += GetLength();
+
+exit:
+    return error;
+}
+
+bool TxtRecord::VerifyTxtData(const uint8_t *aTxtData, uint16_t aTxtLength)
+{
+    bool    valid          = false;
+    uint8_t curEntryLength = 0;
+
+    // Per RFC 1035, TXT-DATA MUST have one or more <character-string>s.
+    VerifyOrExit(aTxtLength > 0);
+
+    for (uint16_t i = 0; i < aTxtLength; ++i)
+    {
+        if (curEntryLength == 0)
+        {
+            curEntryLength = aTxtData[i];
+        }
+        else
+        {
+            --curEntryLength;
+        }
+    }
+
+    valid = (curEntryLength == 0);
+
+exit:
+    return valid;
+}
+
+} // namespace Dns
+} // namespace ot
diff --git a/src/core/net/dns_types.hpp b/src/core/net/dns_types.hpp
new file mode 100644
index 0000000..b9d40cb
--- /dev/null
+++ b/src/core/net/dns_types.hpp
@@ -0,0 +1,2608 @@
+/*
+ *  Copyright (c) 2017, The OpenThread Authors.
+ *  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
+ *   This file includes definitions for generating and processing DNS headers.
+ */
+
+#ifndef DNS_HEADER_HPP_
+#define DNS_HEADER_HPP_
+
+#include "openthread-core-config.h"
+
+#include <openthread/dns.h>
+#include <openthread/dns_client.h>
+
+#include "common/clearable.hpp"
+#include "common/encoding.hpp"
+#include "common/equatable.hpp"
+#include "common/message.hpp"
+#include "crypto/ecdsa.hpp"
+#include "net/ip6_address.hpp"
+
+namespace ot {
+
+/**
+ * @namespace ot::Dns
+ * @brief
+ *   This namespace includes definitions for DNS.
+ *
+ */
+namespace Dns {
+
+using ot::Encoding::BigEndian::HostSwap16;
+using ot::Encoding::BigEndian::HostSwap32;
+
+/**
+ * @addtogroup core-dns
+ *
+ * @brief
+ *   This module includes definitions for DNS.
+ *
+ * @{
+ *
+ */
+
+/**
+ * This class implements DNS header generation and parsing.
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class Header : public Clearable<Header>
+{
+public:
+    /**
+     * Default constructor for DNS Header.
+     *
+     */
+    Header(void) { Clear(); }
+
+    /**
+     * This method returns the Message ID.
+     *
+     * @returns The Message ID value.
+     *
+     */
+    uint16_t GetMessageId(void) const { return HostSwap16(mMessageId); }
+
+    /**
+     * This method sets the Message ID.
+     *
+     * @param[in]  aMessageId The Message ID value.
+     *
+     */
+    void SetMessageId(uint16_t aMessageId) { mMessageId = HostSwap16(aMessageId); }
+
+    /**
+     * This method sets the Message ID to a crypto-secure randomly generated number.
+     *
+     * @retval  kErrorNone     Successfully generated random Message ID.
+     * @retval  kErrorFailed   Could not generate random Message ID.
+     *
+     */
+    Error SetRandomMessageId(void);
+
+    /**
+     * Defines types of DNS message.
+     *
+     */
+    enum Type
+    {
+        kTypeQuery    = 0,
+        kTypeResponse = 1,
+    };
+
+    /**
+     * This method returns the type of the message.
+     *
+     * @returns The type of the message.
+     *
+     */
+    Type GetType(void) const { return static_cast<Type>((mFlags[0] & kQrFlagMask) >> kQrFlagOffset); }
+
+    /**
+     * This method sets the type of the message.
+     *
+     * @param[in]  aType The type of the message.
+     *
+     */
+    void SetType(Type aType)
+    {
+        mFlags[0] &= ~kQrFlagMask;
+        mFlags[0] |= static_cast<uint8_t>(aType) << kQrFlagOffset;
+    }
+
+    /**
+     * Defines types of query.
+     *
+     */
+    enum QueryType
+    {
+        kQueryTypeStandard = 0,
+        kQueryTypeInverse  = 1,
+        kQueryTypeStatus   = 2,
+        kQueryTypeNotify   = 4,
+        kQueryTypeUpdate   = 5
+    };
+
+    /**
+     * This method returns the type of the query.
+     *
+     * @returns The type of the query.
+     *
+     */
+    QueryType GetQueryType(void) const { return static_cast<QueryType>((mFlags[0] & kOpCodeMask) >> kOpCodeOffset); }
+
+    /**
+     * This method sets the type of the query.
+     *
+     * @param[in]  aType The type of the query.
+     *
+     */
+    void SetQueryType(QueryType aType)
+    {
+        mFlags[0] &= ~kOpCodeMask;
+        mFlags[0] |= static_cast<uint8_t>(aType) << kOpCodeOffset;
+    }
+
+    /**
+     * This method specifies in response message if the responding name server is an
+     * authority for the domain name in question section.
+     *
+     * @returns True if Authoritative Answer flag (AA) is set in the header, false otherwise.
+     *
+     */
+    bool IsAuthoritativeAnswerFlagSet(void) const { return (mFlags[0] & kAaFlagMask) == kAaFlagMask; }
+
+    /**
+     * This method clears the Authoritative Answer flag (AA) in the header.
+     *
+     */
+    void ClearAuthoritativeAnswerFlag(void) { mFlags[0] &= ~kAaFlagMask; }
+
+    /**
+     * This method sets the Authoritative Answer flag (AA) in the header.
+     *
+     */
+    void SetAuthoritativeAnswerFlag(void) { mFlags[0] |= kAaFlagMask; }
+
+    /**
+     * This method specifies if message is truncated.
+     *
+     * @returns True if Truncation flag (TC) is set in the header, false otherwise.
+     *
+     */
+    bool IsTruncationFlagSet(void) const { return (mFlags[0] & kTcFlagMask) == kTcFlagMask; }
+
+    /**
+     * This method clears the Truncation flag (TC) in the header.
+     *
+     */
+    void ClearTruncationFlag(void) { mFlags[0] &= ~kTcFlagMask; }
+
+    /**
+     * This method sets the Truncation flag (TC) in the header.
+     *
+     */
+    void SetTruncationFlag(void) { mFlags[0] |= kTcFlagMask; }
+
+    /**
+     * This method specifies if resolver wants to direct the name server to pursue
+     * the query recursively.
+     *
+     * @returns True if Recursion Desired flag (RD) is set in the header, false otherwise.
+     *
+     */
+    bool IsRecursionDesiredFlagSet(void) const { return (mFlags[0] & kRdFlagMask) == kRdFlagMask; }
+
+    /**
+     * This method clears the Recursion Desired flag (RD) in the header.
+     *
+     */
+    void ClearRecursionDesiredFlag(void) { mFlags[0] &= ~kRdFlagMask; }
+
+    /**
+     * This method sets the Recursion Desired flag (RD) in the header.
+     *
+     */
+    void SetRecursionDesiredFlag(void) { mFlags[0] |= kRdFlagMask; }
+
+    /**
+     * This method denotes whether recursive query support is available in the name server.
+     *
+     * @returns True if Recursion Available flag (RA) is set in the header, false otherwise.
+     *
+     */
+    bool IsRecursionAvailableFlagSet(void) const { return (mFlags[1] & kRaFlagMask) == kRaFlagMask; }
+
+    /**
+     * This method clears the Recursion Available flag (RA) in the header.
+     *
+     */
+    void ClearRecursionAvailableFlag(void) { mFlags[1] &= ~kRaFlagMask; }
+
+    /**
+     * This method sets the Recursion Available flag (RA) in the header.
+     *
+     */
+    void SetRecursionAvailableFlag(void) { mFlags[1] |= kRaFlagMask; }
+
+    /**
+     * Defines response codes.
+     *
+     */
+    enum Response
+    {
+        kResponseSuccess         = 0,  ///< Success (no error condition).
+        kResponseFormatError     = 1,  ///< Server unable to interpret request due to format error.
+        kResponseServerFailure   = 2,  ///< Server encountered an internal failure.
+        kResponseNameError       = 3,  ///< Name that ought to exist, does not exists.
+        kResponseNotImplemented  = 4,  ///< Server does not support the query type (OpCode).
+        kResponseRefused         = 5,  ///< Server refused to perform operation for policy or security reasons.
+        kResponseNameExists      = 6,  ///< Some name that ought not to exist, does exist.
+        kResponseRecordExists    = 7,  ///< Some RRset that ought not to exist, does exist.
+        kResponseRecordNotExists = 8,  ///< Some RRset that ought to exist, does not exist.
+        kResponseNotAuth         = 9,  ///< Service is not authoritative for zone.
+        kResponseNotZone         = 10, ///< A name is not in the zone.
+        kResponseBadName         = 20, ///< Bad name.
+        kResponseBadAlg          = 21, ///< Bad algorithm.
+        kResponseBadTruncation   = 22, ///< Bad truncation.
+    };
+
+    /**
+     * This method returns the response code.
+     *
+     * @returns The response code from the header.
+     *
+     */
+    Response GetResponseCode(void) const { return static_cast<Response>((mFlags[1] & kRCodeMask) >> kRCodeOffset); }
+
+    /**
+     * This method sets the response code.
+     *
+     * @param[in]  aResponse The type of the response.
+     *
+     */
+    void SetResponseCode(Response aResponse)
+    {
+        mFlags[1] &= ~kRCodeMask;
+        mFlags[1] |= static_cast<uint8_t>(aResponse) << kRCodeOffset;
+    }
+
+    /**
+     * This method converts a Response Code into a related `Error`.
+     *
+     * - kResponseSuccess (0)         : Success (no error condition)                    -> kErrorNone
+     * - kResponseFormatError (1)     : Server unable to interpret due to format error  -> kErrorParse
+     * - kResponseServerFailure (2)   : Server encountered an internal failure          -> kErrorFailed
+     * - kResponseNameError (3)       : Name that ought to exist, does not exists       -> kErrorNotFound
+     * - kResponseNotImplemented (4)  : Server does not support the query type (OpCode) -> kErrorNotImplemented
+     * - kResponseRefused (5)         : Server refused for policy/security reasons      -> kErrorSecurity
+     * - kResponseNameExists (6)      : Some name that ought not to exist, does exist   -> kErrorDuplicated
+     * - kResponseRecordExists (7)    : Some RRset that ought not to exist, does exist  -> kErrorDuplicated
+     * - kResponseRecordNotExists (8) : Some RRset that ought to exist, does not exist  -> kErrorNotFound
+     * - kResponseNotAuth (9)         : Service is not authoritative for zone           -> kErrorSecurity
+     * - kResponseNotZone (10)        : A name is not in the zone                       -> kErrorParse
+     * - kResponseBadName (20)        : Bad name                                        -> kErrorParse
+     * - kResponseBadAlg (21)         : Bad algorithm                                   -> kErrorSecurity
+     * - kResponseBadTruncation (22)  : Bad truncation                                  -> kErrorParse
+     * - Other error                                                                    -> kErrorFailed
+     *
+     * @param[in] aResponse  The response code to convert.
+     *
+     */
+    static Error ResponseCodeToError(Response aResponse);
+
+    /**
+     * This method returns the number of entries in question section.
+     *
+     * @returns The number of entries in question section.
+     *
+     */
+    uint16_t GetQuestionCount(void) const { return HostSwap16(mQdCount); }
+
+    /**
+     * This method sets the number of entries in question section.
+     *
+     * @param[in]  aCount The number of entries in question section.
+     *
+     */
+    void SetQuestionCount(uint16_t aCount) { mQdCount = HostSwap16(aCount); }
+
+    /**
+     * This method returns the number of entries in answer section.
+     *
+     * @returns The number of entries in answer section.
+     *
+     */
+    uint16_t GetAnswerCount(void) const { return HostSwap16(mAnCount); }
+
+    /**
+     * This method sets the number of entries in answer section.
+     *
+     * @param[in]  aCount The number of entries in answer section.
+     *
+     */
+    void SetAnswerCount(uint16_t aCount) { mAnCount = HostSwap16(aCount); }
+
+    /**
+     * This method returns the number of entries in authority records section.
+     *
+     * @returns The number of entries in authority records section.
+     *
+     */
+    uint16_t GetAuthorityRecordCount(void) const { return HostSwap16(mNsCount); }
+
+    /**
+     * This method sets the number of entries in authority records section.
+     *
+     * @param[in]  aCount The number of entries in authority records section.
+     *
+     */
+    void SetAuthorityRecordCount(uint16_t aCount) { mNsCount = HostSwap16(aCount); }
+
+    /**
+     * This method returns the number of entries in additional records section.
+     *
+     * @returns The number of entries in additional records section.
+     *
+     */
+    uint16_t GetAdditionalRecordCount(void) const { return HostSwap16(mArCount); }
+
+    /**
+     * This method sets the number of entries in additional records section.
+     *
+     * @param[in]  aCount The number of entries in additional records section.
+     *
+     */
+    void SetAdditionalRecordCount(uint16_t aCount) { mArCount = HostSwap16(aCount); }
+
+private:
+    /**
+     * Protocol Constants (RFC 1035).
+     *
+     */
+    enum
+    {
+        kQrFlagOffset = 7,                     // QR Flag offset.
+        kQrFlagMask   = 0x01 << kQrFlagOffset, // QR Flag mask.
+        kOpCodeOffset = 3,                     // OpCode field offset.
+        kOpCodeMask   = 0x0f << kOpCodeOffset, // OpCode field mask.
+        kAaFlagOffset = 2,                     // AA Flag offset.
+        kAaFlagMask   = 0x01 << kAaFlagOffset, // AA Flag mask.
+        kTcFlagOffset = 1,                     // TC Flag offset.
+        kTcFlagMask   = 0x01 << kTcFlagOffset, // TC Flag mask.
+        kRdFlagOffset = 0,                     // RD Flag offset.
+        kRdFlagMask   = 0x01 << kRdFlagOffset, // RD Flag mask.
+        kRaFlagOffset = 7,                     // RA Flag offset.
+        kRaFlagMask   = 0x01 << kRaFlagOffset, // RA Flag mask.
+        kRCodeOffset  = 0,                     // RCODE field offset.
+        kRCodeMask    = 0x0f << kRCodeOffset,  // RCODE field mask.
+    };
+
+    uint16_t mMessageId; // Message identifier for requester to match up replies to outstanding queries.
+    uint8_t  mFlags[2];  // DNS header flags.
+    uint16_t mQdCount;   // Number of entries in the question section.
+    uint16_t mAnCount;   // Number of entries in the answer section.
+    uint16_t mNsCount;   // Number of entries in the authority records section.
+    uint16_t mArCount;   // Number of entries in the additional records section.
+
+} OT_TOOL_PACKED_END;
+
+/**
+ * This class implements DNS Update message header generation and parsing.
+ *
+ * The DNS header specifies record counts for its four sections: Question, Answer, Authority, and Additional. A DNS
+ * Update header uses the same fields, and the same section formats, but the naming and use of these sections differs:
+ * DNS Update header uses Zone, Prerequisite, Update, Additional Data sections.
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class UpdateHeader : public Header
+{
+public:
+    /**
+     * Default constructor for DNS Update message header.
+     *
+     */
+    UpdateHeader(void) { SetQueryType(kQueryTypeUpdate); }
+
+    /**
+     * This method returns the number of records in Zone section.
+     *
+     * @returns The number of records in Zone section.
+     *
+     */
+    uint16_t GetZoneRecordCount(void) const { return GetQuestionCount(); }
+
+    /**
+     * This method sets the number of records in Zone section.
+     *
+     * @param[in]  aCount The number of records in Zone section.
+     *
+     */
+    void SetZoneRecordCount(uint16_t aCount) { SetQuestionCount(aCount); }
+
+    /**
+     * This method returns the number of records in Prerequisite section.
+     *
+     * @returns The number of records in Prerequisite section.
+     *
+     */
+    uint16_t GetPrerequisiteRecordCount(void) const { return GetAnswerCount(); }
+
+    /**
+     * This method sets the number of records in Prerequisite section.
+     *
+     * @param[in]  aCount The number of records in Prerequisite section.
+     *
+     */
+    void SetPrerequisiteRecordCount(uint16_t aCount) { SetAnswerCount(aCount); }
+
+    /**
+     * This method returns the number of records in Update section.
+     *
+     * @returns The number of records in Update section.
+     *
+     */
+    uint16_t GetUpdateRecordCount(void) const { return GetAuthorityRecordCount(); }
+
+    /**
+     * This method sets the number of records in Update section.
+     *
+     * @param[in]  aCount The number of records in Update section.
+     *
+     */
+    void SetUpdateRecordCount(uint16_t aCount) { SetAuthorityRecordCount(aCount); }
+
+} OT_TOOL_PACKED_END;
+
+/**
+ * This class represents a DNS name and implements helper methods for encoding/decoding of DNS Names.
+ *
+ */
+class Name : public Clearable<Name>
+{
+public:
+    enum : uint8_t
+    {
+        /**
+         * Max size (number of chars) in a name string array (includes null char at the end of string).
+         *
+         */
+        kMaxNameSize = OT_DNS_MAX_NAME_SIZE,
+
+        /**
+         * Maximum length in a name string (does not include null char at the end of string).
+         *
+         */
+        kMaxNameLength = kMaxNameSize - 1,
+
+        /**
+         * Max size (number of chars) in a label string array (includes null char at the end of the string).
+         *
+         */
+        kMaxLabelSize = OT_DNS_MAX_LABEL_SIZE,
+
+        /**
+         * Maximum length in a label string (does not include null char at the end of string).
+         *
+         */
+        kMaxLabelLength = kMaxLabelSize - 1,
+    };
+
+    enum : char
+    {
+        kLabelSeperatorChar = '.',
+    };
+
+    /**
+     * This enumeration represents the name type.
+     *
+     */
+    enum Type : uint8_t
+    {
+        kTypeEmpty,   ///< The name is empty (not specified).
+        kTypeCString, ///< The name is given as a C string (dot '.' separated sequence of labels).
+        kTypeMessage, ///< The name is specified from a message at a given offset (encoded in the message).
+    };
+
+    /**
+     * This constructor initializes the `Name` object as empty (not specified).
+     *
+     */
+    Name(void)
+        : Name(nullptr, nullptr, 0)
+    {
+    }
+
+    /**
+     * This constructor initializes the `Name` object with a given string.
+     *
+     * @param[in] aString   A C string specifying the name (dot '.' separated sequence of labels').
+     *
+     */
+    explicit Name(const char *aString)
+        : Name(aString, nullptr, 0)
+    {
+    }
+
+    /**
+     * This constructor initializes the `Name` object from a message at a given offset.
+     *
+     * @param[in] aMessage   The message containing the encoded name. `aMessage.GetOffset()` MUST point to the start of
+     *                       the DNS header in the message (used to parse compressed name).
+     * @param[in] aOffset    The offset in @p aMessage pointing to the start of the name.
+     *
+     */
+    Name(const Message &aMessage, uint16_t aOffset)
+        : Name(nullptr, &aMessage, aOffset)
+    {
+    }
+
+    /**
+     * This method indicates whether the name is empty (not specified).
+     *
+     * @returns TRUE if the name is empty, FALSE otherwise.
+     *
+     */
+    bool IsEmpty(void) const { return (mString == nullptr) && (mMessage == nullptr); }
+
+    /**
+     * This method indicates whether the name is specified from a C string.
+     *
+     * @returns TRUE if the name is specified from a string, FALSE otherwise.
+     *
+     */
+    bool IsFromCString(void) const { return mString != nullptr; }
+
+    /**
+     * This method indicates whether the name is specified from a message.
+     *
+     * @returns TRUE if the name is specified from a message, FALSE otherwise.
+     *
+     */
+    bool IsFromMessage(void) const { return mMessage != nullptr; }
+
+    /**
+     * This method gets the type of `Name` object indicating whether it is empty, specified by a C string or from a
+     * message
+     *
+     * @returns The name type.
+     *
+     */
+    Type GetFromType(void) const
+    {
+        return IsFromCString() ? kTypeCString : (IsFromMessage() ? kTypeMessage : kTypeEmpty);
+    }
+
+    /**
+     * This method sets the name from a given C string.
+     *
+     * @param[in] aString   A C string specifying the name (dot '.' separated sequence of labels).
+     *
+     */
+    void Set(const char *aString)
+    {
+        mString  = aString;
+        mMessage = nullptr;
+    }
+
+    /**
+     * This method sets the name from a message at a given offset.
+     *
+     * @param[in] aMessage   The message containing the encoded name. `aMessage.GetOffset()` MUST point to the start of
+     *                       the DNS header in the message (used to parse compressed name).
+     * @param[in] aOffset    The offset in @p aMessage pointing to the start of the name.
+     *
+     */
+    void SetFromMessage(const Message &aMessage, uint16_t aOffset)
+    {
+        mString  = nullptr;
+        mMessage = &aMessage;
+        mOffset  = aOffset;
+    }
+
+    /**
+     * This method gets the name as a C string.
+     *
+     * This method MUST be used only when the type is `kTypeString`. Otherwise its behavior is undefined.
+     *
+     * @returns A pointer to the C string.
+     *
+     */
+    const char *GetAsCString(void) const { return mString; }
+
+    /**
+     * This method gets the name message and offset.
+     *
+     * This method MUST be used only when the type is `kTypeMessage`. Otherwise its behavior is undefined.
+     *
+     * @param[out]  aOffset    A reference to a variable to output the offset of the start of the name in the message.
+     *
+     * @returns A reference to the message containing the name.
+     *
+     */
+    const Message &GetAsMessage(uint16_t &aOffset) const
+    {
+        aOffset = mOffset;
+        return *mMessage;
+    }
+
+    /**
+     * This static method encodes and appends a single name label to a message.
+     *
+     * The @p aLabel is assumed to contain a single name label as a C string (null-terminated). Unlike
+     * `AppendMultipleLabels()` which parses the label string and treats it as sequence of multiple (dot-separated)
+     * labels, this method always appends @p aLabel as a single whole label. This allows the label string to even
+     * contain dot '.' character, which, for example, is useful for "Service Instance Names" where <Instance> portion
+     * is a user-friendly name and can contain dot characters.
+     *
+     * @param[in] aLabel              The label string to append. MUST NOT be nullptr.
+     * @param[in] aMessage            The message to append to.
+     *
+     * @retval kErrorNone         Successfully encoded and appended the name label to @p aMessage.
+     * @retval kErrorInvalidArgs  @p aLabel is not valid (e.g., label length is not within valid range).
+     * @retval kErrorNoBufs       Insufficient available buffers to grow the message.
+     *
+     */
+    static Error AppendLabel(const char *aLabel, Message &aMessage);
+
+    /**
+     * This static method encodes and appends a single name label of specified length to a message.
+     *
+     * The @p aLabel is assumed to contain a single name label of given @p aLength.  @p aLabel must not contain
+     * '\0' characters within the length @p aLength. Unlike `AppendMultipleLabels()` which parses the label string
+     * and treats it as sequence of multiple (dot-separated) labels, this method always appends @p aLabel as a single
+     * whole label. This allows the label string to even contain dot '.' character, which, for example, is useful for
+     * "Service Instance Names" where <Instance> portion is a user-friendly name and can contain dot characters.
+     *
+     * @param[in] aLabel         The label string to append. MUST NOT be nullptr.
+     * @param[in] aLength        The length of the label to append.
+     * @param[in] aMessage       The message to append to.
+     *
+     * @retval kErrorNone         Successfully encoded and appended the name label to @p aMessage.
+     * @retval kErrorInvalidArgs  @p aLabel is not valid (e.g., label length is not within valid range).
+     * @retval kErrorNoBufs       Insufficient available buffers to grow the message.
+     *
+     */
+    static Error AppendLabel(const char *aLabel, uint8_t aLength, Message &aMessage);
+
+    /**
+     * This static method encodes and appends a sequence of name labels to a given message.
+     *
+     * The @p aLabels must follow  "<label1>.<label2>.<label3>", i.e., a sequence of labels separated by dot '.' char.
+     * E.g., "_http._tcp", "_http._tcp." (same as previous one), "host-1.test".
+     *
+     * This method validates that the @p aLabels is a valid name format, i.e., no empty label, and labels are
+     * `kMaxLabelLength` (63) characters or less.
+     *
+     * @note This method NEVER adds a label terminator (empty label) to the message, even in the case where @p aLabels
+     * ends with a dot character, e.g., "host-1.test." is treated same as "host-1.test".
+     *
+     * @param[in]  aLabels            A name label string. Can be nullptr (then treated as "").
+     * @param[in]  aMessage           The message to which to append the encoded name.
+     *
+     * @retval kErrorNone         Successfully encoded and appended the name label(s) to @p aMessage.
+     * @retval kErrorInvalidArgs  Name label @p aLabels is not valid.
+     * @retval kErrorNoBufs       Insufficient available buffers to grow the message.
+     *
+     */
+    static Error AppendMultipleLabels(const char *aLabels, Message &aMessage);
+
+    /**
+     * This static method encodes and appends a sequence of name labels within the specified length to a given message.
+     * This method stops appending labels if @p aLength characters are read or '\0' is found before @p aLength
+     * characters.
+     *
+     * This method is useful for appending a number of labels of the name instead of appending all labels.
+     *
+     * The @p aLabels must follow  "<label1>.<label2>.<label3>", i.e., a sequence of labels separated by dot '.' char.
+     * E.g., "_http._tcp", "_http._tcp." (same as previous one), "host-1.test".
+     *
+     * This method validates that the @p aLabels is a valid name format, i.e., no empty label, and labels are
+     * `kMaxLabelLength` (63) characters or less.
+     *
+     * @note This method NEVER adds a label terminator (empty label) to the message, even in the case where @p aLabels
+     * ends with a dot character, e.g., "host-1.test." is treated same as "host-1.test".
+     *
+     * @param[in]  aLabels            A name label string. Can be nullptr (then treated as "").
+     * @param[in]  aLength            The max length of the name labels to encode.
+     * @param[in]  aMessage           The message to which to append the encoded name.
+     *
+     * @retval kErrorNone         Successfully encoded and appended the name label(s) to @p aMessage.
+     * @retval kErrorInvalidArgs  Name label @p aLabels is not valid.
+     * @retval kErrorNoBufs       Insufficient available buffers to grow the message.
+     *
+     */
+    static Error AppendMultipleLabels(const char *aLabels, uint8_t aLength, Message &aMessage);
+
+    /**
+     * This static method appends a name label terminator to a message.
+     *
+     * An encoded name is terminated by an empty label (a zero byte).
+     *
+     * @param[in] aMessage            The message to append to.
+     *
+     * @retval kErrorNone         Successfully encoded and appended the terminator label to @p aMessage.
+     * @retval kErrorNoBufs       Insufficient available buffers to grow the message.
+     *
+     */
+    static Error AppendTerminator(Message &aMessage);
+
+    /**
+     * This static method appends a pointer type name label to a message.
+     *
+     * Pointer label is used for name compression. It allows an entire name or a list of labels at the end of an
+     * encoded name to be replaced with a pointer to a prior occurrence of the same name within the message.
+     *
+     * @param[in] aOffset             The offset from the start of DNS header to use for pointer value.
+     * @param[in] aMessage            The message to append to.
+     *
+     * @retval kErrorNone         Successfully encoded and appended the pointer label to @p aMessage.
+     * @retval kErrorNoBufs       Insufficient available buffers to grow the message.
+     *
+     */
+    static Error AppendPointerLabel(uint16_t aOffset, Message &aMessage);
+
+    /**
+     * This static method encodes and appends a full name to a message.
+     *
+     * The @p aName must follow  "<label1>.<label2>.<label3>", i.e., a sequence of labels separated by dot '.' char.
+     * E.g., "example.com", "example.com." (same as previous one), "local.", "default.service.arpa", "." or "" (root).
+     *
+     * This method validates that the @p aName is a valid name format, i.e. no empty labels, and labels are
+     * `kMaxLabelLength` (63) characters or less, and the name is `kMaxLength` (255) characters or less.
+     *
+     * @param[in]  aName              A name string. Can be nullptr (then treated as "." or root).
+     * @param[in]  aMessage           The message to append to.
+     *
+     * @retval kErrorNone         Successfully encoded and appended the name to @p aMessage.
+     * @retval kErrorInvalidArgs  Name @p aName is not valid.
+     * @retval kErrorNoBufs       Insufficient available buffers to grow the message.
+     *
+     */
+    static Error AppendName(const char *aName, Message &aMessage);
+
+    /**
+     * This static method parses and skips over a full name in a message.
+     *
+     * @param[in]    aMessage         The message to parse the name from. `aMessage.GetOffset()` MUST point to
+     *                                the start of DNS header (this is used to handle compressed names).
+     * @param[inout] aOffset          On input the offset in @p aMessage pointing to the start of the name field.
+     *                                On exit (when parsed successfully), @p aOffset is updated to point to the byte
+     *                                after the end of name field.
+     *
+     * @retval kErrorNone          Successfully parsed and skipped over name, @p Offset is updated.
+     * @retval kErrorParse         Name could not be parsed (invalid format).
+     *
+     */
+    static Error ParseName(const Message &aMessage, uint16_t &aOffset);
+
+    /**
+     * This static method reads a name label from a message.
+     *
+     * This method can be used to read labels one by one in a name. After a successful label read, @p aOffset is
+     * updated to point to the start of the next label. When we reach the end of the name, kErrorNotFound is
+     * returned. This method handles compressed names which use pointer labels. So as the labels in a name are read,
+     * the @p aOffset may jump back in the message and at the end the @p aOffset does not necessarily point to the end
+     * of the original name field.
+     *
+     * Unlike `ReadName()` which requires and verifies that the read label to contain no dot '.' character, this method
+     * allows the read label to include any character.
+     *
+     * @param[in]    aMessage         The message to read the label from. `aMessage.GetOffset()` MUST point to
+     *                                the start of DNS header (this is used to handle compressed names).
+     * @param[inout] aOffset          On input, the offset in @p aMessage pointing to the start of the label to read.
+     *                                On exit, when successfully read, @p aOffset is updated to point to the start of
+     *                                the next label.
+     * @param[out]   aLabelBuffer     A pointer to a char array to output the read label as a null-terminated C string.
+     * @param[inout] aLabelLength     On input, the maximum number chars in @p aLabelBuffer array.
+     *                                On output, when label is successfully read, @p aLabelLength is updated to return
+     *                                the label's length (number of chars in the label string, excluding the null char).
+     *
+     * @retval kErrorNone      Successfully read the label and updated @p aLabelBuffer, @p aLabelLength, and @p aOffset.
+     * @retval kErrorNotFound  Reached the end of name and no more label to read.
+     * @retval kErrorParse     Name could not be parsed (invalid format).
+     * @retval kErrorNoBufs    Label could not fit in @p aLabelLength chars.
+     *
+     */
+    static Error ReadLabel(const Message &aMessage, uint16_t &aOffset, char *aLabelBuffer, uint8_t &aLabelLength);
+
+    /**
+     * This static method reads a full name from a message.
+     *
+     * On successful read, the read name follows  "<label1>.<label2>.<label3>.", i.e., a sequence of labels separated by
+     * dot '.' character. The read name will ALWAYS end with a dot.
+     *
+     * This method verifies that the read labels in message do not contain any dot character, otherwise it returns
+     * `kErrorParse`).
+     *
+     * @param[in]    aMessage         The message to read the name from. `aMessage.GetOffset()` MUST point to
+     *                                the start of DNS header (this is used to handle compressed names).
+     * @param[inout] aOffset          On input, the offset in @p aMessage pointing to the start of the name field.
+     *                                On exit (when parsed successfully), @p aOffset is updated to point to the byte
+     *                                after the end of name field.
+     * @param[out]   aNameBuffer      A pointer to a char array to output the read name as a null-terminated C string.
+     * @param[inout] aNameBufferSize  The maximum number of chars in @p aNameBuffer array.
+     *
+     * @retval kErrorNone         Successfully read the name, @p aNameBuffer and @p Offset are updated.
+     * @retval kErrorParse        Name could not be parsed (invalid format).
+     * @retval kErrorNoBufs       Name could not fit in @p aNameBufferSize chars.
+     *
+     */
+    static Error ReadName(const Message &aMessage, uint16_t &aOffset, char *aNameBuffer, uint16_t aNameBufferSize);
+
+    /**
+     * This static method compares a single name label from a message with a given label string.
+     *
+     * This method can be used to compare labels one by one. It checks whether the label read from @p aMessage matches
+     * @p aLabel string.
+     *
+     * Unlike `CompareName()` which requires the labels in the the name string to contain no dot '.' character, this
+     * method allows @p aLabel to include any character.
+     *
+     * @param[in]    aMessage         The message to read the label from to compare. `aMessage.GetOffset()` MUST point
+     *                                to the start of DNS header (this is used to handle compressed names).
+     * @param[inout] aOffset          On input, the offset in @p aMessage pointing to the start of the label to read.
+     *                                On exit and only when label is successfully read and does match @p aLabel,
+     *                                @p aOffset is updated to point to the start of the next label.
+     * @param[in]    aLabel           A pointer to a null terminated string containing the label to compare with.
+
+     * @retval kErrorNone          The label from @p aMessage matches @p aLabel. @p aOffset is updated.
+     * @retval kErrorNotFound      The label from @p aMessage does not match @p aLabel (note that @p aOffset is not
+     *                             updated in this case).
+     * @retval kErrorParse         Name could not be parsed (invalid format).
+     *
+     */
+    static Error CompareLabel(const Message &aMessage, uint16_t &aOffset, const char *aLabel);
+
+    /**
+     * This static method parses and compares a full name from a message with a given name.
+     *
+     * This method checks whether the encoded name in a message matches a given name string. It checks the name in
+     * the message in place and handles compressed names. If the name read from the message does not match @p aName, it
+     * returns `kErrorNotFound`. `kErrorNone` indicates that the name matches @p aName.
+     *
+     * The @p aName must follow  "<label1>.<label2>.<label3>", i.e., a sequence of labels separated by dot '.' char.
+     * E.g., "example.com", "example.com." (same as previous one), "local.", "default.service.arpa", "." or "" (root).
+     *
+     * @param[in]    aMessage         The message to read the name from and compare with @p aName.
+     *                                `aMessage.GetOffset()` MUST point to the start of DNS header (this is used to
+     *                                handle compressed names).
+     * @param[inout] aOffset          On input, the offset in @p aMessage pointing to the start of the name field.
+     *                                On exit (when parsed successfully independent of whether the read name matches
+     *                                @p aName or not), @p aOffset is updated to point to the byte after the end of
+     *                                the name field.
+     * @param[in]    aName            A pointer to a null terminated string containing the name to compare with.
+     *
+     * @retval kErrorNone          The name from @p aMessage matches @p aName. @p aOffset is updated.
+     * @retval kErrorNotFound      The name from @p aMessage does not match @p aName. @p aOffset is updated.
+     * @retval kErrorParse         Name could not be parsed (invalid format).
+     * @retval kErrorInvalidArgs   The @p aName is not a valid name (e.g. back to back "." chars)
+     *
+     */
+    static Error CompareName(const Message &aMessage, uint16_t &aOffset, const char *aName);
+
+    /**
+     * This static method parses and compares a full name from a message with a name from another message.
+     *
+     * This method checks whether the encoded name in @p aMessage matches the name from @p aMessage2. It compares the
+     * names in both messages in place and handles compressed names. Note that this method works correctly even when
+     * the same message instance is used for both @p aMessage and @p aMessage2 (e.g., at different offsets).
+     *
+     * Only the name in @p aMessage is fully parsed and checked for parse errors. This method assumes that the name in
+     * @p aMessage2 was previously parsed and validated before calling this method (if there is a parse error in
+     * @p aMessage2, it is treated as a name mismatch with @p aMessage).
+     *
+     * If the name in @p aMessage can be parsed fully (independent of whether the name matches or not with the name
+     * from @p aMessage2), the @p aOffset is updated (note that @p aOffset2 for @p aMessage2 is not changed).
+     *
+     * @param[in]    aMessage         The message to read the name from and compare. `aMessage.GetOffset()` MUST point
+     *                                to the start of DNS header (this is used to handle compressed names).
+     * @param[inout] aOffset          On input, the offset in @p aMessage pointing to the start of the name field.
+     *                                On exit (when parsed successfully independent of whether the read name matches
+     *                                or not), @p aOffset is updated to point to the byte after the end of the name
+     *                                field.
+     * @param[in]    aMessage2        The second message to read the name from and compare with name from @p aMessage.
+     *                                `aMessage2.GetOffset()` MUST point to the start of DNS header.
+     * @param[in]    aOffset2         The offset in @p aMessage2 pointing to the start of the name field.
+     *
+     * @retval kErrorNone       The name from @p aMessage matches the name from @p aMessage2. @p aOffset is updated.
+     * @retval kErrorNotFound   The name from @p aMessage does not match the name from @p aMessage2. @p aOffset is
+     *                          updated.
+     * @retval kErrorParse      Name in @p aMessage could not be parsed (invalid format).
+     *
+     */
+    static Error CompareName(const Message &aMessage, uint16_t &aOffset, const Message &aMessage2, uint16_t aOffset2);
+
+    /**
+     * This static method parses and compares a full name from a message with a given name.
+     *
+     * If @p aName is empty (not specified), then any name in @p aMessage is considered a match to it.
+     *
+     * @param[in]    aMessage         The message to read the name from and compare. `aMessage.GetOffset()` MUST point
+     *                                to the start of DNS header (this is used to handle compressed names).
+     * @param[inout] aOffset          On input, the offset in @p aMessage pointing to the start of the name field.
+     *                                On exit (when parsed successfully independent of whether the read name matches
+     *                                or not), @p aOffset is updated to point to the byte after the end of the name
+     *                                field.
+     * @param[in]    aName            A reference to a name to compare with.
+     *
+     * @retval kErrorNone          The name from @p aMessage matches @p aName. @p aOffset is updated.
+     * @retval kErrorNotFound      The name from @p aMessage does not match @p aName. @p aOffset is updated.
+     * @retval kErrorParse         Name in @p aMessage could not be parsed (invalid format).
+     *
+     */
+    static Error CompareName(const Message &aMessage, uint16_t &aOffset, const Name &aName);
+
+    /**
+     * This static method tests if a DNS name is a sub-domain of a given domain.
+     *
+     * Both @p aName and @p aDomain can end without dot ('.').
+     *
+     * @param[in]  aName    The dot-separated name.
+     * @param[in]  aDomain  The dot-separated domain.
+     *
+     * @returns  TRUE if the name is a sub-domain of @p aDomain, FALSE if is not.
+     *
+     */
+    static bool IsSubDomainOf(const char *aName, const char *aDomain);
+
+private:
+    enum : char
+    {
+        kNullChar = '\0',
+    };
+
+    enum : uint8_t
+    {
+        // The first 2 bits of the encoded label specifies label type.
+        //
+        // - Value 00 indicates normal text label (lower 6-bits indicates the label length).
+        // - Value 11 indicates pointer label type (lower 14-bits indicates the pointer offset).
+        // - Values 01,10 are reserved (RFC 6891 recommends to not use)
+
+        kLabelTypeMask    = 0xc0, // 0b1100_0000 (first two bits)
+        kTextLabelType    = 0x00, // Text label type (00)
+        kPointerLabelType = 0xc0, // Pointer label type - compressed name (11)
+
+        kMaxEncodedLength = 255, ///< Max length of an encoded name.
+    };
+
+    enum : uint16_t
+    {
+        kPointerLabelTypeUint16 = 0xc000, // Pointer label type as `uint16_t` mask (first 2 bits).
+        kPointerLabelOffsetMask = 0x3fff, // Mask to get the offset field in a pointer label (lower 14 bits).
+    };
+
+    struct LabelIterator
+    {
+        enum : uint16_t
+        {
+            kUnsetNameEndOffset = 0, // Special value indicating `mNameEndOffset` is not yet set.
+        };
+
+        LabelIterator(const Message &aMessage, uint16_t aLabelOffset)
+            : mMessage(aMessage)
+            , mNextLabelOffset(aLabelOffset)
+            , mNameEndOffset(kUnsetNameEndOffset)
+        {
+        }
+
+        bool  IsEndOffsetSet(void) const { return (mNameEndOffset != kUnsetNameEndOffset); }
+        Error GetNextLabel(void);
+        Error ReadLabel(char *aLabelBuffer, uint8_t &aLabelLength, bool aAllowDotCharInLabel) const;
+        bool  CompareLabel(const char *&aName, bool aIsSingleLabel) const;
+        bool  CompareLabel(const LabelIterator &aOtherIterator) const;
+
+        const Message &mMessage;          // Message to read labels from.
+        uint16_t       mLabelStartOffset; // Offset in `mMessage` to the first char of current label text.
+        uint8_t        mLabelLength;      // Length of current label (number of chars).
+        uint16_t       mNextLabelOffset;  // Offset in `mMessage` to the start of the next label.
+        uint16_t       mNameEndOffset;    // Offset in `mMessage` to the byte after the end of domain name field.
+    };
+
+    Name(const char *aString, const Message *aMessage, uint16_t aOffset)
+        : mString(aString)
+        , mMessage(aMessage)
+        , mOffset(aOffset)
+    {
+    }
+
+    const char *   mString;  // String containing the name or `nullptr` if name is not from string.
+    const Message *mMessage; // Message containing the encoded name, or `nullptr` if `Name` is not from message.
+    uint16_t       mOffset;  // Offset in `mMessage` to the start of name (used when name is from `mMessage`).
+};
+
+/**
+ * This type represents a TXT record entry representing a key/value pair (RFC 6763 - section 6.3).
+ *
+ */
+class TxtEntry : public otDnsTxtEntry
+{
+    friend class TxtRecord;
+
+public:
+    enum : uint8_t
+    {
+        kMinKeyLength = OT_DNS_TXT_KEY_MIN_LENGTH, ///< Minimum length of key string (RFC 6763 - section 6.4).
+        kMaxKeyLength = OT_DNS_TXT_KEY_MAX_LENGTH, ///< Recommended max length of key string (RFC 6763 - section 6.4).
+    };
+
+    /**
+     * This class represents an iterator for TXT record entires (key/value pairs).
+     *
+     */
+    class Iterator : public otDnsTxtEntryIterator
+    {
+        friend class TxtEntry;
+
+    public:
+        /**
+         * This method initializes a TXT record iterator.
+         *
+         * The buffer pointer @p aTxtData and its content MUST persist and remain unchanged while the iterator object
+         * is being used.
+         *
+         * @param[in] aTxtData        A pointer to buffer containing the encoded TXT data.
+         * @param[in] aTxtDataLength  The length (number of bytes) of @p aTxtData.
+         *
+         */
+        void Init(const uint8_t *aTxtData, uint16_t aTxtDataLength);
+
+        /**
+         * This method parses the TXT data from the `Iterator` and gets the next TXT record entry (key/value pair).
+         *
+         * The `Iterator` instance MUST be initialized using `Init()` before calling this method and the TXT data
+         * buffer used to initialize the iterator MUST persist and remain unchanged.
+         *
+         * If the parsed key string length is smaller than or equal to `kMaxKeyLength` (recommended max key length)
+         * the key string is returned in `mKey` in @p aEntry. But if the key is longer, then `mKey` is set to NULL and
+         * the entire encoded TXT entry is returned in `mValue` and `mValueLength`.
+         *
+         * @param[out] aEntry          A reference to a `TxtEntry` to output the parsed/read entry.
+         *
+         * @retval kErrorNone       The next entry was parsed successfully. @p aEntry is updated.
+         * @retval kErrorNotFound   No more entries in TXT data.
+         * @retval kErrorParse      The TXT data from `Iterator` is not well-formed.
+         *
+         */
+        Error GetNextEntry(TxtEntry &aEntry);
+
+    private:
+        enum : uint8_t
+        {
+            kIndexTxtLength   = 0,
+            kIndexTxtPosition = 1,
+        };
+
+        const char *GetTxtData(void) const { return reinterpret_cast<const char *>(mPtr); }
+        void        SetTxtData(const uint8_t *aTxtData) { mPtr = aTxtData; }
+        uint16_t    GetTxtDataLength(void) const { return mData[kIndexTxtLength]; }
+        void        SetTxtDataLength(uint16_t aLength) { mData[kIndexTxtLength] = aLength; }
+        uint16_t    GetTxtDataPosition(void) const { return mData[kIndexTxtPosition]; }
+        void        SetTxtDataPosition(uint16_t aValue) { mData[kIndexTxtPosition] = aValue; }
+        void        IncreaseTxtDataPosition(uint16_t aIncrement) { mData[kIndexTxtPosition] += aIncrement; }
+        char *      GetKeyBuffer(void) { return mChar; }
+        const char *GetTxtDataEnd(void) const { return GetTxtData() + GetTxtDataLength(); }
+    };
+
+    /**
+     * This is the default constructor for a `TxtEntry` object.
+     *
+     */
+    TxtEntry(void) = default;
+
+    /**
+     * This constructor initializes a `TxtEntry` object.
+     *
+     * @param[in] aKey           A pointer to the key string.
+     * @param[in] aValue         A pointer to a buffer containing the value.
+     * @param[in] aValueLength   Number of bytes in @p aValue buffer.
+     *
+     */
+    TxtEntry(const char *aKey, const uint8_t *aValue, uint8_t aValueLength)
+    {
+        mKey         = aKey;
+        mValue       = aValue;
+        mValueLength = aValueLength;
+    }
+
+    /**
+     * This method encodes and appends the `TxtEntry` to a message.
+     *
+     * @param[in] aMessage  The message to append to.
+     *
+     * @retval kErrorNone          Entry was appended successfully to @p aMessage.
+     * @retval kErrorInvalidArgs   The `TxTEntry` info is not valid.
+     * @retval kErrorNoBufs        Insufficient available buffers to grow the message.
+     *
+     */
+    Error AppendTo(Message &aMessage) const;
+
+    /**
+     * This static method appends an array of `TxtEntry` items to a message.
+     *
+     * @param[in] aEntries     A pointer to array of `TxtEntry` items.
+     * @param[in] aNumEntries  The number of entries in @p aEntries array.
+     * @param[in] aMessage     The message to append to.
+     *
+     * @retval kErrorNone          Entries appended successfully to @p aMessage.
+     * @retval kErrorInvalidArgs   The `TxTEntry` info is not valid.
+     * @retval kErrorNoBufs        Insufficient available buffers to grow the message.
+     *
+     */
+    static Error AppendEntries(const TxtEntry *aEntries, uint8_t aNumEntries, Message &aMessage);
+
+private:
+    enum : uint8_t
+    {
+        kMaxKeyValueEncodedSize = 255,
+    };
+
+    enum : char
+    {
+        kKeyValueSeparator = '=',
+        kNullChar          = '\0',
+    };
+};
+
+/**
+ * This class implements Resource Record (RR) body format.
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class ResourceRecord
+{
+    friend class OptRecord;
+
+public:
+    /**
+     * Resource Record Types.
+     *
+     */
+    enum : uint16_t
+    {
+        kTypeZero  = 0,   ///< Zero is used as a special indicator for the SIG RR (SIG(0) from RFC 2931).
+        kTypeA     = 1,   ///< Address record (IPv4).
+        kTypeSoa   = 6,   ///< Start of (zone of) authority.
+        kTypeCname = 5,   ///< CNAME record.
+        kTypePtr   = 12,  ///< PTR record.
+        kTypeTxt   = 16,  ///< TXT record.
+        kTypeSig   = 24,  ///< SIG record.
+        kTypeKey   = 25,  ///< KEY record.
+        kTypeAaaa  = 28,  ///< IPv6 address record.
+        kTypeSrv   = 33,  ///< SRV locator record.
+        kTypeOpt   = 41,  ///< Option record.
+        kTypeAny   = 255, ///< ANY record.
+    };
+
+    /**
+     * Resource Record Class Codes.
+     *
+     */
+    enum : uint16_t
+    {
+        kClassInternet = 1,   ///< Class code Internet (IN).
+        kClassNone     = 254, ///< Class code None (NONE) - RFC 2136.
+        kClassAny      = 255, ///< Class code Any (ANY).
+    };
+
+    /**
+     * This method initializes the resource record by setting its type and class.
+     *
+     * This method only sets the type and class fields. Other fields (TTL and length) remain unchanged/uninitialized.
+     *
+     * @param[in] aType   The type of the resource record.
+     * @param[in] aClass  The class of the resource record (default is `kClassInternet`).
+     *
+     */
+    void Init(uint16_t aType, uint16_t aClass = kClassInternet)
+    {
+        SetType(aType);
+        SetClass(aClass);
+    }
+
+    /**
+     * This method indicates whether the resources records matches a given type and class code.
+     *
+     * @param[in] aType   The resource record type to compare with.
+     * @param[in] aClass  The resource record class code to compare with (default is `kClassInternet`).
+     *
+     * @returns TRUE if the resources records matches @p aType and @p aClass, FALSE otherwise.
+     *
+     */
+    bool Matches(uint16_t aType, uint16_t aClass = kClassInternet)
+    {
+        return (mType == HostSwap16(aType)) && (mClass == HostSwap16(aClass));
+    }
+
+    /**
+     * This method returns the type of the resource record.
+     *
+     * @returns The type of the resource record.
+     *
+     */
+    uint16_t GetType(void) const { return HostSwap16(mType); }
+
+    /**
+     * This method sets the type of the resource record.
+     *
+     * @param[in]  aType The type of the resource record.
+     *
+     */
+    void SetType(uint16_t aType) { mType = HostSwap16(aType); }
+
+    /**
+     * This method returns the class of the resource record.
+     *
+     * @returns The class of the resource record.
+     *
+     */
+    uint16_t GetClass(void) const { return HostSwap16(mClass); }
+
+    /**
+     * This method sets the class of the resource record.
+     *
+     * @param[in]  aClass The class of the resource record.
+     *
+     */
+    void SetClass(uint16_t aClass) { mClass = HostSwap16(aClass); }
+
+    /**
+     * This method returns the time to live field of the resource record.
+     *
+     * @returns The time to live field of the resource record.
+     *
+     */
+    uint32_t GetTtl(void) const { return HostSwap32(mTtl); }
+
+    /**
+     * This method sets the time to live field of the resource record.
+     *
+     * @param[in]  aTtl The time to live field of the resource record.
+     *
+     */
+    void SetTtl(uint32_t aTtl) { mTtl = HostSwap32(aTtl); }
+
+    /**
+     * This method returns the length of the resource record data.
+     *
+     * @returns The length of the resource record data.
+     *
+     */
+    uint16_t GetLength(void) const { return HostSwap16(mLength); }
+
+    /**
+     * This method sets the length of the resource record data.
+     *
+     * @param[in]  aLength The length of the resource record data.
+     *
+     */
+    void SetLength(uint16_t aLength) { mLength = HostSwap16(aLength); }
+
+    /**
+     * This method returns the size of (number of bytes) in resource record and its data RDATA section (excluding the
+     * name field).
+     *
+     * @returns Size (number of bytes) of resource record and its data section (excluding the name field)
+     *
+     */
+    uint32_t GetSize(void) const { return sizeof(ResourceRecord) + GetLength(); }
+
+    /**
+     * This static method parses and skips over a given number of resource records in a message from a given offset.
+     *
+     * @param[in]    aMessage     The message from which to parse/read the resource records. `aMessage.GetOffset()`
+     *                            MUST point to the start of DNS header.
+     * @param[inout] aOffset      On input the offset in @p aMessage pointing to the start of the first record.
+     *                            On exit (when parsed successfully), @p aOffset is updated to point to the byte after
+     *                            the last parsed record.
+     * @param[in]    aNumRecords  Number of resource records to parse.
+     *
+     * @retval kErrorNone      Parsed records successfully. @p aOffset is updated.
+     * @retval kErrorParse     Could not parse the records from @p aMessage (e.g., ran out of bytes in @p aMessage).
+     *
+     */
+    static Error ParseRecords(const Message &aMessage, uint16_t &aOffset, uint16_t aNumRecords);
+
+    /**
+     * This static method searches in a given message to find the first resource record matching a given record name.
+     *
+     * @param[in]    aMessage        The message in which to search for a matching resource record.
+     *                               `aMessage.GetOffset()` MUST point to the start of DNS header.
+     * @param[inout] aOffset         On input, the offset in @p aMessage pointing to the start of the first record.
+     *                               On exit, if a matching record is found, @p aOffset is updated to point to the byte
+     *                               after the record name.
+     *                               If a matching record could not be found, @p aOffset is updated to point to the byte
+     *                               after the last record that was checked.
+     * @param[inout] aNumRecords     On input, the maximum number of records to check (starting from @p aOffset).
+     *                               On exit and if a matching record is found, @p aNumRecords is updated to give the
+     *                               number of remaining records after @p aOffset (excluding the matching record).
+     * @param[in]    aName           The record name to match against.
+     *
+     * @retval kErrorNone         A matching record was found. @p aOffset, @p aNumRecords are updated.
+     * @retval kErrorNotFound     A matching record could not be found. @p aOffset and @p aNumRecords are updated.
+     * @retval kErrorParse        Could not parse records from @p aMessage (e.g., ran out of bytes in @p aMessage).
+     *
+     */
+    static Error FindRecord(const Message &aMessage, uint16_t &aOffset, uint16_t &aNumRecords, const Name &aName);
+
+    /**
+     * This template static method searches in a message to find the i-th occurrence of resource records of specific
+     * type with a given record name and if found, reads the record from the message.
+     *
+     * This method searches in @p aMessage starting from @p aOffset up to maximum of @p aNumRecords, for the
+     * `(aIndex+1)`th occurrence of a resource record of `RecordType` with record name @p aName.
+     *
+     * On success (i.e., when a matching record is found and read from the message), @p aOffset is updated to point
+     * to after the last byte read from the message and copied into @p aRecord. This allows the caller to read any
+     * remaining fields in the record data.
+     *
+     * @tparam       RecordType      The resource record type (i.e., a sub-class of `ResourceRecord`).
+     *
+     * @param[in]    aMessage        The message to search within for matching resource records.
+     *                               `aMessage.GetOffset()` MUST point to the start of DNS header.
+     * @param[inout] aOffset         On input, the offset in @p aMessage pointing to the start of the first record.
+     *                               On exit and only if a matching record is found, @p aOffset is updated to point to
+     *                               the last read byte in the record (allowing caller to read any remaining fields in
+     *                               the record data from the message).
+     * @param[in]    aNumRecords     The maximum number of records to check (starting from @p aOffset).
+     * @param[in]    aIndex          The matching record index to find. @p aIndex value of zero returns the first
+     *                               matching record.
+     * @param[in]    aName           The record name to match against.
+     * @param[in]    aRecord         A reference to a record object to read a matching record into.
+     *                               If a matching record is found, `sizeof(RecordType)` bytes from @p aMessage are
+     *                               read and copied into @p aRecord.
+     *
+     * @retval kErrorNone         A matching record was found. @p aOffset is updated.
+     * @retval kErrorNotFound     A matching record could not be found.
+     * @retval kErrorParse        Could not parse records from @p aMessage (e.g., ran out of bytes in @p aMessage).
+     *
+     */
+    template <class RecordType>
+    static Error FindRecord(const Message &aMessage,
+                            uint16_t &     aOffset,
+                            uint16_t       aNumRecords,
+                            uint16_t       aIndex,
+                            const Name &   aName,
+                            RecordType &   aRecord)
+    {
+        return FindRecord(aMessage, aOffset, aNumRecords, aIndex, aName, RecordType::kType, aRecord,
+                          sizeof(RecordType));
+    }
+
+    /**
+     * This template static method tries to read a resource record of a given type from a message. If the record type
+     * does not matches the type, it skips over the record.
+     *
+     * This method requires the record name to be already parsed/read from the message. On input, @p aOffset should
+     * point to the start of the `ResourceRecord` fields (type, class, TTL, data length) in @p aMessage.
+     *
+     * This method verifies that the record is well-formed in the message. It then reads the record type and compares
+     * it with `RecordType::kType` and ensures that the record size is at least `sizeof(RecordType)`. If it all matches,
+     * the record is read into @p aRecord.
+     *
+     * On success (i.e., when a matching record is read from the message), the @p aOffset is updated to point to after
+     * the last byte read from the message and copied into @p aRecord and not necessarily the end of the record.
+     *  Depending on the `RecordType` format, there may still be more data bytes left in the record to be read. For
+     * example, when reading a SRV record using `SrvRecord` type, @p aOffset would point to after the last field in
+     * `SrvRecord`  which is the start of "target host domain name" field.
+     *
+     * @tparam       RecordType      The resource record type (i.e., a sub-class of `ResourceRecord`).
+     *
+     * @param[in]    aMessage        The message from which to read the record.
+     * @param[inout] aOffset         On input, the offset in @p aMessage pointing to the byte after the record name.
+     *                               On exit, if a matching record is read, @p aOffset is updated to point to the last
+     *                               read byte in the record.
+     *                               If a matching record could not be read, @p aOffset is updated to point to the byte
+     *                               after the entire record (skipping over the record).
+     * @param[out]   aRecord         A reference to a record to read a matching record into.
+     *                               If a matching record is found, `sizeof(RecordType)` bytes from @p aMessage are
+     *                               read and copied into @p aRecord.
+     *
+     * @retval kErrorNone         A matching record was read successfully. @p aOffset, and @p aRecord are updated.
+     * @retval kErrorNotFound     A matching record could not be found. @p aOffset is updated.
+     * @retval kErrorParse        Could not parse records from @p aMessage (e.g., ran out of bytes in @p aMessage).
+     *
+     */
+    template <class RecordType> static Error ReadRecord(const Message &aMessage, uint16_t &aOffset, RecordType &aRecord)
+    {
+        return ReadRecord(aMessage, aOffset, RecordType::kType, aRecord, sizeof(RecordType));
+    }
+
+protected:
+    Error ReadName(const Message &aMessage,
+                   uint16_t &     aOffset,
+                   uint16_t       aStartOffset,
+                   char *         aNameBuffer,
+                   uint16_t       aNameBufferSize,
+                   bool           aSkipRecord) const;
+    Error SkipRecord(const Message &aMessage, uint16_t &aOffset) const;
+
+private:
+    enum : uint8_t
+    {
+        kType = kTypeAny, // This is intended for used by `ReadRecord()` only.
+    };
+
+    static Error FindRecord(const Message & aMessage,
+                            uint16_t &      aOffset,
+                            uint16_t        aNumRecords,
+                            uint16_t        aIndex,
+                            const Name &    aName,
+                            uint16_t        aType,
+                            ResourceRecord &aRecord,
+                            uint16_t        aMinRecordSize);
+
+    static Error ReadRecord(const Message & aMessage,
+                            uint16_t &      aOffset,
+                            uint16_t        aType,
+                            ResourceRecord &aRecord,
+                            uint16_t        aMinRecordSize);
+
+    Error CheckRecord(const Message &aMessage, uint16_t aOffset) const;
+    Error ReadFrom(const Message &aMessage, uint16_t aOffset);
+
+    uint16_t mType;   // The type of the data in RDATA section.
+    uint16_t mClass;  // The class of the data in RDATA section.
+    uint32_t mTtl;    // Specifies the maximum time that the resource record may be cached.
+    uint16_t mLength; // The length of RDATA section in bytes.
+
+} OT_TOOL_PACKED_END;
+
+/**
+ * This class implements Resource Record body format of CNAME type.
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class CnameRecord : public ResourceRecord
+{
+public:
+    enum : uint16_t
+    {
+        kType = kTypeCname, ///< The CNAME record type.
+    };
+
+    /**
+     * This method initializes the CNAME Resource Record by setting its type and class.
+     *
+     * Other record fields (TTL, length) remain unchanged/uninitialized.
+     *
+     * @param[in] aClass  The class of the resource record (default is `kClassInternet`).
+     *
+     */
+    void Init(uint16_t aClass = kClassInternet) { ResourceRecord::Init(kTypeCname, aClass); }
+
+    /**
+     * This method parses and reads the CNAME alias name from a message.
+     *
+     * This method also verifies that the CNAME record is well-formed (e.g., the record data length `GetLength()`
+     * matches the CNAME encoded name).
+     *
+     * @param[in]     aMessage          The message to read from. `aMessage.GetOffset()` MUST point to the start of
+     *                                  DNS header.
+     * @param[inout]  aOffset           On input, the offset in @p aMessage to start of CNAME name field.
+     *                                  On exit when successfully read, @p aOffset is updated to point to the byte
+     *                                  after the entire PTR record (skipping over the record).
+     * @param[out]    aNameBuffer       A pointer to a char array to output the read name as a null-terminated C string
+     *                                  (MUST NOT be nullptr).
+     * @param[in]     aNameBufferSize   The size of @p aNameBuffer.
+     *
+     * @retval kErrorNone           The CNAME name was read successfully. @p aOffset and @p aNameBuffer are updated.
+     * @retval kErrorParse          The CNAME record in @p aMessage could not be parsed (invalid format).
+     * @retval kErrorNoBufs         Name could not fit in @p aNameBufferSize chars.
+     *
+     */
+    Error ReadCanonicalName(const Message &aMessage,
+                            uint16_t &     aOffset,
+                            char *         aNameBuffer,
+                            uint16_t       aNameBufferSize) const
+    {
+        return ResourceRecord::ReadName(aMessage, aOffset, /* aStartOffset */ aOffset - sizeof(CnameRecord),
+                                        aNameBuffer, aNameBufferSize, /* aSkipRecord */ true);
+    }
+
+} OT_TOOL_PACKED_END;
+
+/**
+ * This class implements Resource Record body format of PTR type.
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class PtrRecord : public ResourceRecord
+{
+public:
+    enum : uint16_t
+    {
+        kType = kTypePtr, ///< The PTR record type.
+    };
+
+    /**
+     * This method initializes the PTR Resource Record by setting its type and class.
+     *
+     * Other record fields (TTL, length) remain unchanged/uninitialized.
+     *
+     * @param[in] aClass  The class of the resource record (default is `kClassInternet`).
+     *
+     */
+    void Init(uint16_t aClass = kClassInternet) { ResourceRecord::Init(kTypePtr, aClass); }
+
+    /**
+     * This method parses and reads the PTR name from a message.
+     *
+     * This method also verifies that the PTR record is well-formed (e.g., the record data length `GetLength()` matches
+     * the PTR encoded name).
+     *
+     * @param[in]     aMessage          The message to read from.  `aMessage.GetOffset()` MUST point to the start of
+     *                                  DNS header.
+     * @param[inout]  aOffset           On input, the offset in @p aMessage to start of PTR name field.
+     *                                  On exit when successfully read, @p aOffset is updated to point to the byte
+     *                                  after the entire PTR record (skipping over the record).
+     * @param[out]    aNameBuffer       A pointer to a char array to output the read name as a null-terminated C string
+     *                                  (MUST NOT be nullptr).
+     * @param[in]     aNameBufferSize   The size of @p aNameBuffer.
+     *
+     * @retval kErrorNone           The PTR name was read successfully. @p aOffset and @p aNameBuffer are updated.
+     * @retval kErrorParse          The PTR record in @p aMessage could not be parsed (invalid format).
+     * @retval kErrorNoBufs         Name could not fit in @p aNameBufferSize chars.
+     *
+     */
+    Error ReadPtrName(const Message &aMessage, uint16_t &aOffset, char *aNameBuffer, uint16_t aNameBufferSize) const
+    {
+        return ResourceRecord::ReadName(aMessage, aOffset, /* aStartOffset */ aOffset - sizeof(PtrRecord), aNameBuffer,
+                                        aNameBufferSize,
+                                        /* aSkipRecord */ true);
+    }
+
+    /**
+     * This method parses and reads the PTR name from a message.
+     *
+     * This method also verifies that the PTR record is well-formed (e.g., the record data length `GetLength()` matches
+     * the PTR encoded name).
+     *
+     * Unlike the previous method which reads the entire PTR name into a single char buffer, this method reads the
+     * first label separately and into a different buffer @p aLabelBuffer and the rest of the name into @p aNameBuffer.
+     * The @p aNameBuffer can be set to `nullptr` if the caller is only interested in the first label. This method is
+     * intended for "Service Instance Name" where first label (`<Instance>` portion) can be a user-friendly string and
+     * can contain dot character.
+     *
+     * @param[in]     aMessage          The message to read from. `aMessage.GetOffset()` MUST point to the start of
+     *                                  DNS header.
+     * @param[inout]  aOffset           On input, the offset in @p aMessage to the start of PTR name field.
+     *                                  On exit, when successfully read, @p aOffset is updated to point to the byte
+     *                                  after the entire PTR record (skipping over the record).
+     * @param[out]    aLabelBuffer      A pointer to a char array to output the first label as a null-terminated C
+     *                                  string (MUST NOT be nullptr).
+     * @param[in]     aLabelBufferSize  The size of @p aLabelBuffer.
+     * @param[out]    aNameBuffer       A pointer to a char array to output the rest of name (after first label). Can
+     *                                  be `nullptr` if caller is only interested in the first label.
+     * @param[in]     aNameBufferSize   The size of @p aNameBuffer.
+     *
+     * @retval kErrorNone    The PTR name was read successfully. @p aOffset, @aLabelBuffer and @aNameBuffer are updated.
+     * @retval kErrorParse   The PTR record in @p aMessage could not be parsed (invalid format).
+     * @retval kErrorNoBufs  Either label or name could not fit in the related char buffers.
+     *
+     */
+    Error ReadPtrName(const Message &aMessage,
+                      uint16_t &     aOffset,
+                      char *         aLabelBuffer,
+                      uint8_t        aLabelBufferSize,
+                      char *         aNameBuffer,
+                      uint16_t       aNameBufferSize) const;
+
+} OT_TOOL_PACKED_END;
+
+/**
+ * This class implements Resource Record body format of TXT type.
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class TxtRecord : public ResourceRecord
+{
+public:
+    enum : uint16_t
+    {
+        kType = kTypeTxt, ///< The TXT record type.
+    };
+
+    /**
+     * This method initializes the TXT Resource Record by setting its type and class.
+     *
+     * Other record fields (TTL, length) remain unchanged/uninitialized.
+     *
+     * @param[in] aClass  The class of the resource record (default is `kClassInternet`).
+     *
+     */
+    void Init(uint16_t aClass = kClassInternet) { ResourceRecord::Init(kTypeTxt, aClass); }
+
+    /**
+     * This method parses and reads the TXT record data from a message.
+     *
+     * This method also checks if the TXT data is well-formed by calling `VerifyTxtData()`.
+     *
+     * @param[in]     aMessage          The message to read from.
+     * @param[inout]  aOffset           On input, the offset in @p aMessage to start of TXT record data.
+     *                                  On exit when successfully read, @p aOffset is updated to point to the byte
+     *                                  after the entire TXT record (skipping over the record).
+     * @param[out]    aTxtBuffer        A pointer to a byte array to output the read TXT data.
+     * @param[inout]  aTxtBufferSize    On input, the size of @p aTxtBuffer (max bytes that can be read).
+     *                                  On exit, @p aTxtBufferSize gives number of bytes written to @p aTxtBuffer.
+     *
+     * @retval kErrorNone           The TXT data was read successfully. @p aOffset, @p aTxtBuffer and @p aTxtBufferSize
+     *                              are updated.
+     * @retval kErrorParse          The TXT record in @p aMessage could not be parsed (invalid format).
+     * @retval kErrorNoBufs         TXT data could not fit in @p aTxtBufferSize bytes.
+     *
+     */
+    Error ReadTxtData(const Message &aMessage, uint16_t &aOffset, uint8_t *aTxtBuffer, uint16_t &aTxtBufferSize) const;
+
+    /**
+     * This static method tests if a buffer contains valid encoded TXT data.
+     *
+     * @param[in]  aTxtData    The TXT data buffer.
+     * @param[in]  aTxtLength  The length of the TXT data buffer.
+     *
+     * @returns  TRUE if @p aTxtData contains valid encoded TXT data, FALSE if not.
+     *
+     */
+    static bool VerifyTxtData(const uint8_t *aTxtData, uint16_t aTxtLength);
+
+} OT_TOOL_PACKED_END;
+
+/**
+ * This class implements Resource Record body format of AAAA type.
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class AaaaRecord : public ResourceRecord
+{
+public:
+    enum : uint16_t
+    {
+        kType = kTypeAaaa, ///< The AAAA record type.
+    };
+
+    /**
+     * This method initializes the AAAA Resource Record by setting its type, class, and length.
+     *
+     * Other record fields (TTL, address) remain unchanged/uninitialized.
+     *
+     */
+    void Init(void)
+    {
+        ResourceRecord::Init(kTypeAaaa);
+        SetLength(sizeof(Ip6::Address));
+    }
+
+    /**
+     * This method tells whether this is a valid AAAA record.
+     *
+     * @returns  A boolean indicates whether this is a valid AAAA record.
+     *
+     */
+    bool IsValid(void) const;
+
+    /**
+     * This method sets the IPv6 address of the resource record.
+     *
+     * @param[in]  aAddress The IPv6 address of the resource record.
+     *
+     */
+    void SetAddress(const Ip6::Address &aAddress) { mAddress = aAddress; }
+
+    /**
+     * This method returns the reference to IPv6 address of the resource record.
+     *
+     * @returns The reference to IPv6 address of the resource record.
+     *
+     */
+    const Ip6::Address &GetAddress(void) const { return mAddress; }
+
+private:
+    Ip6::Address mAddress; // IPv6 Address of AAAA Resource Record.
+} OT_TOOL_PACKED_END;
+
+/**
+ * This class implements Resource Record body format of SRV type (RFC 2782).
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class SrvRecord : public ResourceRecord
+{
+public:
+    enum : uint16_t
+    {
+        kType = kTypeSrv, ///< The SRV record type.
+    };
+
+    /**
+     * This method initializes the SRV Resource Record by settings its type and class.
+     *
+     * Other record fields (TTL, length, propriety, weight, port, ...) remain unchanged/uninitialized.
+     *
+     * @param[in] aClass  The class of the resource record (default is `kClassInternet`).
+     *
+     */
+    void Init(uint16_t aClass = kClassInternet) { ResourceRecord::Init(kTypeSrv, aClass); }
+
+    /**
+     * This method returns the SRV record's priority value.
+     *
+     * @returns The priority value.
+     *
+     */
+    uint16_t GetPriority(void) const { return HostSwap16(mPriority); }
+
+    /**
+     * This method sets the SRV record's priority value.
+     *
+     * @param[in]  aPriority  The priority value.
+     *
+     */
+    void SetPriority(uint16_t aPriority) { mPriority = HostSwap16(aPriority); }
+
+    /**
+     * This method returns the SRV record's weight value.
+     *
+     * @returns The weight value.
+     *
+     */
+    uint16_t GetWeight(void) const { return HostSwap16(mWeight); }
+
+    /**
+     * This method sets the SRV record's weight value.
+     *
+     * @param[in]  aWeight  The weight value.
+     *
+     */
+    void SetWeight(uint16_t aWeight) { mWeight = HostSwap16(aWeight); }
+
+    /**
+     * This method returns the SRV record's port number on the target host for this service.
+     *
+     * @returns The port number.
+     *
+     */
+    uint16_t GetPort(void) const { return HostSwap16(mPort); }
+
+    /**
+     * This method sets the SRV record's port number on the target host for this service.
+     *
+     * @param[in]  aPort  The port number.
+     *
+     */
+    void SetPort(uint16_t aPort) { mPort = HostSwap16(aPort); }
+
+    /**
+     * This method parses and reads the SRV target host name from a message.
+     *
+     * This method also verifies that the SRV record is well-formed (e.g., the record data length `GetLength()` matches
+     * the SRV encoded name).
+     *
+     * @param[in]     aMessage          The message to read from. `aMessage.GetOffset()` MUST point to the start of
+     *                                  DNS header.
+     * @param[inout]  aOffset           On input, the offset in @p aMessage to start of target host name field.
+     *                                  On exit when successfully read, @p aOffset is updated to point to the byte
+     *                                  after the entire SRV record (skipping over the record).
+     * @param[out]    aNameBuffer       A pointer to a char array to output the read name as a null-terminated C string
+     *                                  (MUST NOT be nullptr).
+     * @param[in]     aNameBufferSize   The size of @p aNameBuffer.
+     *
+     * @retval kErrorNone            The host name was read successfully. @p aOffset and @p aNameBuffer are updated.
+     * @retval kErrorParse           The SRV record in @p aMessage could not be parsed (invalid format).
+     * @retval kErrorNoBufs          Name could not fit in @p aNameBufferSize chars.
+     *
+     */
+    Error ReadTargetHostName(const Message &aMessage,
+                             uint16_t &     aOffset,
+                             char *         aNameBuffer,
+                             uint16_t       aNameBufferSize) const
+    {
+        return ResourceRecord::ReadName(aMessage, aOffset, /* aStartOffset */ aOffset - sizeof(SrvRecord), aNameBuffer,
+                                        aNameBufferSize,
+                                        /* aSkipRecord */ true);
+    }
+
+private:
+    uint16_t mPriority;
+    uint16_t mWeight;
+    uint16_t mPort;
+    // Followed by the target host domain name.
+
+} OT_TOOL_PACKED_END;
+
+/**
+ * This class implements Resource Record body format of KEY type (RFC 2535).
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class KeyRecord : public ResourceRecord
+{
+public:
+    enum : uint16_t
+    {
+        kType = kTypeKey, ///< The KEY record type.
+    };
+
+    /**
+     * This enumeration defines protocol field values (RFC 2535 - section 3.1.3).
+     *
+     */
+    enum : uint8_t
+    {
+        kProtocolTls    = 1, ///< TLS protocol code.
+        kProtocolDnsSec = 3, ///< DNS security protocol code.
+    };
+
+    /**
+     * This enumeration defines algorithm field values (RFC 8624 - section 3.1).
+     *
+     */
+    enum : uint8_t
+    {
+        kAlgorithmEcdsaP256Sha256 = 13, ///< ECDSA-P256-SHA256 algorithm.
+        kAlgorithmEcdsaP384Sha384 = 14, ///< ECDSA-P384-SHA384 algorithm.
+        kAlgorithmEd25519         = 15, ///< ED25519 algorithm.
+        kAlgorithmEd448           = 16, ///< ED448 algorithm.
+    };
+
+    /**
+     * This enumeration type represents the use (or key type) flags (RFC 2535 - section 3.1.2).
+     *
+     */
+    enum UseFlags : uint8_t
+    {
+        kAuthConfidPermitted = 0x00, ///< Use of the key for authentication and/or confidentiality is permitted.
+        kAuthPermitted       = 0x40, ///< Use of the key is only permitted for authentication.
+        kConfidPermitted     = 0x80, ///< Use of the key is only permitted for confidentiality.
+        kNoKey               = 0xc0, ///< No key value (e.g., can indicate zone is not secure).
+    };
+
+    /**
+     * This enumeration type represents key owner (or name type) flags (RFC 2535 - section 3.1.2).
+     *
+     */
+    enum OwnerFlags : uint8_t
+    {
+        kOwnerUser     = 0x00, ///< Key is associated with a "user" or "account" at end entity.
+        kOwnerZone     = 0x01, ///< Key is a zone key (used for data origin authentication).
+        kOwnerNonZone  = 0x02, ///< Key is associated with a non-zone "entity".
+        kOwnerReserved = 0x03, ///< Reserved for future use.
+    };
+
+    /**
+     * This enumeration defines flag bits for the "signatory" flags (RFC 2137).
+     *
+     * The flags defined are for non-zone (`kOwnerNoneZone`) keys (RFC 2137 - section 3.1.3).
+     *
+     */
+    enum : uint8_t
+    {
+        kSignatoryFlagZone    = 1 << 3, ///< Key is authorized to attach, detach, and move zones.
+        kSignatoryFlagStrong  = 1 << 2, ///< Key is authorized to add and delete RRs even if RRs auth with other key.
+        kSignatoryFlagUnique  = 1 << 1, ///< Key is authorized to add and update RRs for only a single owner name.
+        kSignatoryFlagGeneral = 1 << 0, ///< If the other flags are zero, this is used to indicate it is an update key.
+    };
+
+    /**
+     * This method initializes the KEY Resource Record by setting its type and class.
+     *
+     * Other record fields (TTL, length, flags, protocol, algorithm) remain unchanged/uninitialized.
+     *
+     * @param[in] aClass  The class of the resource record (default is `kClassInternet`).
+     *
+     */
+    void Init(uint16_t aClass = kClassInternet) { ResourceRecord::Init(kTypeKey, aClass); }
+
+    /**
+     * This method tells whether the KEY record is valid.
+     *
+     * @returns  TRUE if this is a valid KEY record, FALSE if an invalid KEY record.
+     *
+     */
+    bool IsValid(void) const;
+
+    /**
+     * This method gets the key use (or key type) flags.
+     *
+     * @returns The key use flags.
+     *
+     */
+    UseFlags GetUseFlags(void) const { return static_cast<UseFlags>(mFlags[0] & kUseFlagsMask); }
+
+    /**
+     * This method gets the owner (or name type) flags.
+     *
+     * @returns The key owner flags.
+     *
+     */
+    OwnerFlags GetOwnerFlags(void) const { return static_cast<OwnerFlags>(mFlags[0] & kOwnerFlagsMask); }
+
+    /**
+     * This method gets the signatory flags.
+     *
+     * @returns The signatory flags.
+     *
+     */
+    uint8_t GetSignatoryFlags(void) const { return (mFlags[1] & kSignatoryFlagsMask); }
+
+    /**
+     * This method sets the flags field.
+     *
+     * @param[in] aUseFlags        The `UseFlags` value.
+     * @param[in] aOwnerFlags      The `OwnerFlags` value.
+     * @param[in] aSignatoryFlags  The signatory flags.
+     *
+     */
+    void SetFlags(UseFlags aUseFlags, OwnerFlags aOwnerFlags, uint8_t aSignatoryFlags)
+    {
+        mFlags[0] = (static_cast<uint8_t>(aUseFlags) | static_cast<uint8_t>(aOwnerFlags));
+        mFlags[1] = (aSignatoryFlags & kSignatoryFlagsMask);
+    }
+
+    /**
+     * This method returns the KEY record's protocol value.
+     *
+     * @returns The protocol value.
+     *
+     */
+    uint8_t GetProtocol(void) const { return mProtocol; }
+
+    /**
+     * This method sets the KEY record's protocol value.
+     *
+     * @param[in]  aProtocol  The protocol value.
+     *
+     */
+    void SetProtocol(uint8_t aProtocol) { mProtocol = aProtocol; }
+
+    /**
+     * This method returns the KEY record's algorithm value.
+     *
+     * @returns The algorithm value.
+     *
+     */
+    uint8_t GetAlgorithm(void) const { return mAlgorithm; }
+
+    /**
+     * This method sets the KEY record's algorithm value.
+     *
+     * @param[in]  aAlgorithm  The algorithm value.
+     *
+     */
+    void SetAlgorithm(uint8_t aAlgorithm) { mAlgorithm = aAlgorithm; }
+
+private:
+    enum : uint8_t
+    {
+        kUseFlagsMask       = 0xc0, // top two bits in the first flag byte.
+        kOwnerFlagsMask     = 0x03, // lowest two bits in the first flag byte.
+        kSignatoryFlagsMask = 0x0f, // lower 4 bits in the second flag byte.
+    };
+
+    // Flags format:
+    //
+    //    0   1   2   3   4   5   6   7   8   9   0   1   2   3   4   5
+    //  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+    //  |  Use  | Z | XT| Z | Z | Owner | Z | Z | Z | Z |      SIG      |
+    //  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+    //  \                              / \                             /
+    //   ---------- mFlags[0] ---------   -------- mFlags[1] ----------
+
+    uint8_t mFlags[2];
+    uint8_t mProtocol;
+    uint8_t mAlgorithm;
+    // Followed by the public key
+
+} OT_TOOL_PACKED_END;
+
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+OT_TOOL_PACKED_BEGIN
+class Ecdsa256KeyRecord : public KeyRecord, public Clearable<Ecdsa256KeyRecord>, public Equatable<Ecdsa256KeyRecord>
+{
+public:
+    /**
+     * This method initializes the KEY Resource Record to ECDSA with curve P-256.
+     *
+     * Other record fields (TTL, length, flags, protocol) remain unchanged/uninitialized.
+     *
+     */
+    void Init(void);
+
+    /**
+     * This method tells whether this is a valid ECDSA DNSKEY with curve P-256.
+     *
+     * @returns  A boolean that indicates whether this is a valid ECDSA DNSKEY RR with curve P-256.
+     *
+     */
+    bool IsValid(void) const;
+
+    /**
+     * This method returns the ECDSA P-256 public kek.
+     *
+     * @returns  A reference to the public key.
+     *
+     */
+    const Crypto::Ecdsa::P256::PublicKey &GetKey(void) const { return mKey; }
+
+private:
+    Crypto::Ecdsa::P256::PublicKey mKey;
+} OT_TOOL_PACKED_END;
+#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+
+/**
+ * This class implements Resource Record body format of SIG type (RFC 2535 - section-4.1).
+ *
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class SigRecord : public ResourceRecord, public Clearable<SigRecord>
+{
+public:
+    enum : uint16_t
+    {
+        kType = kTypeSig, ///< The SIG record type.
+    };
+
+    /**
+     * This method initializes the SIG Resource Record by setting its type and class.
+     *
+     * Other record fields (TTL, length, ...) remain unchanged/uninitialized.
+     *
+     * SIG(0) requires SIG RR to set class field as ANY or `kClassAny` (RFC 2931 - section 3).
+     *
+     * @param[in] aClass  The class of the resource record.
+     *
+     */
+    void Init(uint16_t aClass) { ResourceRecord::Init(kTypeSig, aClass); }
+
+    /**
+     * This method tells whether the SIG record is valid.
+     *
+     * @returns  TRUE if this is a valid SIG record, FALSE if not a valid SIG record.
+     *
+     */
+    bool IsValid(void) const;
+
+    /**
+     * This method returns the SIG record's type-covered value.
+     *
+     * @returns The type-covered value.
+     *
+     */
+    uint16_t GetTypeCovered(void) const { return HostSwap16(mTypeCovered); }
+
+    /**
+     * This method sets the SIG record's type-covered value.
+     *
+     * @param[in]  aTypeCovered  The type-covered value.
+     *
+     */
+    void SetTypeCovered(uint8_t aTypeCovered) { mTypeCovered = HostSwap16(aTypeCovered); }
+
+    /**
+     * This method returns the SIG record's algorithm value.
+     *
+     * @returns The algorithm value.
+     *
+     */
+    uint8_t GetAlgorithm(void) const { return mAlgorithm; }
+
+    /**
+     * This method sets the SIG record's algorithm value.
+     *
+     * @param[in]  aAlgorithm  The algorithm value.
+     *
+     */
+    void SetAlgorithm(uint8_t aAlgorithm) { mAlgorithm = aAlgorithm; }
+
+    /**
+     * This method returns the SIG record's labels-count (number of labels, not counting null label, in the original
+     * name of the owner).
+     *
+     * @returns The labels-count value.
+     *
+     */
+    uint8_t GetLabels(void) const { return mLabels; }
+
+    /**
+     * This method sets the SIG record's labels-count (number of labels, not counting null label, in the original
+     * name of the owner).
+     *
+     * @param[in]  aLabels  The labels-count value.
+     *
+     */
+    void SetLabels(uint8_t aLabels) { mLabels = aLabels; }
+
+    /**
+     * This method returns the SIG record's original TTL value.
+     *
+     * @returns The original TTL value.
+     *
+     */
+    uint32_t GetOriginalTtl(void) const { return HostSwap32(mOriginalTtl); }
+
+    /**
+     * This method sets the SIG record's original TTL value.
+     *
+     * @param[in]  aOriginalTtl  The original TTL value.
+     *
+     */
+    void SetOriginalTtl(uint32_t aOriginalTtl) { mOriginalTtl = HostSwap32(aOriginalTtl); }
+
+    /**
+     * This method returns the SIG record's expiration time value.
+     *
+     * @returns The expiration time value (seconds since Jan 1, 1970).
+     *
+     */
+    uint32_t GetExpiration(void) const { return HostSwap32(mExpiration); }
+
+    /**
+     * This method sets the SIG record's expiration time value.
+     *
+     * @param[in]  aExpiration  The expiration time value (seconds since Jan 1, 1970).
+     *
+     */
+    void SetExpiration(uint32_t aExpiration) { mExpiration = HostSwap32(aExpiration); }
+
+    /**
+     * This method returns the SIG record's inception time value.
+     *
+     * @returns The inception time value (seconds since Jan 1, 1970).
+     *
+     */
+    uint32_t GetInception(void) const { return HostSwap32(mInception); }
+
+    /**
+     * This method sets the SIG record's inception time value.
+     *
+     * @param[in]  aInception  The inception time value (seconds since Jan 1, 1970).
+     *
+     */
+    void SetInception(uint32_t aInception) { mInception = HostSwap32(aInception); }
+
+    /**
+     * This method returns the SIG record's key tag value.
+     *
+     * @returns The key tag value.
+     *
+     */
+    uint16_t GetKeyTag(void) const { return HostSwap16(mKeyTag); }
+
+    /**
+     * This method sets the SIG record's key tag value.
+     *
+     * @param[in]  aKeyTag  The key tag value.
+     *
+     */
+    void SetKeyTag(uint16_t aKeyTag) { mKeyTag = HostSwap16(aKeyTag); }
+
+    /**
+     * This method returns a pointer to the start of the record data fields.
+     *
+     * @returns A pointer to the start of the record data fields.
+     *
+     */
+    const uint8_t *GetRecordData(void) const { return reinterpret_cast<const uint8_t *>(&mTypeCovered); }
+
+    /**
+     * This method parses and reads the SIG signer name from a message.
+     *
+     * @param[in]     aMessage          The message to read from. `aMessage.GetOffset()` MUST point to the start of DNS
+     *                                  header.
+     * @param[inout]  aOffset           On input, the offset in @p aMessage to start of signer name field.
+     *                                  On exit when successfully read, @p aOffset is updated to point to the byte
+     *                                  after the name field (i.e., start of signature field).
+     * @param[out]    aNameBuffer       A pointer to a char array to output the read name as a null-terminated C string
+     *                                  (MUST NOT be nullptr).
+     * @param[in]     aNameBufferSize   The size of @p aNameBuffer.
+     *
+     * @retval kErrorNone           The name was read successfully. @p aOffset and @p aNameBuffer are updated.
+     * @retval kErrorParse          The SIG record in @p aMessage could not be parsed (invalid format).
+     * @retval kErrorNoBufs         Name could not fit in @p aNameBufferSize chars.
+     *
+     */
+    Error ReadSignerName(const Message &aMessage, uint16_t &aOffset, char *aNameBuffer, uint16_t aNameBufferSize) const
+    {
+        return ResourceRecord::ReadName(aMessage, aOffset, /* aStartOffset */ aOffset - sizeof(SigRecord), aNameBuffer,
+                                        aNameBufferSize,
+                                        /* aSkipRecord */ false);
+    }
+
+private:
+    uint16_t mTypeCovered; // type of the other RRs covered by this SIG. set to zero for SIG(0).
+    uint8_t  mAlgorithm;   // Algorithm number (see `KeyRecord` enumeration).
+    uint8_t  mLabels;      // Number of labels (not counting null label) in the original name of the owner of RR.
+    uint32_t mOriginalTtl; // Original time-to-live (should set to zero for SIG(0)).
+    uint32_t mExpiration;  // Signature expiration time (seconds since Jan 1, 1970).
+    uint32_t mInception;   // Signature inception time (seconds since Jan 1, 1970).
+    uint16_t mKeyTag;      // Key tag.
+    // Followed by signer name fields and signature fields
+} OT_TOOL_PACKED_END;
+
+/**
+ * This class implements DNS OPT Pseudo Resource Record header for EDNS(0) (RFC 6891 - Section 6.1).
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class OptRecord : public ResourceRecord
+{
+public:
+    enum : uint16_t
+    {
+        kType = kTypeOpt, ///< The OPT record type.
+    };
+
+    /**
+     * This method initializes the OPT Resource Record by setting its type and clearing extended Response Code, version
+     * and all flags.
+     *
+     * Other record fields (UDP payload size, length) remain unchanged/uninitialized.
+     *
+     */
+    void Init(void)
+    {
+        SetType(kTypeOpt);
+        SetTtl(0);
+    }
+
+    /**
+     * This method gets the requester's UDP payload size (the number of bytes of the largest UDP payload that can be
+     * delivered in the requester's network).
+     *
+     * The field is encoded in the CLASS field.
+     *
+     * @returns The UDP payload size.
+     *
+     */
+    uint16_t GetUdpPayloadSize(void) const { return GetClass(); }
+
+    /**
+     * This method gets the requester's UDP payload size (the number of bytes of the largest UDP payload that can be
+     * delivered in the requester's network).
+     *
+     * @param[in] aPayloadSize  The UDP payload size.
+     *
+     */
+    void SetUdpPayloadSize(uint16_t aPayloadSize) { SetClass(aPayloadSize); }
+
+    /**
+     * This method gets the upper 8-bit of the extended 12-bit Response Code.
+     *
+     * Value of 0 indicates that an unextended Response code is in use.
+     *
+     * @return The upper 8-bit of the extended 12-bit Response Code.
+     *
+     */
+    uint8_t GetExtendedResponseCode(void) const { return GetTtlByteAt(kExtRCodeByteIndex); }
+
+    /**
+     * This method sets the upper 8-bit of the extended 12-bit Response Code.
+     *
+     * Value of 0 indicates that an unextended Response code is in use.
+     *
+     * @param[in] aExtendedResponse The upper 8-bit of the extended 12-bit Response Code.
+     *
+     */
+    void SetExtnededResponseCode(uint8_t aExtendedResponse) { GetTtlByteAt(kExtRCodeByteIndex) = aExtendedResponse; }
+
+    /**
+     * This method gets the Version field.
+     *
+     * @returns The version.
+     *
+     */
+    uint8_t GetVersion(void) const { return GetTtlByteAt(kVersionByteIndex); }
+
+    /**
+     * This method set the Version field.
+     *
+     * @param[in] aVersion  The version.
+     *
+     */
+    void SetVersion(uint8_t aVersion) { GetTtlByteAt(kVersionByteIndex) = aVersion; }
+
+    /**
+     * This method indicates whether the DNSSEC OK flag is set or not.
+     *
+     * @returns True if DNSSEC OK flag is set in the header, false otherwise.
+     *
+     */
+    bool IsDnsSecurityFlagSet(void) const { return (GetTtlByteAt(kFlagByteIndex) & kDnsSecFlag) != 0; }
+
+    /**
+     * This method clears the DNSSEC OK bit flag.
+     *
+     */
+    void ClearDnsSecurityFlag(void) { GetTtlByteAt(kFlagByteIndex) &= ~kDnsSecFlag; }
+
+    /**
+     * This method sets the DNSSEC OK bit flag.
+     *
+     */
+    void SetDnsSecurityFlag(void) { GetTtlByteAt(kFlagByteIndex) |= kDnsSecFlag; }
+
+private:
+    // The OPT RR re-purposes the existing CLASS and TTL fields in the
+    // RR. The CLASS field (`uint16_t`) is used for requester UDP
+    // payload size. The TTL field is used for extended Response Code,
+    // version and flags as follows:
+    //
+    //    0   1   2   3   4   5   6   7   8   9   0   1   2   3   4   5
+    //  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+    //  |         EXTENDED-RCODE        |            VERSION            |
+    //  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+    //  | DO|                Z          |             Z                 |
+    //  +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
+    //
+    // The variable data part of OPT RR can contain zero of more `Option`.
+
+    enum : uint8_t
+    {
+        kExtRCodeByteIndex = 0,      // Byte index of Extended RCODE within the TTL field.
+        kVersionByteIndex  = 1,      // Byte index of Version within the TTL field.
+        kFlagByteIndex     = 2,      // Byte index of flag byte within the TTL field.
+        kDnsSecFlag        = 1 << 7, // DNSSec OK bit flag.
+    };
+
+    uint8_t  GetTtlByteAt(uint8_t aIndex) const { return reinterpret_cast<const uint8_t *>(&mTtl)[aIndex]; }
+    uint8_t &GetTtlByteAt(uint8_t aIndex) { return reinterpret_cast<uint8_t *>(&mTtl)[aIndex]; }
+
+} OT_TOOL_PACKED_END;
+
+/**
+ * This class implements the body of an Option in OPT Pseudo Resource Record (RFC 6981 - Section 6.1).
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class Option
+{
+public:
+    /**
+     * This enumeration defines option code values.
+     *
+     */
+    enum : uint16_t
+    {
+        kUpdateLease = 2, ///< Update lease option code.
+    };
+
+    /**
+     * This method returns the option code value.
+     *
+     * @returns The option code value.
+     *
+     */
+    uint16_t GetOptionCode(void) const { return HostSwap16(mOptionCode); }
+
+    /**
+     * This method sets the option code value.
+     *
+     * @param[in]  aOptionCode  The option code value.
+     *
+     */
+    void SetOptionCode(uint16_t aOptionCode) { mOptionCode = HostSwap16(aOptionCode); }
+
+    /**
+     * This method returns the option length value.
+     *
+     * @returns The option length (size of option data in bytes).
+     *
+     */
+    uint16_t GetOptionLength(void) const { return HostSwap16(mOptionLength); }
+
+    /**
+     * This method sets the option length value.
+     *
+     * @param[in]  aOptionLength  The option length (size of option data in bytes).
+     *
+     */
+    void SetOptionLength(uint16_t aOptionLength) { mOptionLength = HostSwap16(aOptionLength); }
+
+    /**
+     * This method returns the size of (number of bytes) in the Option and its data.
+     *
+     * @returns Size (number of bytes) of the Option its data section.
+     *
+     */
+    uint32_t GetSize(void) const { return sizeof(Option) + GetOptionLength(); }
+
+private:
+    uint16_t mOptionCode;
+    uint16_t mOptionLength;
+    // Followed by Option data (varies per option code).
+
+} OT_TOOL_PACKED_END;
+
+/**
+ * This class implements an Update Lease Option body.
+ *
+ * This implementation is intended for use in Dynamic DNS Update Lease Requests and Responses as specified in
+ * https://tools.ietf.org/html/draft-sekar-dns-ul-02.
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class LeaseOption : public Option
+{
+public:
+    enum : uint16_t
+    {
+        kOptionLength = sizeof(uint32_t) + sizeof(uint32_t), ///< Option length (lease and key lease values)
+    };
+
+    /**
+     * This method initialize the Update Lease Option by setting the Option Code and Option Length.
+     *
+     * The lease and key lease intervals remain unchanged/uninitialized.
+     *
+     */
+    void Init(void)
+    {
+        SetOptionCode(kUpdateLease);
+        SetOptionLength(kOptionLength);
+    }
+
+    /**
+     * This method tells whether this is a valid Lease Option.
+     *
+     * @returns  TRUE if this is a valid Lease Option, FALSE if not a valid Lease Option.
+     *
+     */
+    bool IsValid(void) const;
+
+    /**
+     * This method returns the Update Lease OPT record's lease interval value.
+     *
+     * @returns The lease interval value (in seconds).
+     *
+     */
+    uint32_t GetLeaseInterval(void) const { return HostSwap32(mLeaseInterval); }
+
+    /**
+     * This method sets the Update Lease OPT record's lease interval value.
+     *
+     * @param[in]  aLeaseInterval  The lease interval value.
+     *
+     */
+    void SetLeaseInterval(uint32_t aLeaseInterval) { mLeaseInterval = HostSwap32(aLeaseInterval); }
+
+    /**
+     * This method returns the Update Lease OPT record's key lease interval value.
+     *
+     * @returns The key lease interval value (in seconds).
+     *
+     */
+    uint32_t GetKeyLeaseInterval(void) const { return HostSwap32(mKeyLeaseInterval); }
+
+    /**
+     * This method sets the Update Lease OPT record's key lease interval value.
+     *
+     * @param[in]  aKeyLeaseInterval  The key lease interval value (in seconds).
+     *
+     */
+    void SetKeyLeaseInterval(uint32_t aKeyLeaseInterval) { mKeyLeaseInterval = HostSwap32(aKeyLeaseInterval); }
+
+private:
+    uint32_t mLeaseInterval;
+    uint32_t mKeyLeaseInterval;
+} OT_TOOL_PACKED_END;
+
+/**
+ * This class implements Question format.
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class Question
+{
+public:
+    /**
+     * Default constructor for Question
+     *
+     */
+    Question(void) = default;
+
+    /**
+     * Constructor for Question.
+     *
+     */
+    explicit Question(uint16_t aType, uint16_t aClass = ResourceRecord::kClassInternet)
+    {
+        SetType(aType);
+        SetClass(aClass);
+    }
+
+    /**
+     * This method returns the type of the question.
+     *
+     * @returns The type of the question.
+     *
+     */
+    uint16_t GetType(void) const { return HostSwap16(mType); }
+
+    /**
+     * This method sets the type of the question.
+     *
+     * @param[in]  aType The type of the question.
+     *
+     */
+    void SetType(uint16_t aType) { mType = HostSwap16(aType); }
+
+    /**
+     * This method returns the class of the question.
+     *
+     * @returns The class of the question.
+     *
+     */
+    uint16_t GetClass(void) const { return HostSwap16(mClass); }
+
+    /**
+     * This method sets the class of the question.
+     *
+     * @param[in]  aClass The class of the question.
+     *
+     */
+    void SetClass(uint16_t aClass) { mClass = HostSwap16(aClass); }
+
+private:
+    uint16_t mType;  // The type of the data in question section.
+    uint16_t mClass; // The class of the data in question section.
+} OT_TOOL_PACKED_END;
+
+/**
+ * This class implements Zone section body for DNS Update (RFC 2136 - section 2.3).
+ *
+ */
+OT_TOOL_PACKED_BEGIN
+class Zone : public Question
+{
+public:
+    /**
+     * Constructor for Zone.
+     *
+     * @param[in] aClass  The class of the zone (default is `kClassInternet`).
+     *
+     */
+    explicit Zone(uint16_t aClass = ResourceRecord::kClassInternet)
+        : Question(ResourceRecord::kTypeSoa, aClass)
+    {
+    }
+} OT_TOOL_PACKED_END;
+
+/**
+ * @}
+ *
+ */
+
+} // namespace Dns
+} // namespace ot
+
+#endif // DNS_HEADER_HPP_
diff --git a/src/core/net/dnssd_server.cpp b/src/core/net/dnssd_server.cpp
new file mode 100644
index 0000000..15aa100
--- /dev/null
+++ b/src/core/net/dnssd_server.cpp
@@ -0,0 +1,704 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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
+ *   This file implements the DNS-SD server.
+ */
+
+#include "dnssd_server.hpp"
+
+#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
+
+#include "common/code_utils.hpp"
+#include "common/debug.hpp"
+#include "common/instance.hpp"
+#include "common/locator-getters.hpp"
+#include "common/logging.hpp"
+#include "net/srp_server.hpp"
+#include "net/udp6.hpp"
+
+using ot::Encoding::BigEndian::HostSwap16;
+
+namespace ot {
+namespace Dns {
+namespace ServiceDiscovery {
+
+const char Server::kDnssdProtocolUdp[4] = {'_', 'u', 'd', 'p'};
+const char Server::kDnssdProtocolTcp[4] = {'_', 't', 'c', 'p'};
+const char Server::kDefaultDomainName[] = "default.service.arpa.";
+
+Server::Server(Instance &aInstance)
+    : InstanceLocator(aInstance)
+    , mSocket(aInstance)
+{
+}
+
+Error Server::Start(void)
+{
+    Error error = kErrorNone;
+
+    VerifyOrExit(!IsRunning());
+
+    SuccessOrExit(error = mSocket.Open(&Server::HandleUdpReceive, this));
+    SuccessOrExit(error = mSocket.Bind(kPort, OT_NETIF_UNSPECIFIED));
+
+exit:
+    otLogInfoDns("[server] started: %s", ErrorToString(error));
+    return error;
+}
+
+void Server::Stop(void)
+{
+    IgnoreError(mSocket.Close());
+    otLogInfoDns("[server] stopped");
+}
+
+void Server::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
+{
+    static_cast<Server *>(aContext)->HandleUdpReceive(*static_cast<Message *>(aMessage),
+                                                      *static_cast<const Ip6::MessageInfo *>(aMessageInfo));
+}
+
+void Server::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
+{
+    Error    error = kErrorNone;
+    Header   requestHeader;
+    Message *responseMessage = nullptr;
+
+    SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), requestHeader));
+    VerifyOrExit(requestHeader.GetType() == Header::kTypeQuery, error = kErrorDrop);
+
+    responseMessage = mSocket.NewMessage(0);
+    VerifyOrExit(responseMessage != nullptr, error = kErrorNoBufs);
+
+    // Allocate space for DNS header
+    SuccessOrExit(error = responseMessage->SetLength(sizeof(Header)));
+
+    // ProcessQuery is assumed to always prepare the response DNS message and header properly even in the case of system
+    // failures (e.g. no more buffers).
+    ProcessQuery(aMessage, *responseMessage, requestHeader);
+
+    error = mSocket.SendTo(*responseMessage, aMessageInfo);
+
+exit:
+    FreeMessageOnError(responseMessage, error);
+}
+
+void Server::ProcessQuery(Message &aMessage, Message &aResponse, const Header &aRequestHeader)
+{
+    Header           responseHeader;
+    uint16_t         readOffset;
+    Question         question;
+    char             name[Dns::Name::kMaxNameSize];
+    NameCompressInfo compressInfo(kDefaultDomainName);
+    Header::Response response          = Header::Response::kResponseSuccess;
+    Error            error             = kErrorNone;
+    uint8_t          resolveAdditional = kResolveAdditionalAll;
+
+    // Setup initial DNS response header
+    responseHeader.Clear();
+    responseHeader.SetType(Header::kTypeResponse);
+    responseHeader.SetMessageId(aRequestHeader.GetMessageId());
+
+    // Validate the query
+    VerifyOrExit(aRequestHeader.GetQueryType() == Header::kQueryTypeStandard,
+                 response = Header::kResponseNotImplemented);
+    VerifyOrExit(!aRequestHeader.IsTruncationFlagSet(), response = Header::kResponseFormatError);
+    VerifyOrExit(aRequestHeader.GetQuestionCount() > 0, response = Header::kResponseFormatError);
+
+    readOffset = sizeof(Header);
+
+    // Check and append the questions
+    for (uint16_t i = 0; i < aRequestHeader.GetQuestionCount(); i++)
+    {
+        NameComponentsOffsetInfo nameComponentsOffsetInfo;
+
+        VerifyOrExit(kErrorNone == Dns::Name::ReadName(aMessage, readOffset, name, sizeof(name)),
+                     response = Header::kResponseFormatError);
+        VerifyOrExit(kErrorNone == aMessage.Read(readOffset, question), response = Header::kResponseFormatError);
+        readOffset += sizeof(question);
+
+        uint16_t qtype = question.GetType();
+
+        VerifyOrExit(qtype == ResourceRecord::kTypePtr || qtype == ResourceRecord::kTypeSrv ||
+                         qtype == ResourceRecord::kTypeTxt || qtype == ResourceRecord::kTypeAaaa,
+                     response = Header::kResponseNotImplemented);
+
+        VerifyOrExit(kErrorNone == FindNameComponents(name, compressInfo.GetDomainName(), nameComponentsOffsetInfo),
+                     response = Header::kResponseNameError);
+
+        switch (question.GetType())
+        {
+        case ResourceRecord::kTypePtr:
+            VerifyOrExit(nameComponentsOffsetInfo.IsServiceName(), response = Header::kResponseNameError);
+            break;
+        case ResourceRecord::kTypeSrv:
+            VerifyOrExit(nameComponentsOffsetInfo.IsServiceInstanceName(), response = Header::kResponseNameError);
+            resolveAdditional &= ~kResolveAdditionalSrv;
+            break;
+        case ResourceRecord::kTypeTxt:
+            VerifyOrExit(nameComponentsOffsetInfo.IsServiceInstanceName(), response = Header::kResponseNameError);
+            resolveAdditional &= ~kResolveAdditionalTxt;
+            break;
+        case ResourceRecord::kTypeAaaa:
+            VerifyOrExit(nameComponentsOffsetInfo.IsHostName(), response = Header::kResponseNameError);
+            resolveAdditional &= ~kResolveAdditionalAaaa;
+            break;
+        default:
+            ExitNow(response = Header::kResponseNotImplemented);
+        }
+
+        SuccessOrExit(error = AppendQuestion(name, question, aResponse, compressInfo));
+    }
+
+    responseHeader.SetQuestionCount(aRequestHeader.GetQuestionCount());
+
+    // Answer the questions
+    readOffset = sizeof(Header);
+    for (uint16_t i = 0; i < aRequestHeader.GetQuestionCount(); i++)
+    {
+        uint8_t resolveKind = kResolveAnswer;
+
+        IgnoreError(Dns::Name::ReadName(aMessage, readOffset, name, sizeof(name)));
+        IgnoreError(aMessage.Read(readOffset, question));
+        readOffset += sizeof(question);
+
+        response = ResolveQuestion(name, question, responseHeader, aResponse, resolveKind, compressInfo);
+
+        otLogInfoDns("[server] ANSWER: TRANSACTION=0x%04x, QUESTION=[%s %d %d], RCODE=%d",
+                     aRequestHeader.GetMessageId(), name, question.GetClass(), question.GetType(), response);
+    }
+
+    // Answer the questions with additional RRs if required
+    VerifyOrExit(resolveAdditional != kResolveNone);
+
+    readOffset = sizeof(Header);
+    for (uint16_t i = 0; i < aRequestHeader.GetQuestionCount(); i++)
+    {
+        IgnoreError(Dns::Name::ReadName(aMessage, readOffset, name, sizeof(name)));
+        IgnoreError(aMessage.Read(readOffset, question));
+        readOffset += sizeof(question);
+
+        VerifyOrExit(Header::kResponseServerFailure !=
+                         ResolveQuestion(name, question, responseHeader, aResponse, resolveAdditional, compressInfo),
+                     response = Header::kResponseServerFailure);
+
+        otLogInfoDns("[server] ADDITIONAL: TRANSACTION=0x%04x, QUESTION=[%s %d %d], RCODE=%d",
+                     aRequestHeader.GetMessageId(), name, question.GetClass(), question.GetType(), response);
+    }
+
+exit:
+    response = (error == kErrorNone) ? response : Header::Response::kResponseServerFailure;
+
+    if (response == Header::Response::kResponseServerFailure)
+    {
+        otLogWarnDns("[server] failed to handle DNS query due to server failure");
+        responseHeader.SetQuestionCount(0);
+        responseHeader.SetAnswerCount(0);
+        responseHeader.SetAdditionalRecordCount(0);
+        IgnoreError(aResponse.SetLength(sizeof(Header)));
+    }
+
+    responseHeader.SetResponseCode(response);
+    aResponse.Write(0, responseHeader);
+}
+
+Header::Response Server::ResolveQuestion(const char *      aName,
+                                         const Question &  aQuestion,
+                                         Header &          aResponseHeader,
+                                         Message &         aResponseMessage,
+                                         uint8_t           aResolveKind,
+                                         NameCompressInfo &aCompressInfo)
+{
+    OT_UNUSED_VARIABLE(aName);
+    OT_UNUSED_VARIABLE(aQuestion);
+    OT_UNUSED_VARIABLE(aResponseHeader);
+    OT_UNUSED_VARIABLE(aResponseMessage);
+    OT_UNUSED_VARIABLE(aCompressInfo);
+
+    Header::Response response = Header::kResponseNameError;
+
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+    response = ResolveQuestionBySrp(aName, aQuestion, aResponseHeader, aResponseMessage, aResolveKind, aCompressInfo);
+#endif
+
+    return response;
+}
+
+Error Server::AppendQuestion(const char *      aName,
+                             const Question &  aQuestion,
+                             Message &         aMessage,
+                             NameCompressInfo &aCompressInfo)
+{
+    Error error = kErrorNone;
+
+    switch (aQuestion.GetType())
+    {
+    case ResourceRecord::kTypePtr:
+        SuccessOrExit(error = AppendServiceName(aMessage, aName, aCompressInfo));
+        break;
+    case ResourceRecord::kTypeSrv:
+    case ResourceRecord::kTypeTxt:
+        SuccessOrExit(error = AppendInstanceName(aMessage, aName, aCompressInfo));
+        break;
+    case ResourceRecord::kTypeAaaa:
+        SuccessOrExit(error = AppendHostName(aMessage, aName, aCompressInfo));
+        break;
+    default:
+        OT_ASSERT(false);
+    }
+
+    error = aMessage.Append(aQuestion);
+
+exit:
+    return error;
+}
+
+Error Server::AppendPtrRecord(Message &         aMessage,
+                              const char *      aServiceName,
+                              const char *      aInstanceName,
+                              uint32_t          aTtl,
+                              NameCompressInfo &aCompressInfo)
+{
+    Error     error;
+    PtrRecord ptrRecord;
+    uint16_t  recordOffset;
+
+    ptrRecord.Init();
+    ptrRecord.SetTtl(aTtl);
+
+    SuccessOrExit(error = AppendServiceName(aMessage, aServiceName, aCompressInfo));
+
+    recordOffset = aMessage.GetLength();
+    SuccessOrExit(error = aMessage.SetLength(recordOffset + sizeof(ptrRecord)));
+
+    SuccessOrExit(error = AppendInstanceName(aMessage, aInstanceName, aCompressInfo));
+
+    ptrRecord.SetLength(aMessage.GetLength() - (recordOffset + sizeof(ResourceRecord)));
+    aMessage.Write(recordOffset, ptrRecord);
+
+exit:
+    return error;
+}
+
+Error Server::AppendSrvRecord(Message &         aMessage,
+                              const char *      aInstanceName,
+                              const char *      aHostName,
+                              uint32_t          aTtl,
+                              uint16_t          aPriority,
+                              uint16_t          aWeight,
+                              uint16_t          aPort,
+                              NameCompressInfo &aCompressInfo)
+{
+    SrvRecord srvRecord;
+    Error     error = kErrorNone;
+    uint16_t  recordOffset;
+
+    srvRecord.Init();
+    srvRecord.SetTtl(aTtl);
+    srvRecord.SetPriority(aPriority);
+    srvRecord.SetWeight(aWeight);
+    srvRecord.SetPort(aPort);
+
+    SuccessOrExit(error = AppendInstanceName(aMessage, aInstanceName, aCompressInfo));
+
+    recordOffset = aMessage.GetLength();
+    SuccessOrExit(error = aMessage.SetLength(recordOffset + sizeof(srvRecord)));
+
+    SuccessOrExit(error = AppendHostName(aMessage, aHostName, aCompressInfo));
+
+    srvRecord.SetLength(aMessage.GetLength() - (recordOffset + sizeof(ResourceRecord)));
+    aMessage.Write(recordOffset, srvRecord);
+
+exit:
+    return error;
+}
+
+Error Server::AppendAaaaRecord(Message &           aMessage,
+                               const char *        aHostName,
+                               const Ip6::Address &aAddress,
+                               uint32_t            aTtl,
+                               NameCompressInfo &  aCompressInfo)
+{
+    AaaaRecord aaaaRecord;
+    Error      error;
+
+    aaaaRecord.Init();
+    aaaaRecord.SetTtl(aTtl);
+    aaaaRecord.SetAddress(aAddress);
+
+    SuccessOrExit(error = AppendHostName(aMessage, aHostName, aCompressInfo));
+    error = aMessage.Append(aaaaRecord);
+
+exit:
+    return error;
+}
+
+Error Server::AppendServiceName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo)
+{
+    Error    error;
+    uint16_t serviceCompressOffset = aCompressInfo.GetServiceNameOffset(aName);
+
+    if (serviceCompressOffset != NameCompressInfo::kUnknownOffset)
+    {
+        error = Dns::Name::AppendPointerLabel(serviceCompressOffset, aMessage);
+    }
+    else
+    {
+        uint8_t  domainStart          = static_cast<uint8_t>(StringLength(aName, Name::kMaxNameSize - 1) -
+                                                   StringLength(aCompressInfo.GetDomainName(), Name::kMaxNameSize - 1));
+        uint16_t domainCompressOffset = aCompressInfo.GetDomainNameOffset();
+
+        serviceCompressOffset = aMessage.GetLength();
+        aCompressInfo.SetServiceNameOffset(serviceCompressOffset, aName);
+
+        if (domainCompressOffset == NameCompressInfo::kUnknownOffset)
+        {
+            aCompressInfo.SetDomainNameOffset(serviceCompressOffset + domainStart);
+            error = Dns::Name::AppendName(aName, aMessage);
+        }
+        else
+        {
+            SuccessOrExit(error = Dns::Name::AppendMultipleLabels(aName, domainStart, aMessage));
+            error = Dns::Name::AppendPointerLabel(domainCompressOffset, aMessage);
+        }
+    }
+
+exit:
+    return error;
+}
+
+Error Server::AppendInstanceName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo)
+{
+    Error error;
+
+    uint16_t instanceCompressOffset = aCompressInfo.GetInstanceNameOffset(aName);
+
+    if (instanceCompressOffset != NameCompressInfo::kUnknownOffset)
+    {
+        error = Dns::Name::AppendPointerLabel(instanceCompressOffset, aMessage);
+    }
+    else
+    {
+        NameComponentsOffsetInfo nameComponentsInfo;
+
+        IgnoreError(FindNameComponents(aName, aCompressInfo.GetDomainName(), nameComponentsInfo));
+        OT_ASSERT(nameComponentsInfo.IsServiceInstanceName());
+
+        aCompressInfo.SetInstanceNameOffset(aMessage.GetLength(), aName);
+
+        // Append the instance name as one label
+        SuccessOrExit(error = Dns::Name::AppendLabel(aName, nameComponentsInfo.mServiceOffset - 1, aMessage));
+
+        {
+            const char *serviceName           = aName + nameComponentsInfo.mServiceOffset;
+            uint16_t    serviceCompressOffset = aCompressInfo.GetServiceNameOffset(serviceName);
+
+            if (serviceCompressOffset != NameCompressInfo::kUnknownOffset)
+            {
+                error = Dns::Name::AppendPointerLabel(serviceCompressOffset, aMessage);
+            }
+            else
+            {
+                aCompressInfo.SetServiceNameOffset(aMessage.GetLength(), serviceName);
+                error = Dns::Name::AppendName(serviceName, aMessage);
+            }
+        }
+    }
+
+exit:
+    return error;
+}
+
+Error Server::AppendHostName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo)
+{
+    Error    error;
+    uint16_t hostCompressOffset = aCompressInfo.GetHostNameOffset(aName);
+
+    if (hostCompressOffset != NameCompressInfo::kUnknownOffset)
+    {
+        error = Dns::Name::AppendPointerLabel(hostCompressOffset, aMessage);
+    }
+    else
+    {
+        uint8_t  domainStart          = static_cast<uint8_t>(StringLength(aName, Name::kMaxNameLength) -
+                                                   StringLength(aCompressInfo.GetDomainName(), Name::kMaxNameSize - 1));
+        uint16_t domainCompressOffset = aCompressInfo.GetDomainNameOffset();
+
+        hostCompressOffset = aMessage.GetLength();
+        aCompressInfo.SetHostNameOffset(hostCompressOffset, aName);
+
+        if (domainCompressOffset == NameCompressInfo::kUnknownOffset)
+        {
+            aCompressInfo.SetDomainNameOffset(hostCompressOffset + domainStart);
+            error = Dns::Name::AppendName(aName, aMessage);
+        }
+        else
+        {
+            SuccessOrExit(error = Dns::Name::AppendMultipleLabels(aName, domainStart, aMessage));
+            error = Dns::Name::AppendPointerLabel(domainCompressOffset, aMessage);
+        }
+    }
+
+exit:
+    return error;
+}
+
+void Server::IncResourceRecordCount(Header &aHeader, bool aAdditional)
+{
+    if (aAdditional)
+    {
+        aHeader.SetAdditionalRecordCount(aHeader.GetAdditionalRecordCount() + 1);
+    }
+    else
+    {
+        aHeader.SetAnswerCount(aHeader.GetAnswerCount() + 1);
+    }
+}
+
+Error Server::FindNameComponents(const char *aName, const char *aDomain, NameComponentsOffsetInfo &aInfo)
+{
+    uint8_t nameLen   = static_cast<uint8_t>(StringLength(aName, Name::kMaxNameLength));
+    uint8_t domainLen = static_cast<uint8_t>(StringLength(aDomain, Name::kMaxNameLength));
+    Error   error     = kErrorNone;
+    uint8_t labelBegin, labelEnd;
+
+    VerifyOrExit(Dns::Name::IsSubDomainOf(aName, aDomain), error = kErrorInvalidArgs);
+
+    labelBegin          = nameLen - domainLen;
+    aInfo.mDomainOffset = labelBegin;
+
+    while (true)
+    {
+        error = FindPreviousLabel(aName, labelBegin, labelEnd);
+
+        VerifyOrExit(error == kErrorNone, error = (error == kErrorNotFound ? kErrorNone : error));
+
+        if (labelEnd == labelBegin + kProtocolLabelLength &&
+            (memcmp(&aName[labelBegin], kDnssdProtocolUdp, kProtocolLabelLength) == 0 ||
+             memcmp(&aName[labelBegin], kDnssdProtocolTcp, kProtocolLabelLength) == 0))
+        {
+            // <Protocol> label found
+            aInfo.mProtocolOffset = labelBegin;
+            break;
+        }
+    }
+
+    // Get service label <Service>
+    error = FindPreviousLabel(aName, labelBegin, labelEnd);
+    VerifyOrExit(error == kErrorNone, error = (error == kErrorNotFound ? kErrorNone : error));
+
+    aInfo.mServiceOffset = labelBegin;
+
+    // Treat everything before <Service> as <Instance> label
+    error = FindPreviousLabel(aName, labelBegin, labelEnd);
+    VerifyOrExit(error == kErrorNone, error = (error == kErrorNotFound ? kErrorNone : error));
+
+    aInfo.mInstanceOffset = 0;
+
+exit:
+    return error;
+}
+
+Error Server::FindPreviousLabel(const char *aName, uint8_t &aStart, uint8_t &aStop)
+{
+    // This method finds the previous label before the current label (whose start index is @p aStart), and updates @p
+    // aStart to the start index of the label and @p aStop to the index of the dot just after the label.
+    // @note The input value of @p aStop does not matter because it is only used to output.
+
+    Error   error = kErrorNone;
+    uint8_t start = aStart;
+    uint8_t end;
+
+    VerifyOrExit(start > 0, error = kErrorNotFound);
+    VerifyOrExit(aName[--start] == Name::kLabelSeperatorChar, error = kErrorInvalidArgs);
+
+    end = start;
+    while (start > 0 && aName[start - 1] != Name::kLabelSeperatorChar)
+    {
+        start--;
+    }
+
+    VerifyOrExit(start < end, error = kErrorInvalidArgs);
+
+    aStart = start;
+    aStop  = end;
+
+exit:
+    return error;
+}
+
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+Header::Response Server::ResolveQuestionBySrp(const char *      aName,
+                                              const Question &  aQuestion,
+                                              Header &          aResponseHeader,
+                                              Message &         aResponseMessage,
+                                              uint8_t           aResolveKind,
+                                              NameCompressInfo &aCompressInfo)
+{
+    Error                    error    = kErrorNone;
+    const Srp::Server::Host *host     = nullptr;
+    TimeMilli                now      = TimerMilli::GetNow();
+    uint16_t                 qtype    = aQuestion.GetType();
+    Header::Response         response = Header::kResponseNameError;
+
+    while ((host = GetNextSrpHost(host)) != nullptr)
+    {
+        bool        needAdditionalAaaaRecord = false;
+        const char *hostName                 = host->GetFullName();
+
+        // Handle PTR/SRV/TXT query
+        if (qtype == ResourceRecord::kTypePtr || qtype == ResourceRecord::kTypeSrv || qtype == ResourceRecord::kTypeTxt)
+        {
+            const Srp::Server::Service *service = nullptr;
+
+            while ((service = GetNextSrpService(*host, service)) != nullptr)
+            {
+                uint32_t    instanceTtl         = TimeMilli::MsecToSec(service->GetExpireTime() - TimerMilli::GetNow());
+                const char *instanceName        = service->GetFullName();
+                bool        serviceNameMatched  = service->MatchesServiceName(aName);
+                bool        instanceNameMatched = service->Matches(aName);
+                bool        ptrQueryMatched     = qtype == ResourceRecord::kTypePtr && serviceNameMatched;
+                bool        srvQueryMatched     = qtype == ResourceRecord::kTypeSrv && instanceNameMatched;
+                bool        txtQueryMatched     = qtype == ResourceRecord::kTypeTxt && instanceNameMatched;
+
+                if (ptrQueryMatched || srvQueryMatched)
+                {
+                    needAdditionalAaaaRecord = true;
+                }
+
+                if (aResolveKind == kResolveAnswer && ptrQueryMatched)
+                {
+                    SuccessOrExit(
+                        error = AppendPtrRecord(aResponseMessage, aName, instanceName, instanceTtl, aCompressInfo));
+                    IncResourceRecordCount(aResponseHeader, aResolveKind != kResolveAnswer);
+                    response = Header::Response::kResponseSuccess;
+                }
+
+                if ((aResolveKind == kResolveAnswer && srvQueryMatched) ||
+                    ((aResolveKind & kResolveAdditionalSrv) && ptrQueryMatched))
+                {
+                    SuccessOrExit(error = AppendSrvRecord(aResponseMessage, instanceName, hostName, instanceTtl,
+                                                          service->GetPriority(), service->GetWeight(),
+                                                          service->GetPort(), aCompressInfo));
+                    IncResourceRecordCount(aResponseHeader, aResolveKind != kResolveAnswer);
+                    response = Header::Response::kResponseSuccess;
+                }
+
+                if ((aResolveKind == kResolveAnswer && txtQueryMatched) ||
+                    ((aResolveKind & kResolveAdditionalTxt) && ptrQueryMatched))
+                {
+                    SuccessOrExit(
+                        error = AppendTxtRecord(aResponseMessage, instanceName, *service, instanceTtl, aCompressInfo));
+                    IncResourceRecordCount(aResponseHeader, aResolveKind != kResolveAnswer);
+                    response = Header::Response::kResponseSuccess;
+                }
+            }
+        }
+
+        // Handle AAAA query
+        if ((aResolveKind == kResolveAnswer && qtype == ResourceRecord::kTypeAaaa && host->Matches(aName)) ||
+            ((aResolveKind & kResolveAdditionalAaaa) && needAdditionalAaaaRecord))
+        {
+            uint8_t             addrNum;
+            const Ip6::Address *addrs   = host->GetAddresses(addrNum);
+            uint32_t            hostTtl = TimeMilli::MsecToSec(host->GetExpireTime() - now);
+
+            for (uint8_t i = 0; i < addrNum; i++)
+            {
+                SuccessOrExit(error = AppendAaaaRecord(aResponseMessage, hostName, addrs[i], hostTtl, aCompressInfo));
+                IncResourceRecordCount(aResponseHeader, aResolveKind != kResolveAnswer);
+            }
+
+            response = Header::Response::kResponseSuccess;
+        }
+    }
+
+exit:
+    return error == kErrorNone ? response : Header::Response::kResponseServerFailure;
+}
+
+const Srp::Server::Host *Server::GetNextSrpHost(const Srp::Server::Host *aHost)
+{
+    const Srp::Server::Host *host = Get<Srp::Server>().GetNextHost(aHost);
+
+    while (host != nullptr && host->IsDeleted())
+    {
+        host = Get<Srp::Server>().GetNextHost(host);
+    }
+
+    return host;
+}
+
+const Srp::Server::Service *Server::GetNextSrpService(const Srp::Server::Host &   aHost,
+                                                      const Srp::Server::Service *aService)
+{
+    const Srp::Server::Service *service = aHost.GetNextService(aService);
+
+    while (service != nullptr && service->IsDeleted())
+    {
+        service = aHost.GetNextService(service);
+    }
+
+    return service;
+}
+
+Error Server::AppendTxtRecord(Message &                   aMessage,
+                              const char *                aInstanceName,
+                              const Srp::Server::Service &aService,
+                              uint32_t                    aTtl,
+                              NameCompressInfo &          aCompressInfo)
+{
+    Error     error;
+    uint16_t  recordOffset;
+    TxtRecord txtRecord;
+
+    SuccessOrExit(error = AppendInstanceName(aMessage, aInstanceName, aCompressInfo));
+
+    recordOffset = aMessage.GetLength();
+    SuccessOrExit(error = aMessage.SetLength(recordOffset + sizeof(txtRecord)));
+
+    SuccessOrExit(error = aMessage.AppendBytes(aService.GetTxtData(), aService.GetTxtDataLength()));
+
+    txtRecord.Init();
+    txtRecord.SetTtl(aTtl);
+    txtRecord.SetLength(aMessage.GetLength() - (recordOffset + sizeof(ResourceRecord)));
+
+    aMessage.Write(recordOffset, txtRecord);
+
+exit:
+    return error;
+}
+#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+
+} // namespace ServiceDiscovery
+} // namespace Dns
+} // namespace ot
+
+#endif // OPENTHREAD_CONFIG_DNS_SERVER_ENABLE
diff --git a/src/core/net/dnssd_server.hpp b/src/core/net/dnssd_server.hpp
new file mode 100644
index 0000000..af04ba2
--- /dev/null
+++ b/src/core/net/dnssd_server.hpp
@@ -0,0 +1,304 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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.
+ */
+
+#ifndef DNS_SERVER_HPP_
+#define DNS_SERVER_HPP_
+
+#include "openthread-core-config.h"
+
+#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
+
+#include <openthread/dns.h>
+
+#include "common/message.hpp"
+#include "common/non_copyable.hpp"
+#include "common/timer.hpp"
+#include "net/dns_types.hpp"
+#include "net/ip6.hpp"
+#include "net/netif.hpp"
+#include "net/srp_server.hpp"
+
+/**
+ * @file
+ *   This file includes definitions for the DNS-SD server.
+ */
+
+namespace ot {
+namespace Dns {
+namespace ServiceDiscovery {
+
+/**
+ * This class implements DNS-SD server.
+ *
+ */
+class Server : public InstanceLocator, private NonCopyable
+{
+public:
+    /**
+     * This constructor initializes the object.
+     *
+     * @param[in]  aInstance     A reference to the OpenThread instance.
+     *
+     */
+    explicit Server(Instance &aInstance);
+
+    /**
+     * This method starts the DNS-SD server.
+     *
+     * @retval kErrorNone     Successfully started the DNS-SD server.
+     * @retval kErrorFailed   If failed to open or bind the UDP socket.
+     *
+     */
+    Error Start(void);
+
+    /**
+     * This method stops the DNS-SD server.
+     *
+     */
+    void Stop(void);
+
+private:
+    enum
+    {
+        kPort                = OPENTHREAD_CONFIG_DNSSD_SERVER_PORT,
+        kProtocolLabelLength = 4,
+    };
+
+    enum : uint8_t
+    {
+        kResolveNone           = 0,
+        kResolveAnswer         = 1u << 0,
+        kResolveAdditionalSrv  = 1u << 1,
+        kResolveAdditionalTxt  = 1u << 2,
+        kResolveAdditionalAaaa = 1u << 3,
+        kResolveAdditionalAll  = kResolveAdditionalSrv | kResolveAdditionalTxt | kResolveAdditionalAaaa,
+    };
+
+    class NameCompressInfo : public Clearable<NameCompressInfo>
+    {
+    public:
+        enum : uint16_t
+        {
+            kUnknownOffset = 0, // Unknown offset value (used when offset is not yet set).
+        };
+
+        explicit NameCompressInfo(const char *aDomainName)
+            : mDomainName(aDomainName)
+            , mServiceName(nullptr)
+            , mInstanceName(nullptr)
+            , mHostName(nullptr)
+            , mDomainNameOffset(kUnknownOffset)
+            , mServiceNameOffset(kUnknownOffset)
+            , mInstanceNameOffset(kUnknownOffset)
+            , mHostNameOffset(kUnknownOffset)
+        {
+        }
+
+        uint16_t GetDomainNameOffset(void) const { return mDomainNameOffset; }
+
+        void SetDomainNameOffset(uint16_t aOffset) { mDomainNameOffset = aOffset; }
+
+        const char *GetDomainName(void) const { return mDomainName; }
+
+        uint16_t GetServiceNameOffset(const char *aServiceName) const
+        {
+            uint16_t offset = mServiceNameOffset;
+
+            if (offset != kUnknownOffset && strcmp(aServiceName, mServiceName) != 0)
+            {
+                offset = kUnknownOffset;
+            }
+
+            return offset;
+        };
+
+        void SetServiceNameOffset(uint16_t aOffset, const char *aName)
+        {
+            if (mServiceName == nullptr)
+            {
+                mServiceName       = aName;
+                mServiceNameOffset = aOffset;
+            }
+        }
+
+        const char *GetServiceName() const { return mServiceName; }
+
+        uint16_t GetInstanceNameOffset(const char *aName) const
+        {
+            uint16_t offset = mInstanceNameOffset;
+
+            if (offset != kUnknownOffset && strcmp(aName, mInstanceName) != 0)
+            {
+                offset = kUnknownOffset;
+            }
+
+            return offset;
+        }
+
+        void SetInstanceNameOffset(uint16_t aOffset, const char *aName)
+        {
+            if (mInstanceName == nullptr)
+            {
+                mInstanceName       = aName;
+                mInstanceNameOffset = aOffset;
+            }
+        }
+
+        uint16_t GetHostNameOffset(const char *aName) const
+        {
+            uint16_t offset = mHostNameOffset;
+
+            if (offset != kUnknownOffset && strcmp(aName, mHostName) != 0)
+            {
+                offset = kUnknownOffset;
+            }
+
+            return offset;
+        }
+
+        void SetHostNameOffset(uint16_t aOffset, const char *aName)
+        {
+            if (mHostName == nullptr)
+            {
+                mHostName       = aName;
+                mHostNameOffset = aOffset;
+            }
+        }
+
+    private:
+        const char *const mDomainName;   // The serialized domain name.
+        const char *      mServiceName;  // The serialized service name (only support one service name).
+        const char *      mInstanceName; // The serialized instance name or nullptr (only support one instance name).
+        const char *      mHostName;     // The serialized host name or nullptr (only support one host name).
+        uint16_t          mDomainNameOffset;   // Offset of domain name serialization into the response message.
+        uint16_t          mServiceNameOffset;  // Offset of service name serialization into the response message.
+        uint16_t          mInstanceNameOffset; // Offset of instance name serialization into the response message.
+        uint16_t          mHostNameOffset;     // Offset of host name serialization into the response message.
+    };
+
+    // This structure represents the splitting information of a full name.
+    struct NameComponentsOffsetInfo
+    {
+        enum : uint8_t
+        {
+            kNotPresent = 0xff, // Indicates the component is not present.
+        };
+
+        explicit NameComponentsOffsetInfo(void)
+            : mDomainOffset(kNotPresent)
+            , mProtocolOffset(kNotPresent)
+            , mServiceOffset(kNotPresent)
+            , mInstanceOffset(kNotPresent)
+        {
+        }
+
+        bool IsServiceInstanceName(void) const { return mInstanceOffset != kNotPresent; }
+
+        bool IsServiceName(void) const { return mServiceOffset != kNotPresent && mInstanceOffset == kNotPresent; }
+
+        bool IsHostName(void) const { return mProtocolOffset == kNotPresent && mDomainOffset != 0; }
+
+        uint8_t mDomainOffset;   // The offset to the beginning of <Domain>.
+        uint8_t mProtocolOffset; // The offset to the beginning of <Protocol> (i.e. _tcp or _udp) or `kNotPresent` if
+                                 // the name is not a service or instance.
+        uint8_t mServiceOffset;  // The offset to the beginning of <Service> or `kNotPresent` if the name is not a
+                                 // service or instance.
+        uint8_t mInstanceOffset; // The offset to the beginning of <Instance> or `kNotPresent` if the name is not a
+                                 // instance.
+    };
+
+    bool             IsRunning(void) const { return mSocket.IsBound(); }
+    static void      HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
+    void             HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    void             ProcessQuery(Message &aMessage, Message &aResponse, const Header &aRequestHeader);
+    Header::Response ResolveQuestion(const char *      aName,
+                                     const Question &  aQuestion,
+                                     Header &          aResponseHeader,
+                                     Message &         aResponseMessage,
+                                     uint8_t           aResolveKind,
+                                     NameCompressInfo &aCompressInfo);
+    static Error     AppendQuestion(const char *      aName,
+                                    const Question &  aQuestion,
+                                    Message &         aMessage,
+                                    NameCompressInfo &aCompressInfo);
+    static Error     AppendPtrRecord(Message &         aMessage,
+                                     const char *      aServiceName,
+                                     const char *      aInstanceName,
+                                     uint32_t          aTtl,
+                                     NameCompressInfo &aCompressInfo);
+    static Error     AppendSrvRecord(Message &         aMessage,
+                                     const char *      aInstanceName,
+                                     const char *      aHostName,
+                                     uint32_t          aTtl,
+                                     uint16_t          aPriority,
+                                     uint16_t          aWeight,
+                                     uint16_t          aPort,
+                                     NameCompressInfo &aCompressInfo);
+    static Error     AppendAaaaRecord(Message &           aMessage,
+                                      const char *        aHostName,
+                                      const Ip6::Address &aAddress,
+                                      uint32_t            aTtl,
+                                      NameCompressInfo &  aCompressInfo);
+    static Error     AppendServiceName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo);
+    static Error     AppendInstanceName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo);
+    static Error     AppendHostName(Message &aMessage, const char *aName, NameCompressInfo &aCompressInfo);
+    static void      IncResourceRecordCount(Header &aHeader, bool aAdditional);
+    static Error     FindNameComponents(const char *aName, const char *aDomain, NameComponentsOffsetInfo &aInfo);
+    static Error     FindPreviousLabel(const char *aName, uint8_t &aStart, uint8_t &aStop);
+
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+    Header::Response                   ResolveQuestionBySrp(const char *      aName,
+                                                            const Question &  aQuestion,
+                                                            Header &          aResponseHeader,
+                                                            Message &         aResponseMessage,
+                                                            uint8_t           aResolveKind,
+                                                            NameCompressInfo &aCompressInfo);
+    const Srp::Server::Host *          GetNextSrpHost(const Srp::Server::Host *aHost);
+    static const Srp::Server::Service *GetNextSrpService(const Srp::Server::Host &   aHost,
+                                                         const Srp::Server::Service *aService);
+    static Error                       AppendTxtRecord(Message &                   aMessage,
+                                                       const char *                aInstanceName,
+                                                       const Srp::Server::Service &aService,
+                                                       uint32_t                    aTtl,
+                                                       NameCompressInfo &          aCompressInfo);
+#endif
+
+    static const char kDnssdProtocolUdp[4];
+    static const char kDnssdProtocolTcp[4];
+    static const char kDefaultDomainName[];
+
+    Ip6::Udp::Socket mSocket;
+};
+
+} // namespace ServiceDiscovery
+} // namespace Dns
+} // namespace ot
+
+#endif // OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
+
+#endif // DNS_SERVER_HPP_
diff --git a/src/core/net/icmp6.cpp b/src/core/net/icmp6.cpp
index 51a30a8..0fd4bce 100644
--- a/src/core/net/icmp6.cpp
+++ b/src/core/net/icmp6.cpp
@@ -57,14 +57,14 @@
     return Get<Ip6>().NewMessage(sizeof(Header) + aReserved);
 }
 
-otError Icmp::RegisterHandler(Handler &aHandler)
+Error Icmp::RegisterHandler(Handler &aHandler)
 {
     return mHandlers.Add(aHandler);
 }
 
-otError Icmp::SendEchoRequest(Message &aMessage, const MessageInfo &aMessageInfo, uint16_t aIdentifier)
+Error Icmp::SendEchoRequest(Message &aMessage, const MessageInfo &aMessageInfo, uint16_t aIdentifier)
 {
-    otError     error = OT_ERROR_NONE;
+    Error       error = kErrorNone;
     MessageInfo messageInfoLocal;
     Header      icmpHeader;
 
@@ -85,12 +85,9 @@
     return error;
 }
 
-otError Icmp::SendError(Header::Type       aType,
-                        Header::Code       aCode,
-                        const MessageInfo &aMessageInfo,
-                        const Message &    aMessage)
+Error Icmp::SendError(Header::Type aType, Header::Code aCode, const MessageInfo &aMessageInfo, const Message &aMessage)
 {
-    otError           error = OT_ERROR_NONE;
+    Error             error = kErrorNone;
     MessageInfo       messageInfoLocal;
     Message *         message = nullptr;
     Header            icmp6Header;
@@ -107,7 +104,7 @@
 
     messageInfoLocal = aMessageInfo;
 
-    VerifyOrExit((message = Get<Ip6>().NewMessage(0, settings)) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Ip6>().NewMessage(0, settings)) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = message->SetLength(sizeof(icmp6Header) + sizeof(ip6Header)));
 
     message->Write(sizeof(icmp6Header), ip6Header);
@@ -126,10 +123,10 @@
     return error;
 }
 
-otError Icmp::HandleMessage(Message &aMessage, MessageInfo &aMessageInfo)
+Error Icmp::HandleMessage(Message &aMessage, MessageInfo &aMessageInfo)
 {
-    otError error = OT_ERROR_NONE;
-    Header  icmp6Header;
+    Error  error = kErrorNone;
+    Header icmp6Header;
 
     SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), icmp6Header));
 
@@ -174,9 +171,9 @@
     return rval;
 }
 
-otError Icmp::HandleEchoRequest(Message &aRequestMessage, const MessageInfo &aMessageInfo)
+Error Icmp::HandleEchoRequest(Message &aRequestMessage, const MessageInfo &aMessageInfo)
 {
-    otError     error = OT_ERROR_NONE;
+    Error       error = kErrorNone;
     Header      icmp6Header;
     Message *   replyMessage = nullptr;
     MessageInfo replyMessageInfo;
diff --git a/src/core/net/icmp6.hpp b/src/core/net/icmp6.hpp
index 592d4f0..88eda46 100644
--- a/src/core/net/icmp6.hpp
+++ b/src/core/net/icmp6.hpp
@@ -87,6 +87,8 @@
             kTypeParameterProblem = OT_ICMP6_TYPE_PARAMETER_PROBLEM, ///< Parameter Problem
             kTypeEchoRequest      = OT_ICMP6_TYPE_ECHO_REQUEST,      ///< Echo Request
             kTypeEchoReply        = OT_ICMP6_TYPE_ECHO_REPLY,        ///< Echo Reply
+            kTypeRouterSolicit    = OT_ICMP6_TYPE_ROUTER_SOLICIT,    ///< Router Solicitation
+            kTypeRouterAdvert     = OT_ICMP6_TYPE_ROUTER_ADVERT,     ///< Router Advertisement
         };
 
         /**
@@ -195,7 +197,6 @@
          *
          */
         void SetSequence(uint16_t aSequence) { mData.m16[1] = HostSwap16(aSequence); }
-
     } OT_TOOL_PACKED_END;
 
     /**
@@ -251,11 +252,11 @@
      *
      * @param[in]  aHandler  A reference to the ICMPv6 handler.
      *
-     * @retval OT_ERROR_NONE     Successfully registered the ICMPv6 handler.
-     * @retval OT_ERROR_ALREADY  The ICMPv6 handler is already registered.
+     * @retval kErrorNone     Successfully registered the ICMPv6 handler.
+     * @retval kErrorAlready  The ICMPv6 handler is already registered.
      *
      */
-    otError RegisterHandler(Handler &aHandler);
+    Error RegisterHandler(Handler &aHandler);
 
     /**
      * This method sends an ICMPv6 Echo Request message.
@@ -265,11 +266,11 @@
      * @param[in]  aIdentifier   An identifier to aid in matching Echo Replies to this Echo Request.
      *                           May be zero.
      *
-     * @retval OT_ERROR_NONE     Successfully enqueued the ICMPv6 Echo Request message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to generate an ICMPv6 Echo Request message.
+     * @retval kErrorNone     Successfully enqueued the ICMPv6 Echo Request message.
+     * @retval kErrorNoBufs   Insufficient buffers available to generate an ICMPv6 Echo Request message.
      *
      */
-    otError SendEchoRequest(Message &aMessage, const MessageInfo &aMessageInfo, uint16_t aIdentifier);
+    Error SendEchoRequest(Message &aMessage, const MessageInfo &aMessageInfo, uint16_t aIdentifier);
 
     /**
      * This method sends an ICMPv6 error message.
@@ -279,11 +280,11 @@
      * @param[in]  aMessageInfo  A reference to the message info.
      * @param[in]  aMessage      The error-causing IPv6 message.
      *
-     * @retval OT_ERROR_NONE     Successfully enqueued the ICMPv6 error message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available.
+     * @retval kErrorNone     Successfully enqueued the ICMPv6 error message.
+     * @retval kErrorNoBufs   Insufficient buffers available.
      *
      */
-    otError SendError(Header::Type aType, Header::Code aCode, const MessageInfo &aMessageInfo, const Message &aMessage);
+    Error SendError(Header::Type aType, Header::Code aCode, const MessageInfo &aMessageInfo, const Message &aMessage);
 
     /**
      * This method handles an ICMPv6 message.
@@ -291,12 +292,12 @@
      * @param[in]  aMessage      A reference to the ICMPv6 message.
      * @param[in]  aMessageInfo  A reference to the message info associated with @p aMessage.
      *
-     * @retval OT_ERROR_NONE     Successfully processed the ICMPv6 message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to generate the reply.
-     * @retval OT_ERROR_DROP     The ICMPv6 message was invalid and dropped.
+     * @retval kErrorNone     Successfully processed the ICMPv6 message.
+     * @retval kErrorNoBufs   Insufficient buffers available to generate the reply.
+     * @retval kErrorDrop     The ICMPv6 message was invalid and dropped.
      *
      */
-    otError HandleMessage(Message &aMessage, MessageInfo &aMessageInfo);
+    Error HandleMessage(Message &aMessage, MessageInfo &aMessageInfo);
 
     /**
      * This method indicates whether or not ICMPv6 Echo processing is enabled.
@@ -325,7 +326,7 @@
     bool ShouldHandleEchoRequest(const MessageInfo &aMessageInfo);
 
 private:
-    otError HandleEchoRequest(Message &aRequestMessage, const MessageInfo &aMessageInfo);
+    Error HandleEchoRequest(Message &aRequestMessage, const MessageInfo &aMessageInfo);
 
     LinkedList<Handler> mHandlers;
 
diff --git a/src/core/net/ip6.cpp b/src/core/net/ip6.cpp
index 0624318..d2f237f 100644
--- a/src/core/net/ip6.cpp
+++ b/src/core/net/ip6.cpp
@@ -51,6 +51,13 @@
 #include "net/udp6.hpp"
 #include "thread/mle.hpp"
 
+using IcmpType = ot::Ip6::Icmp::Header::Type;
+
+static const IcmpType sForwardICMPTypes[] = {
+    IcmpType::kTypeDstUnreach,       IcmpType::kTypePacketToBig, IcmpType::kTypeTimeExceeded,
+    IcmpType::kTypeParameterProblem, IcmpType::kTypeEchoRequest, IcmpType::kTypeEchoReply,
+};
+
 namespace ot {
 namespace Ip6 {
 
@@ -60,7 +67,7 @@
     , mIsReceiveIp6FilterEnabled(false)
     , mReceiveIp6DatagramCallback(nullptr)
     , mReceiveIp6DatagramCallbackContext(nullptr)
-    , mSendQueueTask(aInstance, Ip6::HandleSendQueue, this)
+    , mSendQueueTask(aInstance, Ip6::HandleSendQueue)
     , mIcmp(aInstance)
     , mUdp(aInstance)
     , mMpl(aInstance)
@@ -79,7 +86,7 @@
 
     VerifyOrExit(message != nullptr);
 
-    if (message->AppendBytes(aData, aDataLength) != OT_ERROR_NONE)
+    if (message->AppendBytes(aData, aDataLength) != kErrorNone)
     {
         message->Free();
         message = nullptr;
@@ -156,16 +163,16 @@
     return dscp;
 }
 
-otError Ip6::GetDatagramPriority(const uint8_t *aData, uint16_t aDataLen, Message::Priority &aPriority)
+Error Ip6::GetDatagramPriority(const uint8_t *aData, uint16_t aDataLen, Message::Priority &aPriority)
 {
-    otError       error = OT_ERROR_NONE;
+    Error         error = kErrorNone;
     const Header *header;
 
-    VerifyOrExit((aData != nullptr) && (aDataLen >= sizeof(Header)), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit((aData != nullptr) && (aDataLen >= sizeof(Header)), error = kErrorInvalidArgs);
 
     header = reinterpret_cast<const Header *>(aData);
-    VerifyOrExit(header->IsValid(), error = OT_ERROR_PARSE);
-    VerifyOrExit(sizeof(Header) + header->GetPayloadLength() == aDataLen, error = OT_ERROR_PARSE);
+    VerifyOrExit(header->IsValid(), error = kErrorParse);
+    VerifyOrExit(sizeof(Header) + header->GetPayloadLength() == aDataLen, error = kErrorParse);
 
     aPriority = DscpToPriority(header->GetDscp());
 
@@ -179,9 +186,9 @@
     mReceiveIp6DatagramCallbackContext = aCallbackContext;
 }
 
-otError Ip6::AddMplOption(Message &aMessage, Header &aHeader)
+Error Ip6::AddMplOption(Message &aMessage, Header &aHeader)
 {
-    otError        error = OT_ERROR_NONE;
+    Error          error = kErrorNone;
     HopByHopHeader hbhHeader;
     OptionMpl      mplOption;
     OptionPadN     padOption;
@@ -206,9 +213,9 @@
     return error;
 }
 
-otError Ip6::AddTunneledMplOption(Message &aMessage, Header &aHeader, MessageInfo &aMessageInfo)
+Error Ip6::AddTunneledMplOption(Message &aMessage, Header &aHeader, MessageInfo &aMessageInfo)
 {
-    otError                    error = OT_ERROR_NONE;
+    Error                      error = kErrorNone;
     Header                     tunnelHeader;
     const NetifUnicastAddress *source;
     MessageInfo                messageInfo(aMessageInfo);
@@ -222,7 +229,7 @@
     tunnelHeader.SetDestination(messageInfo.GetPeerAddr());
     tunnelHeader.SetNextHeader(kProtoIp6);
 
-    VerifyOrExit((source = SelectSourceAddress(messageInfo)) != nullptr, error = OT_ERROR_INVALID_SOURCE_ADDRESS);
+    VerifyOrExit((source = SelectSourceAddress(messageInfo)) != nullptr, error = kErrorInvalidSourceAddress);
 
     tunnelHeader.SetSource(source->GetAddress());
 
@@ -233,9 +240,9 @@
     return error;
 }
 
-otError Ip6::InsertMplOption(Message &aMessage, Header &aHeader, MessageInfo &aMessageInfo)
+Error Ip6::InsertMplOption(Message &aMessage, Header &aHeader, MessageInfo &aMessageInfo)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     VerifyOrExit(aHeader.GetDestination().IsMulticast() &&
                  aHeader.GetDestination().GetScope() >= Address::kRealmLocalScope);
@@ -254,7 +261,7 @@
             IgnoreError(aMessage.Read(0, hbh));
             hbhLength = (hbh.GetLength() + 1) * 8;
 
-            VerifyOrExit(hbhLength <= aHeader.GetPayloadLength(), error = OT_ERROR_PARSE);
+            VerifyOrExit(hbhLength <= aHeader.GetPayloadLength(), error = kErrorParse);
 
             // increase existing hop-by-hop option header length by 8 bytes
             hbh.SetLength(hbh.GetLength() + 1);
@@ -313,9 +320,9 @@
     return error;
 }
 
-otError Ip6::RemoveMplOption(Message &aMessage)
+Error Ip6::RemoveMplOption(Message &aMessage)
 {
-    otError        error = OT_ERROR_NONE;
+    Error          error = kErrorNone;
     Header         ip6Header;
     HopByHopHeader hbh;
     uint16_t       offset;
@@ -331,7 +338,7 @@
 
     IgnoreError(aMessage.Read(offset, hbh));
     endOffset = offset + (hbh.GetLength() + 1) * 8;
-    VerifyOrExit(aMessage.GetLength() >= endOffset, error = OT_ERROR_PARSE);
+    VerifyOrExit(aMessage.GetLength() >= endOffset, error = kErrorParse);
 
     offset += sizeof(hbh);
 
@@ -345,12 +352,12 @@
         {
         case OptionMpl::kType:
             // if multiple MPL options exist, discard packet
-            VerifyOrExit(mplOffset == 0, error = OT_ERROR_PARSE);
+            VerifyOrExit(mplOffset == 0, error = kErrorParse);
 
             mplOffset = offset;
             mplLength = option.GetLength();
 
-            VerifyOrExit(mplLength <= sizeof(OptionMpl) - sizeof(OptionHeader), error = OT_ERROR_PARSE);
+            VerifyOrExit(mplLength <= sizeof(OptionMpl) - sizeof(OptionHeader), error = kErrorParse);
 
             if (mplOffset == sizeof(ip6Header) + sizeof(hbh) && hbh.GetLength() == 0)
             {
@@ -383,7 +390,7 @@
     }
 
     // verify that IPv6 Options header is properly formed
-    VerifyOrExit(offset == endOffset, error = OT_ERROR_PARSE);
+    VerifyOrExit(offset == endOffset, error = kErrorParse);
 
     if (remove)
     {
@@ -435,9 +442,9 @@
     mSendQueueTask.Post();
 }
 
-otError Ip6::SendDatagram(Message &aMessage, MessageInfo &aMessageInfo, uint8_t aIpProto)
+Error Ip6::SendDatagram(Message &aMessage, MessageInfo &aMessageInfo, uint8_t aIpProto)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     Header   header;
     uint16_t payloadLength = aMessage.GetLength();
 
@@ -455,12 +462,11 @@
         header.SetHopLimit(static_cast<uint8_t>(kDefaultHopLimit));
     }
 
-    if (aMessageInfo.GetSockAddr().IsUnspecified() || aMessageInfo.GetSockAddr().IsMulticast() ||
-        Get<Mle::Mle>().IsAnycastLocator(aMessageInfo.GetSockAddr()))
+    if (aMessageInfo.GetSockAddr().IsUnspecified() || aMessageInfo.GetSockAddr().IsMulticast())
     {
         const NetifUnicastAddress *source = SelectSourceAddress(aMessageInfo);
 
-        VerifyOrExit(source != nullptr, error = OT_ERROR_INVALID_SOURCE_ADDRESS);
+        VerifyOrExit(source != nullptr, error = kErrorInvalidSourceAddress);
         header.SetSource(source->GetAddress());
     }
     else
@@ -519,7 +525,7 @@
 
 void Ip6::HandleSendQueue(Tasklet &aTasklet)
 {
-    aTasklet.GetOwner<Ip6>().HandleSendQueue();
+    aTasklet.Get<Ip6>().HandleSendQueue();
 }
 
 void Ip6::HandleSendQueue(void)
@@ -533,9 +539,9 @@
     }
 }
 
-otError Ip6::HandleOptions(Message &aMessage, Header &aHeader, bool aIsOutbound, bool &aReceive)
+Error Ip6::HandleOptions(Message &aMessage, Header &aHeader, bool aIsOutbound, bool &aReceive)
 {
-    otError        error = OT_ERROR_NONE;
+    Error          error = kErrorNone;
     HopByHopHeader hbhHeader;
     OptionHeader   optionHeader;
     uint16_t       endOffset;
@@ -543,7 +549,7 @@
     SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), hbhHeader));
     endOffset = aMessage.GetOffset() + (hbhHeader.GetLength() + 1) * 8;
 
-    VerifyOrExit(endOffset <= aMessage.GetLength(), error = OT_ERROR_PARSE);
+    VerifyOrExit(endOffset <= aMessage.GetLength(), error = kErrorParse);
 
     aMessage.MoveOffset(sizeof(optionHeader));
 
@@ -558,7 +564,7 @@
         }
 
         VerifyOrExit(aMessage.GetOffset() + sizeof(optionHeader) + optionHeader.GetLength() <= endOffset,
-                     error = OT_ERROR_PARSE);
+                     error = kErrorParse);
 
         switch (optionHeader.GetType())
         {
@@ -573,15 +579,15 @@
                 break;
 
             case OptionHeader::kActionDiscard:
-                ExitNow(error = OT_ERROR_DROP);
+                ExitNow(error = kErrorDrop);
 
             case OptionHeader::kActionForceIcmp:
                 // TODO: send icmp error
-                ExitNow(error = OT_ERROR_DROP);
+                ExitNow(error = kErrorDrop);
 
             case OptionHeader::kActionIcmp:
                 // TODO: send icmp error
-                ExitNow(error = OT_ERROR_DROP);
+                ExitNow(error = kErrorDrop);
             }
 
             break;
@@ -595,9 +601,9 @@
 }
 
 #if OPENTHREAD_CONFIG_IP6_FRAGMENTATION_ENABLE
-otError Ip6::FragmentDatagram(Message &aMessage, uint8_t aIpProto)
+Error Ip6::FragmentDatagram(Message &aMessage, uint8_t aIpProto)
 {
-    otError        error = OT_ERROR_NONE;
+    Error          error = kErrorNone;
     Header         header;
     FragmentHeader fragmentHeader;
     Message *      fragment        = nullptr;
@@ -637,7 +643,7 @@
         offset = fragmentCnt * FragmentHeader::BytesToFragmentOffset(maxPayloadFragment);
         fragmentHeader.SetOffset(offset);
 
-        VerifyOrExit((fragment = NewMessage(0)) != nullptr, error = OT_ERROR_NO_BUFS);
+        VerifyOrExit((fragment = NewMessage(0)) != nullptr, error = kErrorNoBufs);
         SuccessOrExit(error = fragment->SetLength(aMessage.GetOffset() + sizeof(fragmentHeader) + payloadFragment));
 
         header.SetPayloadLength(payloadFragment + sizeof(fragmentHeader));
@@ -649,7 +655,7 @@
         VerifyOrExit(aMessage.CopyTo(aMessage.GetOffset() + FragmentHeader::FragmentOffsetToBytes(offset),
                                      aMessage.GetOffset() + sizeof(fragmentHeader), payloadFragment,
                                      *fragment) == static_cast<int>(payloadFragment),
-                     error = OT_ERROR_NO_BUFS);
+                     error = kErrorNoBufs);
 
         EnqueueDatagram(*fragment);
 
@@ -663,7 +669,7 @@
 
 exit:
 
-    if (error == OT_ERROR_NO_BUFS)
+    if (error == kErrorNoBufs)
     {
         otLogWarnIp6("No buffer for Ip6 fragmentation");
     }
@@ -672,9 +678,9 @@
     return error;
 }
 
-otError Ip6::HandleFragment(Message &aMessage, Netif *aNetif, MessageInfo &aMessageInfo, bool aFromNcpHost)
+Error Ip6::HandleFragment(Message &aMessage, Netif *aNetif, MessageInfo &aMessageInfo, bool aFromNcpHost)
 {
-    otError        error = OT_ERROR_NONE;
+    Error          error = kErrorNone;
     Header         header, headerBuffer;
     FragmentHeader fragmentHeader;
     Message *      message         = nullptr;
@@ -715,12 +721,14 @@
     if (offset + payloadFragment + aMessage.GetOffset() > kMaxAssembledDatagramLength)
     {
         otLogWarnIp6("Packet too large for fragment buffer");
-        ExitNow(error = OT_ERROR_NO_BUFS);
+        ExitNow(error = kErrorNoBufs);
     }
 
     if (message == nullptr)
     {
-        VerifyOrExit((message = NewMessage(0)) != nullptr, error = OT_ERROR_NO_BUFS);
+        otLogDebgIp6("start reassembly");
+        VerifyOrExit((message = NewMessage(0)) != nullptr, error = kErrorNoBufs);
+        mReassemblyList.Enqueue(*message);
         SuccessOrExit(error = message->SetLength(aMessage.GetOffset()));
 
         message->SetTimeout(kIp6ReassemblyTimeout);
@@ -732,10 +740,6 @@
         OT_ASSERT(assertValue == aMessage.GetOffset());
 
         Get<TimeTicker>().RegisterReceiver(TimeTicker::kIp6FragmentReassembler);
-
-        mReassemblyList.Enqueue(*message);
-
-        otLogDebgIp6("start reassembly.");
     }
 
     // increase message buffer if necessary
@@ -769,20 +773,20 @@
     }
 
 exit:
-    if (error != OT_ERROR_DROP && error != OT_ERROR_NONE && isFragmented)
+    if (error != kErrorDrop && error != kErrorNone && isFragmented)
     {
         if (message != nullptr)
         {
             mReassemblyList.Dequeue(*message);
             message->Free();
         }
-        otLogWarnIp6("Reassembly failed: %s", otThreadErrorToString(error));
+        otLogWarnIp6("Reassembly failed: %s", ErrorToString(error));
     }
 
     if (isFragmented)
     {
         // drop all fragments, the payload is stored in the fragment buffer
-        error = OT_ERROR_DROP;
+        error = kErrorDrop;
     }
 
     return error;
@@ -834,7 +838,7 @@
 
 void Ip6::SendIcmpError(Message &aMessage, Icmp::Header::Type aIcmpType, Icmp::Header::Code aIcmpCode)
 {
-    otError     error = OT_ERROR_NONE;
+    Error       error = kErrorNone;
     Header      header;
     MessageInfo messageInfo;
 
@@ -849,34 +853,34 @@
 
 exit:
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogWarnIp6("Failed to send ICMP error: %s", otThreadErrorToString(error));
+        otLogWarnIp6("Failed to send ICMP error: %s", ErrorToString(error));
     }
 }
 
 #else
-otError Ip6::FragmentDatagram(Message &aMessage, uint8_t aIpProto)
+Error Ip6::FragmentDatagram(Message &aMessage, uint8_t aIpProto)
 {
     OT_UNUSED_VARIABLE(aIpProto);
 
     EnqueueDatagram(aMessage);
 
-    return OT_ERROR_NONE;
+    return kErrorNone;
 }
 
-otError Ip6::HandleFragment(Message &aMessage, Netif *aNetif, MessageInfo &aMessageInfo, bool aFromNcpHost)
+Error Ip6::HandleFragment(Message &aMessage, Netif *aNetif, MessageInfo &aMessageInfo, bool aFromNcpHost)
 {
     OT_UNUSED_VARIABLE(aNetif);
     OT_UNUSED_VARIABLE(aMessageInfo);
     OT_UNUSED_VARIABLE(aFromNcpHost);
 
-    otError        error = OT_ERROR_NONE;
+    Error          error = kErrorNone;
     FragmentHeader fragmentHeader;
 
     SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), fragmentHeader));
 
-    VerifyOrExit(fragmentHeader.GetOffset() == 0 && !fragmentHeader.IsMoreFlagSet(), error = OT_ERROR_DROP);
+    VerifyOrExit(fragmentHeader.GetOffset() == 0 && !fragmentHeader.IsMoreFlagSet(), error = kErrorDrop);
 
     aMessage.MoveOffset(sizeof(fragmentHeader));
 
@@ -885,16 +889,16 @@
 }
 #endif // OPENTHREAD_CONFIG_IP6_FRAGMENTATION_ENABLE
 
-otError Ip6::HandleExtensionHeaders(Message &    aMessage,
-                                    Netif *      aNetif,
-                                    MessageInfo &aMessageInfo,
-                                    Header &     aHeader,
-                                    uint8_t &    aNextHeader,
-                                    bool         aIsOutbound,
-                                    bool         aFromNcpHost,
-                                    bool &       aReceive)
+Error Ip6::HandleExtensionHeaders(Message &    aMessage,
+                                  Netif *      aNetif,
+                                  MessageInfo &aMessageInfo,
+                                  Header &     aHeader,
+                                  uint8_t &    aNextHeader,
+                                  bool         aIsOutbound,
+                                  bool         aFromNcpHost,
+                                  bool &       aReceive)
 {
-    otError         error = OT_ERROR_NONE;
+    Error           error = kErrorNone;
     ExtensionHeader extHeader;
 
     while (aReceive || aNextHeader == kProtoHopOpts)
@@ -923,7 +927,7 @@
 
         case kProtoRouting:
         case kProtoNone:
-            ExitNow(error = OT_ERROR_DROP);
+            ExitNow(error = kErrorDrop);
 
         default:
             ExitNow();
@@ -936,12 +940,12 @@
     return error;
 }
 
-otError Ip6::HandlePayload(Message &          aMessage,
-                           MessageInfo &      aMessageInfo,
-                           uint8_t            aIpProto,
-                           Message::Ownership aMessageOwnership)
+Error Ip6::HandlePayload(Message &          aMessage,
+                         MessageInfo &      aMessageInfo,
+                         uint8_t            aIpProto,
+                         Message::Ownership aMessageOwnership)
 {
-    otError  error   = OT_ERROR_NONE;
+    Error    error   = kErrorNone;
     Message *message = nullptr;
 
     VerifyOrExit(aIpProto == kProtoUdp || aIpProto == kProtoIcmp6);
@@ -953,7 +957,7 @@
         break;
 
     case Message::kCopyToUse:
-        VerifyOrExit((message = aMessage.Clone()) != nullptr, error = OT_ERROR_NO_BUFS);
+        VerifyOrExit((message = aMessage.Clone()) != nullptr, error = kErrorNoBufs);
         break;
     }
 
@@ -961,7 +965,7 @@
     {
     case kProtoUdp:
         error = mUdp.HandleMessage(*message, aMessageInfo);
-        if (error == OT_ERROR_DROP)
+        if (error == kErrorDrop)
         {
             otLogNoteIp6("Error UDP Checksum");
         }
@@ -976,9 +980,9 @@
     }
 
 exit:
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogNoteIp6("Failed to handle payload: %s", otThreadErrorToString(error));
+        otLogNoteIp6("Failed to handle payload: %s", ErrorToString(error));
     }
 
     FreeMessage(message);
@@ -986,29 +990,31 @@
     return error;
 }
 
-otError Ip6::ProcessReceiveCallback(Message &          aMessage,
-                                    const MessageInfo &aMessageInfo,
-                                    uint8_t            aIpProto,
-                                    bool               aFromNcpHost,
-                                    Message::Ownership aMessageOwnership)
+Error Ip6::ProcessReceiveCallback(Message &          aMessage,
+                                  const MessageInfo &aMessageInfo,
+                                  uint8_t            aIpProto,
+                                  bool               aFromNcpHost,
+                                  Message::Ownership aMessageOwnership)
 {
-    otError  error   = OT_ERROR_NONE;
+    Error    error   = kErrorNone;
     Message *message = &aMessage;
 
-    VerifyOrExit(!aFromNcpHost, error = OT_ERROR_NO_ROUTE);
-    VerifyOrExit(mReceiveIp6DatagramCallback != nullptr, error = OT_ERROR_NO_ROUTE);
+    VerifyOrExit(!aFromNcpHost, error = kErrorNoRoute);
+    VerifyOrExit(mReceiveIp6DatagramCallback != nullptr, error = kErrorNoRoute);
 
     // Do not forward reassembled IPv6 packets.
-    VerifyOrExit(aMessage.GetLength() <= kMinimalMtu, error = OT_ERROR_DROP);
+    VerifyOrExit(aMessage.GetLength() <= kMinimalMtu, error = kErrorDrop);
 
     if (mIsReceiveIp6FilterEnabled)
     {
+#if !OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE
         // do not pass messages sent to an RLOC/ALOC, except Service Locator
         bool isLocator = Get<Mle::Mle>().IsMeshLocalAddress(aMessageInfo.GetSockAddr()) &&
                          aMessageInfo.GetSockAddr().GetIid().IsLocator();
 
         VerifyOrExit(!isLocator || aMessageInfo.GetSockAddr().GetIid().IsAnycastServiceLocator(),
-                     error = OT_ERROR_NO_ROUTE);
+                     error = kErrorNoRoute);
+#endif
 
         switch (aIpProto)
         {
@@ -1019,7 +1025,7 @@
                 IgnoreError(aMessage.Read(aMessage.GetOffset(), icmp));
 
                 // do not pass ICMP Echo Request messages
-                VerifyOrExit(icmp.GetType() != Icmp::Header::kTypeEchoRequest, error = OT_ERROR_DROP);
+                VerifyOrExit(icmp.GetType() != Icmp::Header::kTypeEchoRequest, error = kErrorDrop);
             }
 
             break;
@@ -1027,30 +1033,10 @@
         case kProtoUdp:
         {
             Udp::Header udp;
-            uint16_t    destPort;
 
             IgnoreError(aMessage.Read(aMessage.GetOffset(), udp));
+            VerifyOrExit(Get<Udp>().ShouldUsePlatformUdp(udp.GetDestinationPort()), error = kErrorNoRoute);
 
-            destPort = udp.GetDestinationPort();
-
-            if ((destPort == Mle::kUdpPort) &&
-                (aMessageInfo.GetSockAddr().IsLinkLocal() || aMessageInfo.GetSockAddr().IsLinkLocalMulticast()))
-            {
-                // do not pass MLE messages
-                ExitNow(error = OT_ERROR_NO_ROUTE);
-            }
-            else if ((destPort == Tmf::kUdpPort) && Get<Tmf::TmfAgent>().IsTmfMessage(aMessageInfo))
-            {
-                // do not pass TMF messages
-                ExitNow(error = OT_ERROR_NO_ROUTE);
-            }
-
-#if OPENTHREAD_FTD
-            if (destPort == Get<MeshCoP::JoinerRouter>().GetJoinerUdpPort())
-            {
-                ExitNow(error = OT_ERROR_NO_ROUTE);
-            }
-#endif
             break;
         }
 
@@ -1070,7 +1056,7 @@
         if (message == nullptr)
         {
             otLogWarnIp6("No buff to clone msg (len: %d) to pass to host", aMessage.GetLength());
-            ExitNow(error = OT_ERROR_NO_BUFS);
+            ExitNow(error = kErrorNoBufs);
         }
 
         break;
@@ -1081,7 +1067,7 @@
 
 exit:
 
-    if ((error != OT_ERROR_NONE) && (aMessageOwnership == Message::kTakeCustody))
+    if ((error != kErrorNone) && (aMessageOwnership == Message::kTakeCustody))
     {
         aMessage.Free();
     }
@@ -1089,15 +1075,15 @@
     return error;
 }
 
-otError Ip6::SendRaw(Message &aMessage)
+Error Ip6::SendRaw(Message &aMessage)
 {
-    otError     error = OT_ERROR_NONE;
+    Error       error = kErrorNone;
     Header      header;
     MessageInfo messageInfo;
     bool        freed = false;
 
     SuccessOrExit(error = header.Init(aMessage));
-    VerifyOrExit(!header.GetSource().IsMulticast(), error = OT_ERROR_INVALID_SOURCE_ADDRESS);
+    VerifyOrExit(!header.GetSource().IsMulticast(), error = kErrorInvalidSourceAddress);
 
     messageInfo.SetPeerAddr(header.GetSource());
     messageInfo.SetSockAddr(header.GetDestination());
@@ -1121,9 +1107,9 @@
     return error;
 }
 
-otError Ip6::HandleDatagram(Message &aMessage, Netif *aNetif, const void *aLinkMessageInfo, bool aFromNcpHost)
+Error Ip6::HandleDatagram(Message &aMessage, Netif *aNetif, const void *aLinkMessageInfo, bool aFromNcpHost)
 {
-    otError     error;
+    Error       error;
     MessageInfo messageInfo;
     Header      header;
     bool        receive;
@@ -1241,7 +1227,7 @@
 
         error = ProcessReceiveCallback(aMessage, messageInfo, nextHeader, aFromNcpHost, Message::kCopyToUse);
 
-        if ((error == OT_ERROR_NONE || error == OT_ERROR_NO_ROUTE) && forwardHost)
+        if ((error == kErrorNone || error == kErrorNoRoute) && forwardHost)
         {
             forwardHost = false;
         }
@@ -1273,27 +1259,52 @@
             header.SetHopLimit(header.GetHopLimit() - 1);
         }
 
-        VerifyOrExit(header.GetHopLimit() > 0, error = OT_ERROR_DROP);
+        VerifyOrExit(header.GetHopLimit() > 0, error = kErrorDrop);
 
         hopLimit = header.GetHopLimit();
         aMessage.Write(Header::kHopLimitFieldOffset, hopLimit);
 
-#if OPENTHREAD_CONFIG_UNSECURE_TRAFFIC_MANAGED_BY_STACK_ENABLE
-        // check whether source port is an unsecure port
+        if (aFromNcpHost && nextHeader == kProtoIcmp6)
+        {
+            uint8_t icmpType;
+            bool    isAllowedType = false;
+
+            SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), icmpType));
+            for (IcmpType type : sForwardICMPTypes)
+            {
+                if (icmpType == type)
+                {
+                    isAllowedType = true;
+                    break;
+                }
+            }
+            VerifyOrExit(isAllowedType, error = kErrorDrop);
+        }
         if (aFromNcpHost && (nextHeader == kProtoTcp || nextHeader == kProtoUdp))
         {
-            uint16_t sourcePort;
+            uint16_t sourcePort, destPort;
 
             SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), sourcePort));
+            SuccessOrExit(error = aMessage.Read(aMessage.GetOffset() + sizeof(sourcePort), destPort));
             sourcePort = HostSwap16(sourcePort);
+            destPort   = HostSwap16(destPort);
 
+            if (nextHeader == kProtoUdp)
+            {
+                VerifyOrExit(Get<Udp>().ShouldUsePlatformUdp(destPort), error = kErrorDrop);
+            }
+
+#if OPENTHREAD_CONFIG_UNSECURE_TRAFFIC_MANAGED_BY_STACK_ENABLE
+            // check whether source port is an unsecure port
             if (Get<Filter>().IsUnsecurePort(sourcePort))
             {
                 aMessage.SetLinkSecurityEnabled(false);
                 otLogInfoIp6("Disabled link security for packet to %s", header.GetDestination().ToString().AsCString());
             }
-        }
+#else
+            OT_UNUSED_VARIABLE(sourcePort);
 #endif
+        }
 
         // `SendMessage()` takes custody of message in the success case
         SuccessOrExit(error = Get<ThreadNetif>().SendMessage(aMessage));
@@ -1337,7 +1348,7 @@
 #endif
     }
     else if (Get<ThreadNetif>().RouteLookup(aMessageInfo.GetPeerAddr(), aMessageInfo.GetSockAddr(), nullptr) ==
-             OT_ERROR_NONE)
+             kErrorNone)
     {
         // route
         ExitNow(rval = true);
diff --git a/src/core/net/ip6.hpp b/src/core/net/ip6.hpp
index 6ca1101..d6138c6 100644
--- a/src/core/net/ip6.hpp
+++ b/src/core/net/ip6.hpp
@@ -185,28 +185,28 @@
      * @param[in]  aMessageInfo  A reference to the message info associated with @p aMessage.
      * @param[in]  aIpProto      The Internet Protocol value.
      *
-     * @retval OT_ERROR_NONE     Successfully enqueued the message into an output interface.
-     * @retval OT_ERROR_NO_BUFS  Insufficient available buffer to add the IPv6 headers.
+     * @retval kErrorNone     Successfully enqueued the message into an output interface.
+     * @retval kErrorNoBufs   Insufficient available buffer to add the IPv6 headers.
      *
      */
-    otError SendDatagram(Message &aMessage, MessageInfo &aMessageInfo, uint8_t aIpProto);
+    Error SendDatagram(Message &aMessage, MessageInfo &aMessageInfo, uint8_t aIpProto);
 
     /**
      * This method sends a raw IPv6 datagram with a fully formed IPv6 header.
      *
      * The caller transfers ownership of @p aMessage when making this call. OpenThread will free @p aMessage when
-     * processing is complete, including when a value other than `OT_ERROR_NONE` is returned.
+     * processing is complete, including when a value other than `kErrorNone` is returned.
      *
      * @param[in]  aMessage          A reference to the message.
      *
-     * @retval OT_ERROR_NONE      Successfully processed the message.
-     * @retval OT_ERROR_DROP      Message was well-formed but not fully processed due to packet processing rules.
-     * @retval OT_ERROR_NO_BUFS   Could not allocate necessary message buffers when processing the datagram.
-     * @retval OT_ERROR_NO_ROUTE  No route to host.
-     * @retval OT_ERROR_PARSE     Encountered a malformed header when processing the message.
+     * @retval kErrorNone     Successfully processed the message.
+     * @retval kErrorDrop     Message was well-formed but not fully processed due to packet processing rules.
+     * @retval kErrorNoBufs   Could not allocate necessary message buffers when processing the datagram.
+     * @retval kErrorNoRoute  No route to host.
+     * @retval kErrorParse    Encountered a malformed header when processing the message.
      *
      */
-    otError SendRaw(Message &aMessage);
+    Error SendRaw(Message &aMessage);
 
     /**
      * This method processes a received IPv6 datagram.
@@ -216,14 +216,14 @@
      * @param[in]  aLinkMessageInfo  A pointer to link-specific message information.
      * @param[in]  aFromNcpHost      TRUE if the message was submitted by the NCP host, FALSE otherwise.
      *
-     * @retval OT_ERROR_NONE      Successfully processed the message.
-     * @retval OT_ERROR_DROP      Message was well-formed but not fully processed due to packet processing rules.
-     * @retval OT_ERROR_NO_BUFS   Could not allocate necessary message buffers when processing the datagram.
-     * @retval OT_ERROR_NO_ROUTE  No route to host.
-     * @retval OT_ERROR_PARSE     Encountered a malformed header when processing the message.
+     * @retval kErrorNone     Successfully processed the message.
+     * @retval kErrorDrop     Message was well-formed but not fully processed due to packet processing rules.
+     * @retval kErrorNoBufs   Could not allocate necessary message buffers when processing the datagram.
+     * @retval kErrorNoRoute  No route to host.
+     * @retval kErrorParse    Encountered a malformed header when processing the message.
      *
      */
-    otError HandleDatagram(Message &aMessage, Netif *aNetif, const void *aLinkMessageInfo, bool aFromNcpHost);
+    Error HandleDatagram(Message &aMessage, Netif *aNetif, const void *aLinkMessageInfo, bool aFromNcpHost);
 
     /**
      * This method registers a callback to provide received raw IPv6 datagrams.
@@ -328,41 +328,41 @@
     void        HandleSendQueue(void);
 
     static uint8_t PriorityToDscp(Message::Priority aPriority);
-    static otError GetDatagramPriority(const uint8_t *aData, uint16_t aDataLen, Message::Priority &aPriority);
+    static Error   GetDatagramPriority(const uint8_t *aData, uint16_t aDataLen, Message::Priority &aPriority);
 
-    void    EnqueueDatagram(Message &aMessage);
-    otError ProcessReceiveCallback(Message &          aMessage,
-                                   const MessageInfo &aMessageInfo,
-                                   uint8_t            aIpProto,
-                                   bool               aFromNcpHost,
-                                   Message::Ownership aMessageOwnership);
-    otError HandleExtensionHeaders(Message &    aMessage,
-                                   Netif *      aNetif,
-                                   MessageInfo &aMessageInfo,
-                                   Header &     aHeader,
-                                   uint8_t &    aNextHeader,
-                                   bool         aIsOutbound,
-                                   bool         aFromNcpHost,
-                                   bool &       aReceive);
-    otError FragmentDatagram(Message &aMessage, uint8_t aIpProto);
-    otError HandleFragment(Message &aMessage, Netif *aNetif, MessageInfo &aMessageInfo, bool aFromNcpHost);
+    void  EnqueueDatagram(Message &aMessage);
+    Error ProcessReceiveCallback(Message &          aMessage,
+                                 const MessageInfo &aMessageInfo,
+                                 uint8_t            aIpProto,
+                                 bool               aFromNcpHost,
+                                 Message::Ownership aMessageOwnership);
+    Error HandleExtensionHeaders(Message &    aMessage,
+                                 Netif *      aNetif,
+                                 MessageInfo &aMessageInfo,
+                                 Header &     aHeader,
+                                 uint8_t &    aNextHeader,
+                                 bool         aIsOutbound,
+                                 bool         aFromNcpHost,
+                                 bool &       aReceive);
+    Error FragmentDatagram(Message &aMessage, uint8_t aIpProto);
+    Error HandleFragment(Message &aMessage, Netif *aNetif, MessageInfo &aMessageInfo, bool aFromNcpHost);
 #if OPENTHREAD_CONFIG_IP6_FRAGMENTATION_ENABLE
     void CleanupFragmentationBuffer(void);
     void HandleTimeTick(void);
     void UpdateReassemblyList(void);
     void SendIcmpError(Message &aMessage, Icmp::Header::Type aIcmpType, Icmp::Header::Code aIcmpCode);
 #endif
-    otError AddMplOption(Message &aMessage, Header &aHeader);
-    otError AddTunneledMplOption(Message &aMessage, Header &aHeader, MessageInfo &aMessageInfo);
-    otError InsertMplOption(Message &aMessage, Header &aHeader, MessageInfo &aMessageInfo);
-    otError RemoveMplOption(Message &aMessage);
-    otError HandleOptions(Message &aMessage, Header &aHeader, bool aIsOutbound, bool &aReceive);
-    otError HandlePayload(Message &          aMessage,
-                          MessageInfo &      aMessageInfo,
-                          uint8_t            aIpProto,
-                          Message::Ownership aMessageOwnership);
-    bool    ShouldForwardToThread(const MessageInfo &aMessageInfo, bool aFromNcpHost) const;
-    bool    IsOnLink(const Address &aAddress) const;
+    Error AddMplOption(Message &aMessage, Header &aHeader);
+    Error AddTunneledMplOption(Message &aMessage, Header &aHeader, MessageInfo &aMessageInfo);
+    Error InsertMplOption(Message &aMessage, Header &aHeader, MessageInfo &aMessageInfo);
+    Error RemoveMplOption(Message &aMessage);
+    Error HandleOptions(Message &aMessage, Header &aHeader, bool aIsOutbound, bool &aReceive);
+    Error HandlePayload(Message &          aMessage,
+                        MessageInfo &      aMessageInfo,
+                        uint8_t            aIpProto,
+                        Message::Ownership aMessageOwnership);
+    bool  ShouldForwardToThread(const MessageInfo &aMessageInfo, bool aFromNcpHost) const;
+    bool  IsOnLink(const Address &aAddress) const;
 
     bool                 mForwardingEnabled;
     bool                 mIsReceiveIp6FilterEnabled;
diff --git a/src/core/net/ip6_address.cpp b/src/core/net/ip6_address.cpp
index f5ed71d..808d531 100644
--- a/src/core/net/ip6_address.cpp
+++ b/src/core/net/ip6_address.cpp
@@ -49,7 +49,7 @@
 //---------------------------------------------------------------------------------------------------------------------
 // NetworkPrefix methods
 
-otError NetworkPrefix::GenerateRandomUla(void)
+Error NetworkPrefix::GenerateRandomUla(void)
 {
     m8[0] = 0xfd;
 
@@ -70,6 +70,23 @@
     return (mLength == aPrefixLength) && (MatchLength(GetBytes(), aPrefixBytes, GetBytesSize()) >= mLength);
 }
 
+bool Prefix::operator<(const Prefix &aOther) const
+{
+    bool    isSmaller;
+    uint8_t matchedLength;
+
+    VerifyOrExit(GetLength() == aOther.GetLength(), isSmaller = GetLength() < aOther.GetLength());
+
+    matchedLength = MatchLength(GetBytes(), aOther.GetBytes(), GetBytesSize());
+
+    VerifyOrExit(matchedLength < GetLength(), isSmaller = false);
+
+    isSmaller = GetBytes()[matchedLength / CHAR_BIT] < aOther.GetBytes()[matchedLength / CHAR_BIT];
+
+exit:
+    return isSmaller;
+}
+
 uint8_t Prefix::MatchLength(const uint8_t *aPrefixA, const uint8_t *aPrefixB, uint8_t aMaxSize)
 {
     uint8_t matchedLength = 0;
@@ -152,13 +169,13 @@
 
 void InterfaceIdentifier::GenerateRandom(void)
 {
-    otError error;
+    Error error;
 
     OT_UNUSED_VARIABLE(error);
 
     error = Random::Crypto::FillBuffer(mFields.m8, kSize);
 
-    OT_ASSERT(error == OT_ERROR_NONE);
+    OT_ASSERT(error == kErrorNone);
 }
 
 void InterfaceIdentifier::SetBytes(const uint8_t *aBuffer)
@@ -434,9 +451,9 @@
     return matches;
 }
 
-otError Address::FromString(const char *aBuf)
+Error Address::FromString(const char *aBuf)
 {
-    otError     error  = OT_ERROR_NONE;
+    Error       error  = kErrorNone;
     uint8_t *   dst    = reinterpret_cast<uint8_t *>(mFields.m8);
     uint8_t *   endp   = reinterpret_cast<uint8_t *>(mFields.m8 + 15);
     uint8_t *   colonp = nullptr;
@@ -465,7 +482,7 @@
         {
             if (count)
             {
-                VerifyOrExit(dst + 2 <= endp, error = OT_ERROR_PARSE);
+                VerifyOrExit(dst + 2 <= endp, error = kErrorParse);
                 *(dst + 1) = static_cast<uint8_t>(val >> 8);
                 *(dst + 2) = static_cast<uint8_t>(val);
                 dst += 2;
@@ -474,7 +491,7 @@
             }
             else if (ch == ':')
             {
-                VerifyOrExit(colonp == nullptr || first, error = OT_ERROR_PARSE);
+                VerifyOrExit(colonp == nullptr || first, error = kErrorParse);
                 colonp = dst;
             }
 
@@ -494,21 +511,21 @@
             // Do not count bytes of the embedded IPv4 address.
             endp -= kIp4AddressSize;
 
-            VerifyOrExit(dst <= endp, error = OT_ERROR_PARSE);
+            VerifyOrExit(dst <= endp, error = kErrorParse);
 
             break;
         }
         else
         {
-            VerifyOrExit('0' <= ch && ch <= '9', error = OT_ERROR_PARSE);
+            VerifyOrExit('0' <= ch && ch <= '9', error = kErrorParse);
         }
 
         first = false;
         val   = static_cast<uint16_t>((val << 4) | d);
-        VerifyOrExit(++count <= 4, error = OT_ERROR_PARSE);
+        VerifyOrExit(++count <= 4, error = kErrorParse);
     }
 
-    VerifyOrExit(colonp || dst == endp, error = OT_ERROR_PARSE);
+    VerifyOrExit(colonp || dst == endp, error = kErrorParse);
 
     while (colonp && dst > colonp)
     {
@@ -534,7 +551,7 @@
 
             if (ch == '.' || ch == '\0' || ch == ' ')
             {
-                VerifyOrExit(dst <= endp, error = OT_ERROR_PARSE);
+                VerifyOrExit(dst <= endp, error = kErrorParse);
 
                 *dst++ = static_cast<uint8_t>(val);
                 val    = 0;
@@ -542,18 +559,18 @@
                 if (ch == '\0' || ch == ' ')
                 {
                     // Check if embedded IPv4 address had exactly four parts.
-                    VerifyOrExit(dst == endp + 1, error = OT_ERROR_PARSE);
+                    VerifyOrExit(dst == endp + 1, error = kErrorParse);
                     break;
                 }
             }
             else
             {
-                VerifyOrExit('0' <= ch && ch <= '9', error = OT_ERROR_PARSE);
+                VerifyOrExit('0' <= ch && ch <= '9', error = kErrorParse);
 
                 val = (10 * val) + (ch & 0xf);
 
                 // Single part of IPv4 address has to fit in one byte.
-                VerifyOrExit(val <= 0xff, error = OT_ERROR_PARSE);
+                VerifyOrExit(val <= 0xff, error = kErrorParse);
             }
         }
     }
diff --git a/src/core/net/ip6_address.hpp b/src/core/net/ip6_address.hpp
index 00c82a0..d7aa689 100644
--- a/src/core/net/ip6_address.hpp
+++ b/src/core/net/ip6_address.hpp
@@ -74,11 +74,11 @@
      * This method generates and sets the Network Prefix to a crypto-secure random Unique Local Address (ULA) based
      * on the pattern `fdxx:xxxx:xxxx:` (RFC 4193).
      *
-     * @retval OT_ERROR_NONE     Successfully generated a random ULA Network Prefix
-     * @retval OT_ERROR_FAILED   Failed to generate random ULA Network Prefix.
+     * @retval kErrorNone     Successfully generated a random ULA Network Prefix
+     * @retval kErrorFailed   Failed to generate random ULA Network Prefix.
      *
      */
-    otError GenerateRandomUla(void);
+    Error GenerateRandomUla(void);
 
 } OT_TOOL_PACKED_END;
 
@@ -87,7 +87,7 @@
  *
  */
 OT_TOOL_PACKED_BEGIN
-class Prefix : public otIp6Prefix
+class Prefix : public otIp6Prefix, public Clearable<Prefix>, public Unequatable<Prefix>
 {
 public:
     enum : uint8_t
@@ -223,15 +223,18 @@
     }
 
     /**
-     * This method overloads operator `==` to evaluate whether or not two prefixes are unequal.
+     * This method overloads operator `<` to compare two prefixes.
      *
-     * @param[in]  aOther  The other prefix to compare with.
+     * A prefix with shorter length is considered smaller than the one with longer length. If the prefix lengths are
+     * equal, then the prefix bytes are compared directly.
      *
-     * @retval TRUE   If the two prefixes are unequal.
-     * @retval FALSE  If the two prefixes are not unequal.
+     * @param[in] aOther  The other prefix to compare against.
+     *
+     * @retval TRUE   If the prefix is smaller than @p aOther.
+     * @retval FALSE  If the prefix is not smaller than @p aOther.
      *
      */
-    bool operator!=(const Prefix &aOther) const { return !(*this == aOther); }
+    bool operator<(const Prefix &aOther) const;
 
     /**
      * This static method converts a prefix length (in bits) to size (number of bytes).
@@ -880,11 +883,11 @@
      *
      * @param[in]  aBuf  A pointer to the null-terminated string.
      *
-     * @retval OT_ERROR_NONE          Successfully parsed the IPv6 address string.
-     * @retval OT_ERROR_INVALID_ARGS  Failed to parse the IPv6 address string.
+     * @retval kErrorNone          Successfully parsed the IPv6 address string.
+     * @retval kErrorInvalidArgs   Failed to parse the IPv6 address string.
      *
      */
-    otError FromString(const char *aBuf);
+    Error FromString(const char *aBuf);
 
     /**
      * This method converts an IPv6 address object to a string
diff --git a/src/core/net/ip6_filter.cpp b/src/core/net/ip6_filter.cpp
index 4c25174..56603e3 100644
--- a/src/core/net/ip6_filter.cpp
+++ b/src/core/net/ip6_filter.cpp
@@ -120,11 +120,11 @@
     return rval;
 }
 
-otError Filter::AddUnsecurePort(uint16_t aPort)
+Error Filter::AddUnsecurePort(uint16_t aPort)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(aPort != 0, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aPort != 0, error = kErrorInvalidArgs);
 
     for (uint16_t unsecurePort : mUnsecurePorts)
     {
@@ -144,17 +144,17 @@
         }
     }
 
-    ExitNow(error = OT_ERROR_NO_BUFS);
+    ExitNow(error = kErrorNoBufs);
 
 exit:
     return error;
 }
 
-otError Filter::RemoveUnsecurePort(uint16_t aPort)
+Error Filter::RemoveUnsecurePort(uint16_t aPort)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(aPort != 0, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aPort != 0, error = kErrorInvalidArgs);
 
     for (int i = 0; i < kMaxUnsecurePorts; i++)
     {
@@ -174,7 +174,7 @@
         }
     }
 
-    ExitNow(error = OT_ERROR_NOT_FOUND);
+    ExitNow(error = kErrorNotFound);
 
 exit:
     return error;
diff --git a/src/core/net/ip6_filter.hpp b/src/core/net/ip6_filter.hpp
index 5fc6374..8549809 100644
--- a/src/core/net/ip6_filter.hpp
+++ b/src/core/net/ip6_filter.hpp
@@ -81,24 +81,24 @@
      *
      * @param[in]  aPort  The port value.
      *
-     * @retval OT_ERROR_NONE         The port was successfully added to the allowed unsecure port list.
-     * @retval OT_ERROR_INVALID_ARGS The port is invalid (value 0 is reserved for internal use).
-     * @retval OT_ERROR_NO_BUFS      The unsecure port list is full.
+     * @retval kErrorNone         The port was successfully added to the allowed unsecure port list.
+     * @retval kErrorInvalidArgs  The port is invalid (value 0 is reserved for internal use).
+     * @retval kErrorNoBufs       The unsecure port list is full.
      *
      */
-    otError AddUnsecurePort(uint16_t aPort);
+    Error AddUnsecurePort(uint16_t aPort);
 
     /**
      * This method removes a port from the allowed unsecure port list.
      *
      * @param[in]  aPort  The port value.
      *
-     * @retval OT_ERROR_NONE         The port was successfully removed from the allowed unsecure port list.
-     * @retval OT_ERROR_INVALID_ARGS The port is invalid (value 0 is reserved for internal use).
-     * @retval OT_ERROR_NOT_FOUND    The port was not found in the unsecure port list.
+     * @retval kErrorNone         The port was successfully removed from the allowed unsecure port list.
+     * @retval kErrorInvalidArgs  The port is invalid (value 0 is reserved for internal use).
+     * @retval kErrorNotFound     The port was not found in the unsecure port list.
      *
      */
-    otError RemoveUnsecurePort(uint16_t aPort);
+    Error RemoveUnsecurePort(uint16_t aPort);
 
     /**
      * This method checks whether a port is in the unsecure port list.
diff --git a/src/core/net/ip6_headers.cpp b/src/core/net/ip6_headers.cpp
index 78ad218..f545d2c 100644
--- a/src/core/net/ip6_headers.cpp
+++ b/src/core/net/ip6_headers.cpp
@@ -38,14 +38,14 @@
 namespace ot {
 namespace Ip6 {
 
-otError Header::Init(const Message &aMessage)
+Error Header::Init(const Message &aMessage)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     SuccessOrExit(error = aMessage.Read(0, *this));
 
-    VerifyOrExit(IsValid(), error = OT_ERROR_PARSE);
-    VerifyOrExit((sizeof(*this) + GetPayloadLength()) == aMessage.GetLength(), error = OT_ERROR_PARSE);
+    VerifyOrExit(IsValid(), error = kErrorParse);
+    VerifyOrExit((sizeof(*this) + GetPayloadLength()) == aMessage.GetLength(), error = kErrorParse);
 
 exit:
     return error;
diff --git a/src/core/net/ip6_headers.hpp b/src/core/net/ip6_headers.hpp
index 88d6012..422de7c 100644
--- a/src/core/net/ip6_headers.hpp
+++ b/src/core/net/ip6_headers.hpp
@@ -151,11 +151,11 @@
      *
      * @param[in]  aMessage  The IPv6 datagram.
      *
-     * @retval OT_ERROR_NONE   Successfully read the IPv6 header.
-     * @retval OT_ERROR_PARSE  Malformed IPv6 header.
+     * @retval kErrorNone   Successfully read the IPv6 header.
+     * @retval kErrorParse  Malformed IPv6 header.
      *
      */
-    otError Init(const Message &aMessage);
+    Error Init(const Message &aMessage);
 
     /**
      * This method indicates whether or not the header appears to be well-formed.
diff --git a/src/core/net/ip6_mpl.cpp b/src/core/net/ip6_mpl.cpp
index d59af2c..b4b3bbd 100644
--- a/src/core/net/ip6_mpl.cpp
+++ b/src/core/net/ip6_mpl.cpp
@@ -46,11 +46,11 @@
 Mpl::Mpl(Instance &aInstance)
     : InstanceLocator(aInstance)
     , mMatchingAddress(nullptr)
-    , mSeedSetTimer(aInstance, Mpl::HandleSeedSetTimer, this)
+    , mSeedSetTimer(aInstance, Mpl::HandleSeedSetTimer)
     , mSeedId(0)
     , mSequence(0)
 #if OPENTHREAD_FTD
-    , mRetransmissionTimer(aInstance, Mpl::HandleRetransmissionTimer, this)
+    , mRetransmissionTimer(aInstance, Mpl::HandleRetransmissionTimer)
     , mTimerExpirations(0)
 #endif
 {
@@ -77,15 +77,15 @@
     }
 }
 
-otError Mpl::ProcessOption(Message &aMessage, const Address &aAddress, bool aIsOutbound, bool &aReceive)
+Error Mpl::ProcessOption(Message &aMessage, const Address &aAddress, bool aIsOutbound, bool &aReceive)
 {
-    otError   error;
+    Error     error;
     OptionMpl option;
 
     VerifyOrExit(aMessage.ReadBytes(aMessage.GetOffset(), &option, sizeof(option)) >= OptionMpl::kMinLength &&
                      (option.GetSeedIdLength() == OptionMpl::kSeedIdLength0 ||
                       option.GetSeedIdLength() == OptionMpl::kSeedIdLength2),
-                 error = OT_ERROR_PARSE);
+                 error = kErrorParse);
 
     if (option.GetSeedIdLength() == OptionMpl::kSeedIdLength0)
     {
@@ -96,7 +96,7 @@
     // Check if the MPL Data Message is new.
     error = UpdateSeedSet(option.GetSeedId(), option.GetSequence());
 
-    if (error == OT_ERROR_NONE)
+    if (error == kErrorNone)
     {
 #if OPENTHREAD_FTD
         AddBufferedMessage(aMessage, option.GetSeedId(), option.GetSequence(), aIsOutbound);
@@ -107,7 +107,7 @@
         aReceive = false;
         // In case MPL Data Message is generated locally, ignore potential error of the MPL Seed Set
         // to allow subsequent retransmissions with the same sequence number.
-        ExitNow(error = OT_ERROR_NONE);
+        ExitNow(error = kErrorNone);
     }
 
 exit:
@@ -135,9 +135,9 @@
  *   - Require group size to have >=2 entries.
  *   - If inserting into existing group, require Sequence to be larger than oldest stored Sequence in group.
  */
-otError Mpl::UpdateSeedSet(uint16_t aSeedId, uint8_t aSequence)
+Error Mpl::UpdateSeedSet(uint16_t aSeedId, uint8_t aSequence)
 {
-    otError    error    = OT_ERROR_NONE;
+    Error      error    = kErrorNone;
     SeedEntry *insert   = nullptr;
     SeedEntry *group    = mSeedSet;
     SeedEntry *evict    = mSeedSet;
@@ -192,7 +192,7 @@
             if (diff == 0)
             {
                 // already received, drop message
-                ExitNow(error = OT_ERROR_DROP);
+                ExitNow(error = kErrorDrop);
             }
             else if (insert == nullptr && diff < 0)
             {
@@ -223,7 +223,7 @@
         }
 
         // require evict group size to have >= 2 entries
-        VerifyOrExit(maxCount > 1, error = OT_ERROR_DROP);
+        VerifyOrExit(maxCount > 1, error = kErrorDrop);
 
         if (insert == nullptr)
         {
@@ -233,7 +233,7 @@
         else
         {
             // require Sequence to be larger than oldest stored Sequence in group
-            VerifyOrExit(insert > mSeedSet && aSeedId == (insert - 1)->mSeedId, error = OT_ERROR_DROP);
+            VerifyOrExit(insert > mSeedSet && aSeedId == (insert - 1)->mSeedId, error = kErrorDrop);
         }
     }
 
@@ -264,7 +264,7 @@
 
 void Mpl::HandleSeedSetTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<Mpl>().HandleSeedSetTimer();
+    aTimer.Get<Mpl>().HandleSeedSetTimer();
 }
 
 void Mpl::HandleSeedSetTimer(void)
@@ -298,7 +298,7 @@
 
 void Mpl::AddBufferedMessage(Message &aMessage, uint16_t aSeedId, uint8_t aSequence, bool aIsOutbound)
 {
-    otError  error       = OT_ERROR_NONE;
+    Error    error       = kErrorNone;
     Message *messageCopy = nullptr;
     Metadata metadata;
     uint8_t  hopLimit = 0;
@@ -311,12 +311,12 @@
 #endif
 
     VerifyOrExit(GetTimerExpirations() > 0);
-    VerifyOrExit((messageCopy = aMessage.Clone()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((messageCopy = aMessage.Clone()) != nullptr, error = kErrorNoBufs);
 
     if (!aIsOutbound)
     {
         IgnoreError(aMessage.Read(Header::kHopLimitFieldOffset, hopLimit));
-        VerifyOrExit(hopLimit-- > 1, error = OT_ERROR_DROP);
+        VerifyOrExit(hopLimit-- > 1, error = kErrorDrop);
         messageCopy->Write(Header::kHopLimitFieldOffset, hopLimit);
     }
 
@@ -337,7 +337,7 @@
 
 void Mpl::HandleRetransmissionTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<Mpl>().HandleRetransmissionTimer();
+    aTimer.Get<Mpl>().HandleRetransmissionTimer();
 }
 
 void Mpl::HandleRetransmissionTimer(void)
@@ -427,9 +427,9 @@
 
 void Mpl::Metadata::RemoveFrom(Message &aMessage) const
 {
-    otError error = aMessage.SetLength(aMessage.GetLength() - sizeof(*this));
+    Error error = aMessage.SetLength(aMessage.GetLength() - sizeof(*this));
 
-    OT_ASSERT(error == OT_ERROR_NONE);
+    OT_ASSERT(error == kErrorNone);
     OT_UNUSED_VARIABLE(error);
 }
 
diff --git a/src/core/net/ip6_mpl.hpp b/src/core/net/ip6_mpl.hpp
index 9549b72..1caab6e 100644
--- a/src/core/net/ip6_mpl.hpp
+++ b/src/core/net/ip6_mpl.hpp
@@ -219,11 +219,11 @@
      * @param[out] aReceive    Set to FALSE if the MPL message is a duplicate and must not
      *                         go through the receiving process again, untouched otherwise.
      *
-     * @retval OT_ERROR_NONE  Successfully processed the MPL option.
-     * @retval OT_ERROR_DROP  The MPL message is a duplicate and should be dropped.
+     * @retval kErrorNone  Successfully processed the MPL option.
+     * @retval kErrorDrop  The MPL message is a duplicate and should be dropped.
      *
      */
-    otError ProcessOption(Message &aMessage, const Address &aAddress, bool aIsOutbound, bool &aReceive);
+    Error ProcessOption(Message &aMessage, const Address &aAddress, bool aIsOutbound, bool &aReceive);
 
     /**
      * This method returns the MPL Seed Id value.
@@ -296,7 +296,7 @@
     static void HandleSeedSetTimer(Timer &aTimer);
     void        HandleSeedSetTimer(void);
 
-    otError UpdateSeedSet(uint16_t aSeedId, uint8_t aSequence);
+    Error UpdateSeedSet(uint16_t aSeedId, uint8_t aSequence);
 
     SeedEntry      mSeedSet[kNumSeedEntries];
     const Address *mMatchingAddress;
@@ -307,11 +307,11 @@
 #if OPENTHREAD_FTD
     struct Metadata
     {
-        otError AppendTo(Message &aMessage) const { return aMessage.Append(*this); }
-        void    ReadFrom(const Message &aMessage);
-        void    RemoveFrom(Message &aMessage) const;
-        void    UpdateIn(Message &aMessage) const;
-        void    GenerateNextTransmissionTime(TimeMilli aCurrentTime, uint8_t aInterval);
+        Error AppendTo(Message &aMessage) const { return aMessage.Append(*this); }
+        void  ReadFrom(const Message &aMessage);
+        void  RemoveFrom(Message &aMessage) const;
+        void  UpdateIn(Message &aMessage) const;
+        void  GenerateNextTransmissionTime(TimeMilli aCurrentTime, uint8_t aInterval);
 
         TimeMilli mTransmissionTime;
         uint16_t  mSeedId;
diff --git a/src/core/net/netif.cpp b/src/core/net/netif.cpp
index e733fa0..4dc6b66 100644
--- a/src/core/net/netif.cpp
+++ b/src/core/net/netif.cpp
@@ -208,7 +208,7 @@
 
 void Netif::SubscribeAllRoutersMulticast(void)
 {
-    otError                error                      = OT_ERROR_NONE;
+    Error                  error                      = kErrorNone;
     NetifMulticastAddress *prev                       = nullptr;
     NetifMulticastAddress &linkLocalAllRoutersAddress = static_cast<NetifMulticastAddress &>(
         const_cast<otNetifMulticastAddress &>(kLinkLocalAllRoutersMulticastAddress));
@@ -222,7 +222,7 @@
     // This method MUST be called after `SubscribeAllNodesMulticast()`
     // Ensure that the `LinkLocalAll` was found on the list.
 
-    OT_ASSERT(error == OT_ERROR_NONE);
+    OT_ASSERT(error == kErrorNone);
     OT_UNUSED_VARIABLE(error);
 
     // The tail of multicast address linked list contains the
@@ -355,15 +355,15 @@
     return;
 }
 
-otError Netif::SubscribeExternalMulticast(const Address &aAddress)
+Error Netif::SubscribeExternalMulticast(const Address &aAddress)
 {
-    otError                error                      = OT_ERROR_NONE;
+    Error                  error                      = kErrorNone;
     NetifMulticastAddress &linkLocalAllRoutersAddress = static_cast<NetifMulticastAddress &>(
         const_cast<otNetifMulticastAddress &>(kLinkLocalAllRoutersMulticastAddress));
     ExternalNetifMulticastAddress *entry;
 
-    VerifyOrExit(aAddress.IsMulticast(), error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit(!IsMulticastSubscribed(aAddress), error = OT_ERROR_ALREADY);
+    VerifyOrExit(aAddress.IsMulticast(), error = kErrorInvalidArgs);
+    VerifyOrExit(!IsMulticastSubscribed(aAddress), error = kErrorAlready);
 
     // Check that the address is not one of the fixed addresses:
     // LinkLocalAllRouters -> RealmLocalAllRouters -> LinkLocalAllNodes
@@ -371,11 +371,11 @@
 
     for (const NetifMulticastAddress *cur = &linkLocalAllRoutersAddress; cur; cur = cur->GetNext())
     {
-        VerifyOrExit(cur->GetAddress() != aAddress, error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit(cur->GetAddress() != aAddress, error = kErrorInvalidArgs);
     }
 
     entry = mExtMulticastAddressPool.Allocate();
-    VerifyOrExit(entry != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(entry != nullptr, error = kErrorNoBufs);
 
     entry->mAddress = aAddress;
 #if OPENTHREAD_CONFIG_MLR_ENABLE
@@ -388,16 +388,16 @@
     return error;
 }
 
-otError Netif::UnsubscribeExternalMulticast(const Address &aAddress)
+Error Netif::UnsubscribeExternalMulticast(const Address &aAddress)
 {
-    otError                error = OT_ERROR_NONE;
+    Error                  error = kErrorNone;
     NetifMulticastAddress *entry;
     NetifMulticastAddress *prev;
 
     entry = mMulticastAddresses.FindMatching(aAddress, prev);
-    VerifyOrExit(entry != nullptr, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(entry != nullptr, error = kErrorNotFound);
 
-    VerifyOrExit(IsMulticastAddressExternal(*entry), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(IsMulticastAddressExternal(*entry), error = kErrorInvalidArgs);
 
     mMulticastAddresses.PopAfter(prev);
 
@@ -466,18 +466,18 @@
     return;
 }
 
-otError Netif::AddExternalUnicastAddress(const NetifUnicastAddress &aAddress)
+Error Netif::AddExternalUnicastAddress(const NetifUnicastAddress &aAddress)
 {
-    otError              error = OT_ERROR_NONE;
+    Error                error = kErrorNone;
     NetifUnicastAddress *entry;
 
-    VerifyOrExit(!aAddress.GetAddress().IsMulticast(), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(!aAddress.GetAddress().IsMulticast(), error = kErrorInvalidArgs);
 
     entry = mUnicastAddresses.FindMatching(aAddress.GetAddress());
 
     if (entry != nullptr)
     {
-        VerifyOrExit(IsUnicastAddressExternal(*entry), error = OT_ERROR_ALREADY);
+        VerifyOrExit(IsUnicastAddressExternal(*entry), error = kErrorAlready);
 
         entry->mPrefixLength  = aAddress.mPrefixLength;
         entry->mAddressOrigin = aAddress.mAddressOrigin;
@@ -486,10 +486,10 @@
         ExitNow();
     }
 
-    VerifyOrExit(!aAddress.GetAddress().IsLinkLocal(), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(!aAddress.GetAddress().IsLinkLocal(), error = kErrorInvalidArgs);
 
     entry = mExtUnicastAddressPool.Allocate();
-    VerifyOrExit(entry != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(entry != nullptr, error = kErrorNoBufs);
 
     *entry = aAddress;
     mUnicastAddresses.Push(*entry);
@@ -499,16 +499,16 @@
     return error;
 }
 
-otError Netif::RemoveExternalUnicastAddress(const Address &aAddress)
+Error Netif::RemoveExternalUnicastAddress(const Address &aAddress)
 {
-    otError              error = OT_ERROR_NONE;
+    Error                error = kErrorNone;
     NetifUnicastAddress *entry;
     NetifUnicastAddress *prev;
 
     entry = mUnicastAddresses.FindMatching(aAddress, prev);
-    VerifyOrExit(entry != nullptr, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(entry != nullptr, error = kErrorNotFound);
 
-    VerifyOrExit(IsUnicastAddressExternal(*entry), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(IsUnicastAddressExternal(*entry), error = kErrorInvalidArgs);
 
     mUnicastAddresses.PopAfter(prev);
     mExtUnicastAddressPool.Free(*entry);
diff --git a/src/core/net/netif.hpp b/src/core/net/netif.hpp
index ad2ef31..ca0448f 100644
--- a/src/core/net/netif.hpp
+++ b/src/core/net/netif.hpp
@@ -38,6 +38,7 @@
 
 #include "common/clearable.hpp"
 #include "common/code_utils.hpp"
+#include "common/iterator_utils.hpp"
 #include "common/linked_list.hpp"
 #include "common/locator.hpp"
 #include "common/message.hpp"
@@ -273,7 +274,9 @@
      *
      */
     class ExternalMulticastAddressIterator
+        : public ItemPtrIterator<ExternalNetifMulticastAddress, ExternalMulticastAddressIterator>
     {
+        friend class ItemPtrIterator<ExternalNetifMulticastAddress, ExternalMulticastAddressIterator>;
         friend class ExternalMulticastAddressIteratorBuilder;
 
     public:
@@ -286,88 +289,13 @@
          *
          */
         explicit ExternalMulticastAddressIterator(const Netif &aNetif, Address::TypeFilter aFilter = Address::kTypeAny)
-            : mNetif(aNetif)
+            : ItemPtrIterator(nullptr)
+            , mNetif(aNetif)
             , mFilter(aFilter)
         {
             AdvanceFrom(mNetif.GetMulticastAddresses());
         }
 
-        /**
-         * This method indicates whether the iterator has reached end of the list.
-         *
-         * @retval TRUE   There are no more entries in the list (reached end of the list).
-         * @retval FALSE  The current address entry is valid.
-         *
-         */
-        bool IsDone(void) const { return mCurrent != nullptr; }
-
-        /**
-         * This method overloads `++` operator (pre-increment) to advance the iterator.
-         *
-         * The iterator is moved to point to the next entry.  If there are no more entries matching the iterator becomes
-         * empty.
-         *
-         */
-        void operator++(void) { AdvanceFrom(mCurrent->GetNext()); }
-
-        /**
-         * This method overloads `++` operator (post-increment) to advance the iterator.
-         *
-         * The iterator is moved to point to the next entry.  If there are no more entries matching the iterator becomes
-         * empty.
-         *
-         */
-        void operator++(int) { AdvanceFrom(mCurrent->GetNext()); }
-
-        /**
-         * This method overloads the `*` dereference operator and gets a reference to `ExternalNetifMulticastAddress`
-         * entry to which the iterator is currently pointing.
-         *
-         * This method MUST be used when the iterator is not empty.
-         *
-         * @returns A reference to the `ExternalNetifMulticastAddress` entry currently pointed by the iterator.
-         *
-         */
-        ExternalNetifMulticastAddress &operator*(void) { return *mCurrent; }
-
-        /**
-         * This method overloads the `->` dereference operator and gets a pointer to `ExternalNetifMulticastAddress`
-         * entry to which the iterator is current pointing.
-         *
-         * @returns A pointer to the `ExternalNetifMulticastAddress` entry associated with the iterator, or `nullptr` if
-         * iterator is empty.
-         *
-         */
-        ExternalNetifMulticastAddress *operator->(void) { return mCurrent; }
-
-        /**
-         * This method overloads operator `==` to evaluate whether or not two `ExternalMulticastAddressIterator`
-         * instances point to the same `ExternalNetifMulticastAddress` entry.
-         *
-         * @param[in] aOther  The other `Iterator` to compare with.
-         *
-         * @retval TRUE   If the two `ExternalMulticastAddressIterator` objects point to the same
-         * `ExternalNetifMulticastAddress` entry or both are done.
-         * @retval FALSE  If the two `ExternalMulticastAddressIterator` objects do not point to the same
-         * `ExternalNetifMulticastAddress` entry.
-         *
-         */
-        bool operator==(const ExternalMulticastAddressIterator &aOther) { return mCurrent == aOther.mCurrent; }
-
-        /**
-         * This method overloads operator `!=` to evaluate whether or not two `ExternalMulticastAddressIterator`
-         * instances point to the same `ExternalNetifMulticastAddress` entry.
-         *
-         * @param[in]  aOther  The other `ExternalMulticastAddressIterator` to compare with.
-         *
-         * @retval TRUE   If the two `ExternalMulticastAddressIterator` objects do not point to the same
-         * `ExternalNetifMulticastAddress` entry.
-         * @retval FALSE  If the two `ExternalMulticastAddressIterator` objects point to the same
-         * `ExternalNetifMulticastAddress` entry or both are done.
-         *
-         */
-        bool operator!=(const ExternalMulticastAddressIterator &aOther) { return mCurrent != aOther.mCurrent; }
-
     private:
         enum IteratorType
         {
@@ -376,7 +304,6 @@
 
         ExternalMulticastAddressIterator(const Netif &aNetif, IteratorType)
             : mNetif(aNetif)
-            , mCurrent(nullptr)
         {
         }
 
@@ -388,13 +315,14 @@
                 aAddr = aAddr->GetNext();
             }
 
-            mCurrent =
+            mItem =
                 const_cast<ExternalNetifMulticastAddress *>(static_cast<const ExternalNetifMulticastAddress *>(aAddr));
         }
 
-        const Netif &                  mNetif;
-        ExternalNetifMulticastAddress *mCurrent;
-        Address::TypeFilter            mFilter;
+        void Advance(void) { AdvanceFrom(mItem->GetNext()); }
+
+        const Netif &       mNetif;
+        Address::TypeFilter mFilter;
     };
 
     /**
@@ -486,24 +414,24 @@
      *
      * @param[in]  aAddress  A reference to the unicast address.
      *
-     * @retval OT_ERROR_NONE          Successfully added (or updated) the unicast address.
-     * @retval OT_ERROR_INVALID_ARGS  The address indicated by @p aAddress is an internal address.
-     * @retval OT_ERROR_NO_BUFS       The maximum number of allowed external addresses are already added.
+     * @retval kErrorNone         Successfully added (or updated) the unicast address.
+     * @retval kErrorInvalidArgs  The address indicated by @p aAddress is an internal address.
+     * @retval kErrorNoBufs       The maximum number of allowed external addresses are already added.
      *
      */
-    otError AddExternalUnicastAddress(const NetifUnicastAddress &aAddress);
+    Error AddExternalUnicastAddress(const NetifUnicastAddress &aAddress);
 
     /**
      * This method removes a external (to OpenThread) unicast address from the network interface.
      *
      * @param[in]  aAddress  A reference to the unicast address.
      *
-     * @retval OT_ERROR_NONE          Successfully removed the unicast address.
-     * @retval OT_ERROR_INVALID_ARGS  The address indicated by @p aAddress is an internal address.
-     * @retval OT_ERROR_NOT_FOUND     The unicast address was not found.
+     * @retval kErrorNone         Successfully removed the unicast address.
+     * @retval kErrorInvalidArgs  The address indicated by @p aAddress is an internal address.
+     * @retval kErrorNotFound     The unicast address was not found.
      *
      */
-    otError RemoveExternalUnicastAddress(const Address &aAddress);
+    Error RemoveExternalUnicastAddress(const Address &aAddress);
 
     /**
      * This method removes all the previously added external (to OpenThread) unicast addresses from the
@@ -587,25 +515,25 @@
      *
      * @param[in]  aAddress  A reference to the multicast address.
      *
-     * @retval OT_ERROR_NONE           Successfully subscribed to @p aAddress.
-     * @retval OT_ERROR_ALREADY        The multicast address is already subscribed.
-     * @retval OT_ERROR_INVALID_ARGS   The address indicated by @p aAddress is an internal multicast address.
-     * @retval OT_ERROR_NO_BUFS        The maximum number of allowed external multicast addresses are already added.
+     * @retval kErrorNone          Successfully subscribed to @p aAddress.
+     * @retval kErrorAlready       The multicast address is already subscribed.
+     * @retval kErrorInvalidArgs   The address indicated by @p aAddress is an internal multicast address.
+     * @retval kErrorNoBufs        The maximum number of allowed external multicast addresses are already added.
      *
      */
-    otError SubscribeExternalMulticast(const Address &aAddress);
+    Error SubscribeExternalMulticast(const Address &aAddress);
 
     /**
      * This method unsubscribes the network interface to the external (to OpenThread) multicast address.
      *
      * @param[in]  aAddress  A reference to the multicast address.
      *
-     * @retval OT_ERROR_NONE          Successfully unsubscribed to the unicast address.
-     * @retval OT_ERROR_INVALID_ARGS  The address indicated by @p aAddress is an internal address.
-     * @retval OT_ERROR_NOT_FOUND     The multicast address was not found.
+     * @retval kErrorNone         Successfully unsubscribed to the unicast address.
+     * @retval kErrorInvalidArgs  The address indicated by @p aAddress is an internal address.
+     * @retval kErrorNotFound     The multicast address was not found.
      *
      */
-    otError UnsubscribeExternalMulticast(const Address &aAddress);
+    Error UnsubscribeExternalMulticast(const Address &aAddress);
 
     /**
      * This method unsubscribes the network interface from all previously added external (to OpenThread) multicast
diff --git a/src/core/net/sntp_client.cpp b/src/core/net/sntp_client.cpp
index 40ce98d..c2be9ee 100644
--- a/src/core/net/sntp_client.cpp
+++ b/src/core/net/sntp_client.cpp
@@ -92,14 +92,14 @@
 
 Client::Client(Instance &aInstance)
     : mSocket(aInstance)
-    , mRetransmissionTimer(aInstance, Client::HandleRetransmissionTimer, this)
+    , mRetransmissionTimer(aInstance, Client::HandleRetransmissionTimer)
     , mUnixEra(0)
 {
 }
 
-otError Client::Start(void)
+Error Client::Start(void)
 {
-    otError error;
+    Error error;
 
     SuccessOrExit(error = mSocket.Open(&Client::HandleUdpReceive, this));
     SuccessOrExit(error = mSocket.Bind());
@@ -108,7 +108,7 @@
     return error;
 }
 
-otError Client::Stop(void)
+Error Client::Stop(void)
 {
     Message *     message = mPendingQueries.GetHead();
     Message *     messageToRemove;
@@ -121,27 +121,27 @@
         message         = message->GetNext();
 
         queryMetadata.ReadFrom(*messageToRemove);
-        FinalizeSntpTransaction(*messageToRemove, queryMetadata, 0, OT_ERROR_ABORT);
+        FinalizeSntpTransaction(*messageToRemove, queryMetadata, 0, kErrorAbort);
     }
 
     return mSocket.Close();
 }
 
-otError Client::Query(const otSntpQuery *aQuery, otSntpResponseHandler aHandler, void *aContext)
+Error Client::Query(const otSntpQuery *aQuery, otSntpResponseHandler aHandler, void *aContext)
 {
-    otError                 error;
+    Error                   error;
     QueryMetadata           queryMetadata(aHandler, aContext);
     Message *               message     = nullptr;
     Message *               messageCopy = nullptr;
     Header                  header;
     const Ip6::MessageInfo *messageInfo;
 
-    VerifyOrExit(aQuery->mMessageInfo != nullptr, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aQuery->mMessageInfo != nullptr, error = kErrorInvalidArgs);
 
     // Originate timestamp is used only as a unique token.
     header.SetTransmitTimestampSeconds(TimerMilli::GetNow().GetValue() / 1000 + kTimeAt1970);
 
-    VerifyOrExit((message = NewMessage(header)) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMessage(header)) != nullptr, error = kErrorNoBufs);
 
     messageInfo = static_cast<const Ip6::MessageInfo *>(aQuery->mMessageInfo);
 
@@ -152,12 +152,12 @@
     queryMetadata.mDestinationAddress  = messageInfo->GetPeerAddr();
     queryMetadata.mRetransmissionCount = 0;
 
-    VerifyOrExit((messageCopy = CopyAndEnqueueMessage(*message, queryMetadata)) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((messageCopy = CopyAndEnqueueMessage(*message, queryMetadata)) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = SendMessage(*message, *messageInfo));
 
 exit:
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         if (message)
         {
@@ -187,11 +187,11 @@
 
 Message *Client::CopyAndEnqueueMessage(const Message &aMessage, const QueryMetadata &aQueryMetadata)
 {
-    otError  error       = OT_ERROR_NONE;
+    Error    error       = kErrorNone;
     Message *messageCopy = nullptr;
 
     // Create a message copy for further retransmissions.
-    VerifyOrExit((messageCopy = aMessage.Clone()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((messageCopy = aMessage.Clone()) != nullptr, error = kErrorNoBufs);
 
     // Append the copy with retransmission data and add it to the queue.
     SuccessOrExit(error = aQueryMetadata.AppendTo(*messageCopy));
@@ -218,28 +218,28 @@
     aMessage.Free();
 }
 
-otError Client::SendMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
+Error Client::SendMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
     return mSocket.SendTo(aMessage, aMessageInfo);
 }
 
 void Client::SendCopy(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError  error;
+    Error    error;
     Message *messageCopy = nullptr;
 
     // Create a message copy for lower layers.
     VerifyOrExit((messageCopy = aMessage.Clone(aMessage.GetLength() - sizeof(QueryMetadata))) != nullptr,
-                 error = OT_ERROR_NO_BUFS);
+                 error = kErrorNoBufs);
 
     // Send the copy.
     SuccessOrExit(error = SendMessage(*messageCopy, aMessageInfo));
 
 exit:
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         FreeMessage(messageCopy);
-        otLogWarnIp6("Failed to send SNTP request: %s", otThreadErrorToString(error));
+        otLogWarnIp6("Failed to send SNTP request: %s", ErrorToString(error));
     }
 }
 
@@ -268,7 +268,7 @@
 void Client::FinalizeSntpTransaction(Message &            aQuery,
                                      const QueryMetadata &aQueryMetadata,
                                      uint64_t             aTime,
-                                     otError              aResult)
+                                     Error                aResult)
 {
     DequeueMessage(aQuery);
 
@@ -280,7 +280,7 @@
 
 void Client::HandleRetransmissionTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<Client>().HandleRetransmissionTimer();
+    aTimer.Get<Client>().HandleRetransmissionTimer();
 }
 
 void Client::HandleRetransmissionTimer(void)
@@ -303,7 +303,7 @@
             if (queryMetadata.mRetransmissionCount >= kMaxRetransmit)
             {
                 // No expected response.
-                FinalizeSntpTransaction(*message, queryMetadata, 0, OT_ERROR_RESPONSE_TIMEOUT);
+                FinalizeSntpTransaction(*message, queryMetadata, 0, kErrorResponseTimeout);
                 continue;
             }
 
@@ -342,7 +342,7 @@
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
 
-    otError       error = OT_ERROR_NONE;
+    Error         error = kErrorNone;
     Header        responseHeader;
     QueryMetadata queryMetadata;
     Message *     message  = nullptr;
@@ -353,7 +353,7 @@
     VerifyOrExit((message = FindRelatedQuery(responseHeader, queryMetadata)) != nullptr);
 
     // Check if response came from the server.
-    VerifyOrExit(responseHeader.GetMode() == Header::kModeServer, error = OT_ERROR_FAILED);
+    VerifyOrExit(responseHeader.GetMode() == Header::kModeServer, error = kErrorFailed);
 
     // Check the Kiss-o'-death packet.
     if (!responseHeader.GetStratum())
@@ -364,13 +364,13 @@
         kissCode[Header::kKissCodeLength] = 0;
 
         otLogInfoIp6("SNTP response contains the Kiss-o'-death packet with %s code", kissCode);
-        ExitNow(error = OT_ERROR_BUSY);
+        ExitNow(error = kErrorBusy);
     }
 
     // Check if timestamp has been set.
     VerifyOrExit(responseHeader.GetTransmitTimestampSeconds() != 0 &&
                      responseHeader.GetTransmitTimestampFraction() != 0,
-                 error = OT_ERROR_FAILED);
+                 error = kErrorFailed);
 
     // The NTP time starts at 1900 while the unix epoch starts at 1970.
     // Due to NTP protocol limitation, this module stops working correctly after around year 2106, if
@@ -388,11 +388,11 @@
     }
 
     // Return the time since 1970.
-    FinalizeSntpTransaction(*message, queryMetadata, unixTime, OT_ERROR_NONE);
+    FinalizeSntpTransaction(*message, queryMetadata, unixTime, kErrorNone);
 
 exit:
 
-    if (message != nullptr && error != OT_ERROR_NONE)
+    if (message != nullptr && error != kErrorNone)
     {
         FinalizeSntpTransaction(*message, queryMetadata, 0, error);
     }
diff --git a/src/core/net/sntp_client.hpp b/src/core/net/sntp_client.hpp
index a5b55b5..ed1eb59 100644
--- a/src/core/net/sntp_client.hpp
+++ b/src/core/net/sntp_client.hpp
@@ -441,11 +441,11 @@
      *
      * @param[in]  aMessage  A reference to the message.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the bytes.
-     * @retval OT_ERROR_NO_BUFS  Insufficient available buffers to grow the message.
+     * @retval kErrorNone    Successfully appended the bytes.
+     * @retval kErrorNoBufs  Insufficient available buffers to grow the message.
      *
      */
-    otError AppendTo(Message &aMessage) const { return aMessage.Append(*this); }
+    Error AppendTo(Message &aMessage) const { return aMessage.Append(*this); }
 
     /**
      * This method reads request data from the message.
@@ -455,9 +455,9 @@
      */
     void ReadFrom(const Message &aMessage)
     {
-        otError error = aMessage.Read(aMessage.GetLength() - sizeof(*this), *this);
+        Error error = aMessage.Read(aMessage.GetLength() - sizeof(*this), *this);
 
-        OT_ASSERT(error == OT_ERROR_NONE);
+        OT_ASSERT(error == kErrorNone);
         OT_UNUSED_VARIABLE(error);
     }
 
@@ -498,18 +498,18 @@
     /**
      * This method starts the SNTP client.
      *
-     * @retval OT_ERROR_NONE     Successfully started the SNTP client.
-     * @retval OT_ERROR_ALREADY  The socket is already open.
+     * @retval kErrorNone     Successfully started the SNTP client.
+     * @retval kErrorAlready  The socket is already open.
      */
-    otError Start(void);
+    Error Start(void);
 
     /**
      * This method stops the SNTP client.
      *
-     * @retval OT_ERROR_NONE  Successfully stopped the SNTP client.
+     * @retval kErrorNone  Successfully stopped the SNTP client.
      *
      */
-    otError Stop(void);
+    Error Stop(void);
 
     /**
      * This method returns the unix era number.
@@ -534,12 +534,12 @@
      * @param[in]  aHandler  A function pointer that shall be called on response reception or time-out.
      * @param[in]  aContext  A pointer to arbitrary context information.
      *
-     * @retval OT_ERROR_NONE          Successfully sent SNTP query.
-     * @retval OT_ERROR_NO_BUFS       Failed to allocate retransmission data.
-     * @retval OT_ERROR_INVALID_ARGS  Invalid arguments supplied.
+     * @retval kErrorNone         Successfully sent SNTP query.
+     * @retval kErrorNoBufs       Failed to allocate retransmission data.
+     * @retval kErrorInvalidArgs  Invalid arguments supplied.
      *
      */
-    otError Query(const otSntpQuery *aQuery, otSntpResponseHandler aHandler, void *aContext);
+    Error Query(const otSntpQuery *aQuery, otSntpResponseHandler aHandler, void *aContext);
 
 private:
     /**
@@ -568,11 +568,11 @@
     Message *NewMessage(const Header &aHeader);
     Message *CopyAndEnqueueMessage(const Message &aMessage, const QueryMetadata &aQueryMetadata);
     void     DequeueMessage(Message &aMessage);
-    otError  SendMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    Error    SendMessage(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
     void     SendCopy(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
 
     Message *FindRelatedQuery(const Header &aResponseHeader, QueryMetadata &aQueryMetadata);
-    void FinalizeSntpTransaction(Message &aQuery, const QueryMetadata &aQueryMetadata, uint64_t aTime, otError aResult);
+    void FinalizeSntpTransaction(Message &aQuery, const QueryMetadata &aQueryMetadata, uint64_t aTime, Error aResult);
 
     static void HandleRetransmissionTimer(Timer &aTimer);
     void        HandleRetransmissionTimer(void);
diff --git a/src/core/net/socket.cpp b/src/core/net/socket.cpp
new file mode 100644
index 0000000..5e44143
--- /dev/null
+++ b/src/core/net/socket.cpp
@@ -0,0 +1,45 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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
+ *   This file implements the IPv6 sockets related functionality.
+ */
+
+#include "socket.hpp"
+
+namespace ot {
+namespace Ip6 {
+
+SockAddr::InfoString SockAddr::ToString(void) const
+{
+    return InfoString("[%s]:%u", GetAddress().ToString().AsCString(), GetPort());
+}
+
+} // namespace Ip6
+} // namespace ot
diff --git a/src/core/net/socket.hpp b/src/core/net/socket.hpp
index 9c9a2ef..0b59ded 100644
--- a/src/core/net/socket.hpp
+++ b/src/core/net/socket.hpp
@@ -37,6 +37,7 @@
 #include "openthread-core-config.h"
 
 #include "common/clearable.hpp"
+#include "common/equatable.hpp"
 #include "net/ip6_address.hpp"
 
 namespace ot {
@@ -233,9 +234,20 @@
  * This class implements a socket address.
  *
  */
-class SockAddr : public otSockAddr, public Clearable<SockAddr>
+class SockAddr : public otSockAddr, public Clearable<SockAddr>, public Unequatable<SockAddr>
 {
 public:
+    enum : uint16_t
+    {
+        kInfoStringSize = 50, ///< Max chars for the info string (`ToString()`).
+    };
+
+    /**
+     * This type defines the fixed-length `String` object returned from `ToString()`.
+     *
+     */
+    typedef String<kInfoStringSize> InfoString;
+
     /**
      * This constructor initializes the socket address (all fields are set to zero).
      *
@@ -282,6 +294,47 @@
      *
      */
     const Address &GetAddress(void) const { return *static_cast<const Address *>(&mAddress); }
+
+    /**
+     * This method returns the socket address port number.
+     *
+     * @returns The port number
+     *
+     */
+    uint16_t GetPort(void) const { return mPort; }
+
+    /**
+     * This method sets the socket address port number.
+     *
+     * @param[in] aPort  The port number.
+     *
+     */
+    void SetPort(uint16_t aPort) { mPort = aPort; }
+
+    /**
+     * This method overloads operator `==` to evaluate whether or not two `SockAddr` instances are equal (same address
+     * and port number).
+     *
+     * @param[in]  aOther  The other `SockAddr` instance to compare with.
+     *
+     * @retval TRUE   If the two `SockAddr` instances are equal.
+     * @retval FALSE  If the two `SockAddr` instances not equal.
+     *
+     */
+    bool operator==(const SockAddr &aOther) const
+    {
+        return (GetPort() == aOther.GetPort()) && (GetAddress() == aOther.GetAddress());
+    }
+
+    /**
+     * This method converts the socket address to a string.
+     *
+     * The string is formatted as "[<ipv6 address>]:<port number>".
+     *
+     * @returns An `InfoString` containing the string representation of the `SockAddr`
+     *
+     */
+    InfoString ToString(void) const;
 };
 
 /**
diff --git a/src/core/net/srp_client.cpp b/src/core/net/srp_client.cpp
new file mode 100644
index 0000000..a32da3e
--- /dev/null
+++ b/src/core/net/srp_client.cpp
@@ -0,0 +1,1588 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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.
+ */
+
+#include "srp_client.hpp"
+
+#include "common/code_utils.hpp"
+#include "common/debug.hpp"
+#include "common/instance.hpp"
+#include "common/locator-getters.hpp"
+#include "common/logging.hpp"
+#include "common/random.hpp"
+#include "common/settings.hpp"
+#include "common/string.hpp"
+#include "thread/network_data_service.hpp"
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+
+/**
+ * @file
+ *   This file implements the SRP client.
+ */
+
+namespace ot {
+namespace Srp {
+
+//---------------------------------------------------------------------
+// Client::HostInfo
+
+void Client::HostInfo::Init(void)
+{
+    Clearable<HostInfo>::Clear();
+
+    // State is directly set on `mState` instead of using `SetState()`
+    // to avoid logging.
+    mState = OT_SRP_CLIENT_ITEM_STATE_REMOVED;
+}
+
+void Client::HostInfo::Clear(void)
+{
+    Clearable<HostInfo>::Clear();
+    SetState(kRemoved);
+}
+
+void Client::HostInfo::SetState(ItemState aState)
+{
+    if (aState != GetState())
+    {
+        otLogInfoSrp("[client] HostInfo %s -> %s", ItemStateToString(GetState()), ItemStateToString(aState));
+        mState = static_cast<otSrpClientItemState>(aState);
+    }
+}
+
+void Client::HostInfo::SetAddresses(const Ip6::Address *aAddresses, uint8_t aNumAddresses)
+{
+    mAddresses    = aAddresses;
+    mNumAddresses = aNumAddresses;
+
+    otLogInfoSrp("[client] HostInfo set %d addrs", GetNumAddresses());
+
+    for (uint8_t index = 0; index < GetNumAddresses(); index++)
+    {
+        otLogInfoSrp("[client] %s", GetAddress(index).ToString().AsCString());
+    }
+}
+
+//---------------------------------------------------------------------
+// Client::Service
+
+Error Client::Service::Init(void)
+{
+    Error error = kErrorNone;
+
+    VerifyOrExit((GetName() != nullptr) && (GetInstanceName() != nullptr), error = kErrorInvalidArgs);
+    VerifyOrExit((GetTxtEntries() != nullptr) || (GetNumTxtEntries() == 0), error = kErrorInvalidArgs);
+
+    // State is directly set on `mState` instead of using `SetState()`
+    // to avoid logging.
+    mState = OT_SRP_CLIENT_ITEM_STATE_REMOVED;
+
+exit:
+    return error;
+}
+
+void Client::Service::SetState(ItemState aState)
+{
+    VerifyOrExit(GetState() != aState);
+
+    otLogInfoSrp("[client] Service %s -> %s, \"%s\" \"%s\"", ItemStateToString(GetState()), ItemStateToString(aState),
+                 GetInstanceName(), GetName());
+
+    if (aState == kToAdd)
+    {
+        // Log more details only when entering `kToAdd` state.
+
+        otLogInfoSrp("[client] port:%d weight:%d prio:%d txts:%d", GetPort(), GetWeight(), GetPriority(),
+                     GetNumTxtEntries());
+    }
+
+    mState = static_cast<otSrpClientItemState>(aState);
+
+exit:
+    return;
+}
+
+//---------------------------------------------------------------------
+// Client
+
+const char Client::kDefaultDomainName[] = "default.service.arpa";
+
+Client::Client(Instance &aInstance)
+    : InstanceLocator(aInstance)
+    , mState(kStateStopped)
+    , mTxFailureRetryCount(0)
+    , mShouldRemoveKeyLease(false)
+#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+    , mAutoStartModeEnabled(kAutoStartDefaultMode)
+    , mAutoStartDidSelectServer(false)
+#endif
+    , mUpdateMessageId(0)
+    , mRetryWaitInterval(kMinRetryWaitInterval)
+    , mAcceptedLeaseInterval(0)
+    , mLeaseInterval(kDefaultLease)
+    , mKeyLeaseInterval(kDefaultKeyLease)
+    , mSocket(aInstance)
+    , mCallback(nullptr)
+    , mCallbackContext(nullptr)
+#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+    , mAutoStartCallback(nullptr)
+    , mAutoStartContext(nullptr)
+#endif
+    , mDomainName(kDefaultDomainName)
+    , mTimer(aInstance, Client::HandleTimer)
+{
+    mHostInfo.Init();
+
+    // The `Client` implementation uses different constant array of
+    // `ItemState` to define transitions between states in `Pause()`,
+    // `Stop()`, `SendUpdate`, and `ProcessResponse()`, or to convert
+    // an `ItemState` to string. Here, we assert that the enumeration
+    // values are correct.
+
+    static_assert(kToAdd == 0, "kToAdd value is not correct");
+    static_assert(kAdding == 1, "kAdding value is not correct");
+    static_assert(kToRefresh == 2, "kToRefresh value is not correct");
+    static_assert(kRefreshing == 3, "kRefreshing value is not correct");
+    static_assert(kToRemove == 4, "kToRemove value is not correct");
+    static_assert(kRemoving == 5, "kRemoving value is not correct");
+    static_assert(kRegistered == 6, "kRegistered value is not correct");
+    static_assert(kRemoved == 7, "kRemoved value is not correct");
+}
+
+Error Client::Start(const Ip6::SockAddr &aServerSockAddr, Requester aRequester)
+{
+    Error error;
+
+    VerifyOrExit(GetState() == kStateStopped,
+                 error = (aServerSockAddr == GetServerAddress()) ? kErrorNone : kErrorBusy);
+
+    SuccessOrExit(error = mSocket.Open(Client::HandleUdpReceive, this));
+    SuccessOrExit(error = mSocket.Connect(aServerSockAddr));
+
+    otLogInfoSrp("[client] %starting, server %s", (aRequester == kRequesterUser) ? "S" : "Auto-s",
+                 aServerSockAddr.ToString().AsCString());
+
+    Resume();
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+    mAutoStartDidSelectServer = (aRequester == kRequesterAuto);
+
+    VerifyOrExit((aRequester == kRequesterAuto) && (mAutoStartCallback != nullptr));
+    mAutoStartCallback(&aServerSockAddr, mAutoStartContext);
+#endif
+
+exit:
+    return error;
+}
+
+void Client::Stop(Requester aRequester)
+{
+    // Change the state of host info and services so that they are
+    // added/removed again once the client is started back. In the
+    // case of `kAdding`, we intentionally move to `kToRefresh`
+    // instead of `kToAdd` since the server may receive our add
+    // request and the item may be registered on the server. This
+    // ensures that if we are later asked to remove the item, we do
+    // notify server.
+
+    static const ItemState kNewStateOnStop[]{
+        /* (0) kToAdd      -> */ kToAdd,
+        /* (1) kAdding     -> */ kToRefresh,
+        /* (2) kToRefresh  -> */ kToRefresh,
+        /* (3) kRefreshing -> */ kToRefresh,
+        /* (4) kToRemove   -> */ kToRemove,
+        /* (5) kRemoving   -> */ kToRemove,
+        /* (6) kRegistered -> */ kToRefresh,
+        /* (7) kRemoved    -> */ kRemoved,
+    };
+
+    VerifyOrExit(GetState() != kStateStopped);
+
+    // State changes:
+    //   kAdding     -> kToRefresh
+    //   kRefreshing -> kToRefresh
+    //   kRemoving   -> kToRemove
+    //   kRegistered -> kToRefresh
+
+    ChangeHostAndServiceStates(kNewStateOnStop);
+
+    IgnoreError(mSocket.Close());
+    mShouldRemoveKeyLease = false;
+    mTxFailureRetryCount  = 0;
+    ResetRetryWaitInterval();
+    SetState(kStateStopped);
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+    VerifyOrExit((aRequester == kRequesterAuto) && (mAutoStartCallback != nullptr));
+    mAutoStartCallback(nullptr, mAutoStartContext);
+#endif
+
+exit:
+#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+    if (aRequester == kRequesterUser)
+    {
+        DisableAutoStartMode();
+    }
+#endif
+}
+
+void Client::SetCallback(Callback aCallback, void *aContext)
+{
+    mCallback        = aCallback;
+    mCallbackContext = aContext;
+}
+
+void Client::Resume(void)
+{
+    SetState(kStateUpdated);
+    UpdateState();
+}
+
+void Client::Pause(void)
+{
+    // Change the state of host info and services that are are being
+    // added or removed so that they are added/removed again once the
+    // client is resumed or started back.
+
+    static const ItemState kNewStateOnPause[]{
+        /* (0) kToAdd      -> */ kToAdd,
+        /* (1) kAdding     -> */ kToRefresh,
+        /* (2) kToRefresh  -> */ kToRefresh,
+        /* (3) kRefreshing -> */ kToRefresh,
+        /* (4) kToRemove   -> */ kToRemove,
+        /* (5) kRemoving   -> */ kToRemove,
+        /* (6) kRegistered -> */ kRegistered,
+        /* (7) kRemoved    -> */ kRemoved,
+    };
+
+    // State changes:
+    //   kAdding     -> kToRefresh
+    //   kRefreshing -> kToRefresh
+    //   kRemoving   -> kToRemove
+
+    ChangeHostAndServiceStates(kNewStateOnPause);
+
+    SetState(kStatePaused);
+}
+
+void Client::HandleNotifierEvents(Events aEvents)
+{
+    if (aEvents.Contains(kEventThreadRoleChanged))
+    {
+        HandleRoleChanged();
+    }
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+    if (aEvents.Contains(kEventThreadNetdataChanged))
+    {
+        ProcessAutoStart();
+    }
+#endif
+}
+
+void Client::HandleRoleChanged(void)
+{
+    if (Get<Mle::Mle>().IsAttached())
+    {
+        VerifyOrExit(GetState() == kStatePaused);
+        Resume();
+    }
+    else
+    {
+        VerifyOrExit(GetState() != kStateStopped);
+        Pause();
+    }
+
+exit:
+    return;
+}
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE
+Error Client::SetDomainName(const char *aName)
+{
+    Error error = kErrorNone;
+
+    VerifyOrExit((mHostInfo.GetState() == kToAdd) || (mHostInfo.GetState() == kRemoved), error = kErrorInvalidState);
+
+    mDomainName = (aName != nullptr) ? aName : kDefaultDomainName;
+    otLogInfoSrp("[client] Domain name \"%s\"", mDomainName);
+
+exit:
+    return error;
+}
+#endif
+
+Error Client::SetHostName(const char *aName)
+{
+    Error error = kErrorNone;
+
+    VerifyOrExit(aName != nullptr, error = kErrorInvalidArgs);
+
+    VerifyOrExit((mHostInfo.GetState() == kToAdd) || (mHostInfo.GetState() == kRemoved), error = kErrorInvalidState);
+
+    otLogInfoSrp("[client] Host name \"%s\"", aName);
+    mHostInfo.SetName(aName);
+    mHostInfo.SetState(kToAdd);
+    UpdateState();
+
+exit:
+    return error;
+}
+
+Error Client::SetHostAddresses(const Ip6::Address *aAddresses, uint8_t aNumAddresses)
+{
+    Error error = kErrorNone;
+
+    VerifyOrExit((aAddresses != nullptr) && (aNumAddresses > 0), error = kErrorInvalidArgs);
+
+    VerifyOrExit((mHostInfo.GetState() != kToRemove) && (mHostInfo.GetState() != kRemoving),
+                 error = kErrorInvalidState);
+
+    if (mHostInfo.GetState() == kRemoved)
+    {
+        mHostInfo.SetState(kToAdd);
+    }
+    else if (mHostInfo.GetState() != kToAdd)
+    {
+        mHostInfo.SetState(kToRefresh);
+    }
+
+    mHostInfo.SetAddresses(aAddresses, aNumAddresses);
+    UpdateState();
+
+exit:
+    return error;
+}
+
+Error Client::AddService(Service &aService)
+{
+    Error error;
+
+    VerifyOrExit(!mServices.Contains(aService), error = kErrorAlready);
+
+    SuccessOrExit(error = aService.Init());
+    mServices.Push(aService);
+
+    aService.SetState(kToAdd);
+    UpdateState();
+
+exit:
+    return error;
+}
+
+Error Client::RemoveService(Service &aService)
+{
+    Error               error = kErrorNone;
+    LinkedList<Service> removedServices;
+
+    VerifyOrExit(mServices.Contains(aService), error = kErrorNotFound);
+
+    UpdateServiceStateToRemove(aService);
+
+    // Check if the service was removed immediately, if so
+    // invoke the callback to report the removed service.
+    GetRemovedServices(removedServices);
+
+    if (!removedServices.IsEmpty())
+    {
+        InvokeCallback(kErrorNone, mHostInfo, removedServices.GetHead());
+    }
+
+    UpdateState();
+
+exit:
+    return error;
+}
+
+void Client::UpdateServiceStateToRemove(Service &aService)
+{
+    if (aService.GetState() == kToAdd)
+    {
+        // If the service has not been added yet, we can remove it immediately.
+        aService.SetState(kRemoved);
+    }
+    else if (aService.GetState() != kRemoving)
+    {
+        aService.SetState(kToRemove);
+    }
+}
+
+Error Client::RemoveHostAndServices(bool aShouldRemoveKeyLease)
+{
+    Error error = kErrorNone;
+
+    otLogInfoSrp("[client] Remove host & services");
+
+    VerifyOrExit(mHostInfo.GetState() != kRemoved, error = kErrorAlready);
+
+    if ((mHostInfo.GetState() == kToRemove) || (mHostInfo.GetState() == kRemoving))
+    {
+        // Host info remove is already ongoing, if "key lease" remove mode is
+        // the same, there is no need to send a new update message.
+        VerifyOrExit(mShouldRemoveKeyLease != aShouldRemoveKeyLease);
+    }
+
+    mShouldRemoveKeyLease = aShouldRemoveKeyLease;
+
+    for (Service *service = mServices.GetHead(); service != nullptr; service = service->GetNext())
+    {
+        UpdateServiceStateToRemove(*service);
+    }
+
+    if (mHostInfo.GetState() == kToAdd)
+    {
+        // Host info is not added yet (not yet registered with
+        // server), so we can remove it and all services immediately.
+        mHostInfo.SetState(kRemoved);
+        HandleUpdateDone();
+        ExitNow();
+    }
+
+    mHostInfo.SetState(kToRemove);
+    UpdateState();
+
+exit:
+    return error;
+}
+
+void Client::ClearHostAndServices(void)
+{
+    otLogInfoSrp("[client] Clear host & services");
+
+    switch (GetState())
+    {
+    case kStateStopped:
+    case kStatePaused:
+        break;
+
+    case kStateToUpdate:
+    case kStateUpdating:
+    case kStateUpdated:
+    case kStateToRetry:
+        SetState(kStateUpdated);
+        break;
+    }
+
+    mTxFailureRetryCount = 0;
+    ResetRetryWaitInterval();
+
+    mServices.Clear();
+    mHostInfo.Clear();
+}
+
+void Client::SetState(State aState)
+{
+    VerifyOrExit(aState != mState);
+
+    otLogInfoSrp("[client] State %s -> %s", StateToString(mState), StateToString(aState));
+    mState = aState;
+
+    switch (mState)
+    {
+    case kStateStopped:
+    case kStatePaused:
+    case kStateUpdated:
+        mTimer.Stop();
+        break;
+
+    case kStateToUpdate:
+        mTimer.Start(kUpdateTxDelay);
+        break;
+
+    case kStateUpdating:
+        mTimer.Start(GetRetryWaitInterval());
+        break;
+
+    case kStateToRetry:
+        break;
+    }
+exit:
+    return;
+}
+
+void Client::ChangeHostAndServiceStates(const ItemState *aNewStates)
+{
+    mHostInfo.SetState(aNewStates[mHostInfo.GetState()]);
+
+    for (Service *service = mServices.GetHead(); service != nullptr; service = service->GetNext())
+    {
+        service->SetState(aNewStates[service->GetState()]);
+    }
+}
+
+void Client::InvokeCallback(Error aError) const
+{
+    InvokeCallback(aError, mHostInfo, nullptr);
+}
+
+void Client::InvokeCallback(Error aError, const HostInfo &aHostInfo, const Service *aRemovedServices) const
+{
+    VerifyOrExit(GetState() != kStateStopped);
+    VerifyOrExit(mCallback != nullptr);
+    mCallback(aError, &aHostInfo, mServices.GetHead(), aRemovedServices, mCallbackContext);
+
+exit:
+    return;
+}
+
+void Client::SendUpdate(void)
+{
+    static const ItemState kNewStateOnMessageTx[]{
+        /* (0) kToAdd      -> */ kAdding,
+        /* (1) kAdding     -> */ kAdding,
+        /* (2) kToRefresh  -> */ kRefreshing,
+        /* (3) kRefreshing -> */ kRefreshing,
+        /* (4) kToRemove   -> */ kRemoving,
+        /* (5) kRemoving   -> */ kRemoving,
+        /* (6) kRegistered -> */ kRegistered,
+        /* (7) kRemoved    -> */ kRemoved,
+    };
+
+    Error    error   = kErrorNone;
+    Message *message = mSocket.NewMessage(0);
+
+    VerifyOrExit(message != nullptr, error = kErrorNoBufs);
+    SuccessOrExit(error = PrepareUpdateMessage(*message));
+    SuccessOrExit(error = mSocket.SendTo(*message, Ip6::MessageInfo()));
+
+    otLogInfoSrp("[client] Send update");
+
+    // State changes:
+    //   kToAdd     -> kAdding
+    //   kToRefresh -> kRefreshing
+    //   kToRemove  -> kRemoving
+
+    ChangeHostAndServiceStates(kNewStateOnMessageTx);
+
+    // Remember the update message tx time to use later to determine the
+    // lease renew time.
+    mLeaseRenewTime      = TimerMilli::GetNow();
+    mTxFailureRetryCount = 0;
+
+    SetState(kStateUpdating);
+
+    if (!Get<Mle::Mle>().IsRxOnWhenIdle())
+    {
+        // If device is sleepy send fast polls while waiting for
+        // the response from server.
+        Get<DataPollSender>().SendFastPolls(kFastPollsAfterUpdateTx);
+    }
+
+exit:
+    if (error != kErrorNone)
+    {
+        // If there is an error in preparation or transmission of the
+        // update message (e.g., no buffer to allocate message), up to
+        // `kMaxTxFailureRetries` times, we wait for a short interval
+        // `kTxFailureRetryInterval` and try again. After this, we
+        // continue to retry using the `mRetryWaitInterval` (which keeps
+        // growing on each failure).
+
+        otLogInfoSrp("[client] Failed to send update: %s", ErrorToString(error));
+
+        FreeMessage(message);
+
+        SetState(kStateToRetry);
+
+        if (mTxFailureRetryCount < kMaxTxFailureRetries)
+        {
+            uint32_t interval;
+
+            mTxFailureRetryCount++;
+            interval = Random::NonCrypto::AddJitter(kTxFailureRetryInterval, kTxFailureRetryJitter);
+            mTimer.Start(interval);
+
+            otLogInfoSrp("[client] Quick retry %d in %u msec", mTxFailureRetryCount, interval);
+
+            // Do not report message preparation errors to user
+            // until `kMaxTxFailureRetries` are exhausted.
+        }
+        else
+        {
+            LogRetryWaitInterval();
+            mTimer.Start(Random::NonCrypto::AddJitter(GetRetryWaitInterval(), kRetryIntervalJitter));
+            GrowRetryWaitInterval();
+            InvokeCallback(error);
+        }
+    }
+}
+
+Error Client::PrepareUpdateMessage(Message &aMessage)
+{
+    enum : uint16_t
+    {
+        kHeaderOffset = 0,
+    };
+
+    Error             error = kErrorNone;
+    Dns::UpdateHeader header;
+    Info              info;
+
+    info.Clear();
+
+    SuccessOrExit(error = ReadOrGenerateKey(info.mKeyPair));
+
+    // Generate random Message ID and ensure it is different from last one
+    do
+    {
+        SuccessOrExit(error = header.SetRandomMessageId());
+    } while (header.GetMessageId() == mUpdateMessageId);
+
+    mUpdateMessageId = header.GetMessageId();
+
+    // SRP Update (DNS Update) message must have exactly one record in
+    // Zone section, no records in Prerequisite Section, can have
+    // multiple records in Update Section (tracked as they are added),
+    // and two records in Additional Data Section (OPT and SIG records).
+    // The SIG record itself should not be included in calculation of
+    // SIG(0) signature, so the addition record count is set to one
+    // here. After signature calculation and appending of SIG record,
+    // the additional record count is updated to two and the header is
+    // rewritten in the message.
+
+    header.SetZoneRecordCount(1);
+    header.SetAdditionalRecordCount(1);
+    SuccessOrExit(error = aMessage.Append(header));
+
+    // Prepare Zone section
+
+    info.mDomainNameOffset = aMessage.GetLength();
+    SuccessOrExit(error = Dns::Name::AppendName(mDomainName, aMessage));
+    SuccessOrExit(error = aMessage.Append(Dns::Zone()));
+
+    // Prepare Update section
+
+    if (mHostInfo.GetState() != kToRemove)
+    {
+        for (Service *service = mServices.GetHead(); service != nullptr; service = service->GetNext())
+        {
+            SuccessOrExit(error = AppendServiceInstructions(*service, aMessage, info));
+        }
+    }
+
+    SuccessOrExit(error = AppendHostDescriptionInstruction(aMessage, info));
+
+    header.SetUpdateRecordCount(info.mRecordCount);
+    aMessage.Write(kHeaderOffset, header);
+
+    // Prepare Additional Data section
+
+    SuccessOrExit(error = AppendUpdateLeaseOptRecord(aMessage));
+    SuccessOrExit(error = AppendSignature(aMessage, info));
+
+    header.SetAdditionalRecordCount(2); // Lease OPT and SIG RRs
+    aMessage.Write(kHeaderOffset, header);
+
+exit:
+    return error;
+}
+
+Error Client::ReadOrGenerateKey(Crypto::Ecdsa::P256::KeyPair &aKeyPair)
+{
+    Error error;
+
+    error = Get<Settings>().ReadSrpKey(aKeyPair);
+
+    if (error == kErrorNone)
+    {
+        Crypto::Ecdsa::P256::PublicKey publicKey;
+
+        if (aKeyPair.GetPublicKey(publicKey) == kErrorNone)
+        {
+            ExitNow();
+        }
+    }
+
+    SuccessOrExit(error = aKeyPair.Generate());
+    IgnoreError(Get<Settings>().SaveSrpKey(aKeyPair));
+
+exit:
+    return error;
+}
+
+Error Client::AppendServiceInstructions(Service &aService, Message &aMessage, Info &aInfo)
+{
+    Error               error = kErrorNone;
+    Dns::ResourceRecord rr;
+    Dns::SrvRecord      srv;
+    bool                removing;
+    uint16_t            serviceNameOffset;
+    uint16_t            instanceNameOffset;
+    uint16_t            offset;
+
+    if (aService.GetState() == kRegistered)
+    {
+        // If the lease needs to be renewed or if we are close to the
+        // renewal time of a registered service, we refresh the service
+        // early and include it in this update. This helps put more
+        // services on the same lease refresh schedule.
+
+        VerifyOrExit(ShouldRenewEarly(aService));
+        aService.SetState(kToRefresh);
+    }
+
+    removing = ((aService.GetState() == kToRemove) || (aService.GetState() == kRemoving));
+
+    //----------------------------------
+    // Service Discovery Instruction
+
+    // PTR record
+
+    // "service name labels" + (pointer to) domain name.
+    serviceNameOffset = aMessage.GetLength();
+    SuccessOrExit(error = Dns::Name::AppendMultipleLabels(aService.GetName(), aMessage));
+    SuccessOrExit(error = Dns::Name::AppendPointerLabel(aInfo.mDomainNameOffset, aMessage));
+
+    // On remove, we use "Delete an RR from an RRSet" where class is set
+    // to NONE and TTL to zero (RFC 2136 - section 2.5.4).
+
+    rr.Init(Dns::ResourceRecord::kTypePtr, removing ? Dns::PtrRecord::kClassNone : Dns::PtrRecord::kClassInternet);
+    rr.SetTtl(removing ? 0 : mLeaseInterval);
+    offset = aMessage.GetLength();
+    SuccessOrExit(error = aMessage.Append(rr));
+
+    // "Instance name" + (pointer to) service name.
+    instanceNameOffset = aMessage.GetLength();
+    SuccessOrExit(error = Dns::Name::AppendLabel(aService.GetInstanceName(), aMessage));
+    SuccessOrExit(error = Dns::Name::AppendPointerLabel(serviceNameOffset, aMessage));
+
+    UpdateRecordLengthInMessage(rr, offset, aMessage);
+    aInfo.mRecordCount++;
+
+    //----------------------------------
+    // Service Description Instruction
+
+    // "Delete all RRsets from a name" for Instance Name.
+
+    SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, aMessage));
+    SuccessOrExit(error = AppendDeleteAllRrsets(aMessage));
+    aInfo.mRecordCount++;
+
+    VerifyOrExit(!removing);
+
+    // SRV RR
+
+    SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, aMessage));
+    srv.Init();
+    srv.SetTtl(mLeaseInterval);
+    srv.SetPriority(aService.GetPriority());
+    srv.SetWeight(aService.GetWeight());
+    srv.SetPort(aService.GetPort());
+    offset = aMessage.GetLength();
+    SuccessOrExit(error = aMessage.Append(srv));
+    SuccessOrExit(error = AppendHostName(aMessage, aInfo));
+    UpdateRecordLengthInMessage(srv, offset, aMessage);
+    aInfo.mRecordCount++;
+
+    // TXT RR
+
+    SuccessOrExit(error = Dns::Name::AppendPointerLabel(instanceNameOffset, aMessage));
+    rr.Init(Dns::ResourceRecord::kTypeTxt);
+    offset = aMessage.GetLength();
+    SuccessOrExit(error = aMessage.Append(rr));
+    SuccessOrExit(error =
+                      Dns::TxtEntry::AppendEntries(aService.GetTxtEntries(), aService.GetNumTxtEntries(), aMessage));
+    UpdateRecordLengthInMessage(rr, offset, aMessage);
+    aInfo.mRecordCount++;
+
+exit:
+    return error;
+}
+
+Error Client::AppendHostDescriptionInstruction(Message &aMessage, Info &aInfo) const
+{
+    Error                          error = kErrorNone;
+    Dns::ResourceRecord            rr;
+    Dns::KeyRecord                 key;
+    Crypto::Ecdsa::P256::PublicKey publicKey;
+
+    //----------------------------------
+    // Host Description Instruction
+
+    // "Delete all RRsets from a name" for Host Name.
+
+    SuccessOrExit(error = AppendHostName(aMessage, aInfo));
+    SuccessOrExit(error = AppendDeleteAllRrsets(aMessage));
+    aInfo.mRecordCount++;
+
+    // AAAA RRs
+
+    rr.Init(Dns::ResourceRecord::kTypeAaaa);
+    rr.SetTtl(mLeaseInterval);
+    rr.SetLength(sizeof(Ip6::Address));
+
+    for (uint8_t index = 0; index < mHostInfo.GetNumAddresses(); index++)
+    {
+        SuccessOrExit(error = AppendHostName(aMessage, aInfo));
+        SuccessOrExit(error = aMessage.Append(rr));
+        SuccessOrExit(error = aMessage.Append(mHostInfo.GetAddress(index)));
+        aInfo.mRecordCount++;
+    }
+
+    // KEY RR
+
+    SuccessOrExit(error = AppendHostName(aMessage, aInfo));
+    key.Init();
+    key.SetTtl(mLeaseInterval);
+    key.SetFlags(Dns::KeyRecord::kAuthConfidPermitted, Dns::KeyRecord::kOwnerNonZone,
+                 Dns::KeyRecord::kSignatoryFlagGeneral);
+    key.SetProtocol(Dns::KeyRecord::kProtocolDnsSec);
+    key.SetAlgorithm(Dns::KeyRecord::kAlgorithmEcdsaP256Sha256);
+    key.SetLength(sizeof(Dns::KeyRecord) - sizeof(Dns::ResourceRecord) + sizeof(Crypto::Ecdsa::P256::PublicKey));
+    SuccessOrExit(error = aMessage.Append(key));
+    SuccessOrExit(error = aInfo.mKeyPair.GetPublicKey(publicKey));
+    SuccessOrExit(error = aMessage.Append(publicKey));
+    aInfo.mRecordCount++;
+
+exit:
+    return error;
+}
+
+Error Client::AppendDeleteAllRrsets(Message &aMessage) const
+{
+    // "Delete all RRsets from a name" (RFC 2136 - 2.5.3)
+    // Name should be already appended in the message.
+
+    Dns::ResourceRecord rr;
+
+    rr.Init(Dns::ResourceRecord::kTypeAny, Dns::ResourceRecord::kClassAny);
+    rr.SetTtl(0);
+    rr.SetLength(0);
+
+    return aMessage.Append(rr);
+}
+
+Error Client::AppendHostName(Message &aMessage, Info &aInfo, bool aDoNotCompress) const
+{
+    Error error;
+
+    if (aDoNotCompress)
+    {
+        // Uncompressed (canonical form) of host name is used for SIG(0)
+        // calculation.
+        SuccessOrExit(error = Dns::Name::AppendMultipleLabels(mHostInfo.GetName(), aMessage));
+        error = Dns::Name::AppendName(mDomainName, aMessage);
+        ExitNow();
+    }
+
+    // If host name was previously added in the message, add it
+    // compressed as pointer to the previous one. Otherwise,
+    // append it and remember the offset.
+
+    if (aInfo.mHostNameOffset != Info::kUnknownOffset)
+    {
+        ExitNow(error = Dns::Name::AppendPointerLabel(aInfo.mHostNameOffset, aMessage));
+    }
+
+    aInfo.mHostNameOffset = aMessage.GetLength();
+    SuccessOrExit(error = Dns::Name::AppendMultipleLabels(mHostInfo.GetName(), aMessage));
+    error = Dns::Name::AppendPointerLabel(aInfo.mDomainNameOffset, aMessage);
+
+exit:
+    return error;
+}
+
+Error Client::AppendUpdateLeaseOptRecord(Message &aMessage) const
+{
+    Error            error;
+    Dns::OptRecord   optRecord;
+    Dns::LeaseOption leaseOption;
+
+    // Append empty (root domain) as OPT RR name.
+    SuccessOrExit(error = Dns::Name::AppendTerminator(aMessage));
+
+    // `Init()` sets the type and clears (set to zero) the extended
+    // Response Code, version and all flags.
+    optRecord.Init();
+    optRecord.SetUdpPayloadSize(kUdpPayloadSize);
+    optRecord.SetDnsSecurityFlag();
+    optRecord.SetLength(sizeof(Dns::LeaseOption));
+
+    SuccessOrExit(error = aMessage.Append(optRecord));
+
+    leaseOption.Init();
+
+    if (mHostInfo.GetState() == kToRemove)
+    {
+        leaseOption.SetLeaseInterval(0);
+        leaseOption.SetKeyLeaseInterval(mShouldRemoveKeyLease ? 0 : mKeyLeaseInterval);
+    }
+    else
+    {
+        leaseOption.SetLeaseInterval(mLeaseInterval);
+        leaseOption.SetKeyLeaseInterval(mKeyLeaseInterval);
+    }
+
+    error = aMessage.Append(leaseOption);
+
+exit:
+    return error;
+}
+
+Error Client::AppendSignature(Message &aMessage, Info &aInfo)
+{
+    Error                          error;
+    Dns::SigRecord                 sig;
+    Crypto::Sha256                 sha256;
+    Crypto::Sha256::Hash           hash;
+    Crypto::Ecdsa::P256::Signature signature;
+    uint16_t                       offset;
+    uint16_t                       len;
+
+    // Prepare SIG RR: TTL, type covered, labels count should be set
+    // to zero. Since we have no clock, inception and expiration time
+    // are also set to zero. The RDATA length will be set later (not
+    // yet known due to variably (and possible compression) of signer's
+    // name.
+
+    sig.Clear();
+    sig.Init(Dns::ResourceRecord::kClassAny);
+    sig.SetAlgorithm(Dns::KeyRecord::kAlgorithmEcdsaP256Sha256);
+
+    // Append the SIG RR with full uncompressed form of the host name
+    // as the signer's name. This is used for SIG(0) calculation only.
+    // It will be overwritten with host name compressed.
+
+    offset = aMessage.GetLength();
+    SuccessOrExit(error = aMessage.Append(sig));
+    SuccessOrExit(error = AppendHostName(aMessage, aInfo, /* aDoNotCompress */ true));
+
+    // Calculate signature (RFC 2931): Calculated over "data" which is
+    // concatenation of (1) the SIG RR RDATA wire format (including
+    // the canonical form of the signer's name), entirely omitting the
+    // signature subfield, (2) DNS query message, including DNS header
+    // but not UDP/IP header before the header RR counts have been
+    // adjusted for the inclusion of SIG(0).
+
+    sha256.Start();
+
+    // (1) SIG RR RDATA wire format
+    len = aMessage.GetLength() - offset - sizeof(Dns::ResourceRecord);
+    sha256.Update(aMessage, offset + sizeof(Dns::ResourceRecord), len);
+
+    // (2) Message from DNS header before SIG
+    sha256.Update(aMessage, 0, offset);
+
+    sha256.Finish(hash);
+    SuccessOrExit(error = aInfo.mKeyPair.Sign(hash, signature));
+
+    // Move back in message and append SIG RR now with compressed host
+    // name (as signer's name) along with the calculated signature.
+
+    IgnoreError(aMessage.SetLength(offset));
+
+    // SIG(0) uses owner name of root (single zero byte).
+    SuccessOrExit(error = Dns::Name::AppendTerminator(aMessage));
+
+    offset = aMessage.GetLength();
+    SuccessOrExit(error = aMessage.Append(sig));
+    SuccessOrExit(error = AppendHostName(aMessage, aInfo));
+    SuccessOrExit(error = aMessage.Append(signature));
+    UpdateRecordLengthInMessage(sig, offset, aMessage);
+
+exit:
+    return error;
+}
+
+void Client::UpdateRecordLengthInMessage(Dns::ResourceRecord &aRecord, uint16_t aOffset, Message &aMessage) const
+{
+    // This method is used to calculate an RR DATA length and update
+    // (rewrite) it in a message. This should be called immediately
+    // after all the fields in the record are written in the message.
+    // `aOffset` gives the offset in the message to the start of the
+    // record.
+
+    aRecord.SetLength(aMessage.GetLength() - aOffset - sizeof(Dns::ResourceRecord));
+    aMessage.Write(aOffset, aRecord);
+}
+
+void Client::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
+{
+    OT_UNUSED_VARIABLE(aMessageInfo);
+
+    static_cast<Client *>(aContext)->ProcessResponse(*static_cast<Message *>(aMessage));
+}
+
+void Client::ProcessResponse(Message &aMessage)
+{
+    static const ItemState kNewStateOnUpdateDone[]{
+        /* (0) kToAdd      -> */ kToAdd,
+        /* (1) kAdding     -> */ kRegistered,
+        /* (2) kToRefresh  -> */ kToRefresh,
+        /* (3) kRefreshing -> */ kRegistered,
+        /* (4) kToRemove   -> */ kToRemove,
+        /* (5) kRemoving   -> */ kRemoved,
+        /* (6) kRegistered -> */ kRegistered,
+        /* (7) kRemoved    -> */ kRemoved,
+    };
+
+    Error               error = kErrorNone;
+    Dns::UpdateHeader   header;
+    uint16_t            offset = aMessage.GetOffset();
+    uint16_t            recordCount;
+    LinkedList<Service> removedServices;
+
+    VerifyOrExit(GetState() == kStateUpdating);
+
+    SuccessOrExit(error = aMessage.Read(offset, header));
+
+    VerifyOrExit(header.GetType() == Dns::Header::kTypeResponse, error = kErrorParse);
+    VerifyOrExit(header.GetQueryType() == Dns::Header::kQueryTypeUpdate, error = kErrorParse);
+    VerifyOrExit(header.GetMessageId() == mUpdateMessageId, error = kErrorDrop);
+
+    if (!Get<Mle::Mle>().IsRxOnWhenIdle())
+    {
+        Get<DataPollSender>().StopFastPolls();
+    }
+
+    // Response is for the earlier request message.
+
+    otLogInfoSrp("[client] Received response");
+
+    error = Dns::Header::ResponseCodeToError(header.GetResponseCode());
+
+    if (error != kErrorNone)
+    {
+        otLogInfoSrp("[client] Server rejected %s code:%d", ErrorToString(error), header.GetResponseCode());
+
+        if (mHostInfo.GetState() == kAdding)
+        {
+            // Since server rejected the update message, we go back to
+            // `kToAdd` state to allow user to give a new name using
+            // `SetHostName()`.
+            mHostInfo.SetState(kToAdd);
+        }
+
+        // Wait for the timer to expire to retry. Note that timer is
+        // already scheduled for the current wait interval when state
+        // was changed to `kStateUpdating`.
+
+        LogRetryWaitInterval();
+        GrowRetryWaitInterval();
+        SetState(kStateToRetry);
+        InvokeCallback(error);
+        ExitNow(error = kErrorNone);
+    }
+
+    offset += sizeof(header);
+
+    // Skip over all sections till Additional Data section
+    // SPEC ENHANCEMENT: Sever can echo the request back or not
+    // include any of RRs. Would be good to explicitly require SRP server
+    // to not echo back RRs.
+
+    if (header.GetZoneRecordCount() != 0)
+    {
+        VerifyOrExit(header.GetZoneRecordCount() == 1, error = kErrorParse);
+        SuccessOrExit(error = Dns::Name::ParseName(aMessage, offset));
+        VerifyOrExit(offset + sizeof(Dns::Zone) <= aMessage.GetLength(), error = kErrorParse);
+        offset += sizeof(Dns::Zone);
+    }
+
+    // Check for Update Lease OPT RR. This determines the lease
+    // interval accepted by server. If not present, then use the
+    // transmitted lease interval from the update request message.
+
+    mAcceptedLeaseInterval = mLeaseInterval;
+    recordCount =
+        header.GetPrerequisiteRecordCount() + header.GetUpdateRecordCount() + header.GetAdditionalRecordCount();
+
+    while (recordCount > 0)
+    {
+        uint16_t            startOffset = offset;
+        Dns::ResourceRecord rr;
+
+        SuccessOrExit(error = ReadResourceRecord(aMessage, offset, rr));
+        recordCount--;
+
+        if (rr.GetType() == Dns::ResourceRecord::kTypeOpt)
+        {
+            SuccessOrExit(error = ProcessOptRecord(aMessage, startOffset, static_cast<Dns::OptRecord &>(rr)));
+        }
+    }
+
+    // Calculate the lease renew time based on update message tx time
+    // and the lease time. `kLeaseRenewGuardInterval` is used to
+    // ensure that we renew the lease before server expires it. In the
+    // unlikely (but maybe useful for testing) case where the accepted
+    // lease interval is too short (shorter than the guard time) we
+    // just use half of the accepted lease interval.
+
+    if (mAcceptedLeaseInterval > kLeaseRenewGuardInterval)
+    {
+        mLeaseRenewTime += Time::SecToMsec(mAcceptedLeaseInterval - kLeaseRenewGuardInterval);
+    }
+    else
+    {
+        mLeaseRenewTime += Time::SecToMsec(mAcceptedLeaseInterval) / 2;
+    }
+
+    for (Service *service = mServices.GetHead(); service != nullptr; service = service->GetNext())
+    {
+        if ((service->GetState() == kAdding) || (service->GetState() == kRefreshing))
+        {
+            service->SetLeaseRenewTime(mLeaseRenewTime);
+        }
+    }
+
+    // State changes:
+    //   kAdding     -> kRegistered
+    //   kRefreshing -> kRegistered
+    //   kRemoving   -> kRemoved
+
+    ChangeHostAndServiceStates(kNewStateOnUpdateDone);
+
+    HandleUpdateDone();
+    UpdateState();
+
+exit:
+    if (error != kErrorNone)
+    {
+        otLogInfoSrp("[clinet] Failed to process response %s", ErrorToString(error));
+    }
+}
+
+void Client::HandleUpdateDone(void)
+{
+    HostInfo            hostInfoCopy = mHostInfo;
+    LinkedList<Service> removedServices;
+
+    if (mHostInfo.GetState() == kRemoved)
+    {
+        mHostInfo.Clear();
+    }
+
+    ResetRetryWaitInterval();
+    SetState(kStateUpdated);
+
+    GetRemovedServices(removedServices);
+    InvokeCallback(kErrorNone, hostInfoCopy, removedServices.GetHead());
+}
+
+void Client::GetRemovedServices(LinkedList<Service> &aRemovedServices)
+{
+    Service *service;
+    Service *prev;
+    Service *next;
+
+    for (prev = nullptr, service = mServices.GetHead(); service != nullptr; service = next)
+    {
+        next = service->GetNext();
+
+        if (service->GetState() == kRemoved)
+        {
+            mServices.PopAfter(prev);
+            aRemovedServices.Push(*service);
+
+            // When the service is removed from the list
+            // we keep the `prev` pointer same as before.
+        }
+        else
+        {
+            prev = service;
+        }
+    }
+}
+
+Error Client::ReadResourceRecord(const Message &aMessage, uint16_t &aOffset, Dns::ResourceRecord &aRecord)
+{
+    // Reads and skips over a Resource Record (RR) from message at
+    // given offset. On success, `aOffset` is updated to point to end
+    // of RR.
+
+    Error error;
+
+    SuccessOrExit(error = Dns::Name::ParseName(aMessage, aOffset));
+    SuccessOrExit(error = aMessage.Read(aOffset, aRecord));
+    VerifyOrExit(aOffset + aRecord.GetSize() <= aMessage.GetLength(), error = kErrorParse);
+    aOffset += static_cast<uint16_t>(aRecord.GetSize());
+
+exit:
+    return error;
+}
+
+Error Client::ProcessOptRecord(const Message &aMessage, uint16_t aOffset, const Dns::OptRecord &aOptRecord)
+{
+    // Read and process all options (in an OPT RR) from a message.
+    // The `aOffset` points to beginning of record in `aMessage`.
+
+    Error    error = kErrorNone;
+    uint16_t len;
+
+    IgnoreError(Dns::Name::ParseName(aMessage, aOffset));
+    aOffset += sizeof(Dns::OptRecord);
+
+    len = aOptRecord.GetLength();
+
+    while (len > 0)
+    {
+        Dns::LeaseOption leaseOption;
+        Dns::Option &    option = leaseOption;
+        uint16_t         size;
+
+        SuccessOrExit(error = aMessage.Read(aOffset, option));
+
+        VerifyOrExit(aOffset + option.GetSize() <= aMessage.GetLength(), error = kErrorParse);
+
+        if ((option.GetOptionCode() == Dns::Option::kUpdateLease) &&
+            (option.GetOptionLength() >= Dns::LeaseOption::kOptionLength))
+        {
+            SuccessOrExit(error = aMessage.Read(aOffset, leaseOption));
+
+            mAcceptedLeaseInterval = leaseOption.GetLeaseInterval();
+
+            if (mAcceptedLeaseInterval > kMaxLease)
+            {
+                mAcceptedLeaseInterval = kMaxLease;
+            }
+        }
+
+        size = static_cast<uint16_t>(option.GetSize());
+        aOffset += size;
+        len -= size;
+    }
+
+exit:
+    return error;
+}
+
+void Client::UpdateState(void)
+{
+    TimeMilli now               = TimerMilli::GetNow();
+    TimeMilli earliestRenewTime = now.GetDistantFuture();
+    bool      shouldUpdate      = false;
+
+    VerifyOrExit((GetState() != kStateStopped) && (GetState() != kStatePaused));
+    VerifyOrExit((mHostInfo.GetName() != nullptr) && (mHostInfo.GetNumAddresses() > 0));
+
+    // Go through the host info and all the services to check if there
+    // are any new changes (i.e., anything new to add or remove). This
+    // is used to determine whether to send an SRP update message or
+    // not. Also keep track of the earliest renew time among the
+    // previously registered services. This is used to schedule the
+    // timer for next refresh.
+
+    switch (mHostInfo.GetState())
+    {
+    case kAdding:
+    case kRefreshing:
+    case kRemoving:
+        break;
+
+    case kRegistered:
+        if (now < mLeaseRenewTime)
+        {
+            break;
+        }
+
+        mHostInfo.SetState(kToRefresh);
+
+        // Fall through
+
+    case kToAdd:
+    case kToRefresh:
+        // Make sure we have at least one service otherwise no need to
+        // send SRP update message with host info only. The exception
+        // is when removing host info where we allow for empty
+        // service list.
+        VerifyOrExit(!mServices.IsEmpty());
+
+        // Fall through
+
+    case kToRemove:
+        shouldUpdate = true;
+        break;
+
+    case kRemoved:
+        ExitNow();
+    }
+
+    // If host info is being removed, we skip over checking service list
+    // for new adds (or removes). This handles the situation where while
+    // remove is ongoing and before we get a response from the server,
+    // user adds a new service to be registered. We wait for remove to
+    // finish (receive response from server) before starting with a new
+    // service adds.
+
+    if (mHostInfo.GetState() != kRemoving)
+    {
+        for (Service *service = mServices.GetHead(); service != nullptr; service = service->GetNext())
+        {
+            switch (service->GetState())
+            {
+            case kToAdd:
+            case kToRefresh:
+            case kToRemove:
+                shouldUpdate = true;
+                break;
+
+            case kRegistered:
+                if (service->GetLeaseRenewTime() <= now)
+                {
+                    service->SetState(kToRefresh);
+                    shouldUpdate = true;
+                }
+                else if (service->GetLeaseRenewTime() < earliestRenewTime)
+                {
+                    earliestRenewTime = service->GetLeaseRenewTime();
+                }
+
+                break;
+
+            case kAdding:
+            case kRefreshing:
+            case kRemoving:
+            case kRemoved:
+                break;
+            }
+        }
+    }
+
+    if (shouldUpdate)
+    {
+        SetState(kStateToUpdate);
+        ExitNow();
+    }
+
+    if ((GetState() == kStateUpdated) && (earliestRenewTime != now.GetDistantFuture()))
+    {
+        mTimer.FireAt(earliestRenewTime);
+    }
+
+exit:
+    return;
+}
+
+void Client::GrowRetryWaitInterval(void)
+{
+    mRetryWaitInterval =
+        mRetryWaitInterval / kRetryIntervalGrowthFactorDenominator * kRetryIntervalGrowthFactorNumerator;
+
+    if (mRetryWaitInterval > kMaxRetryWaitInterval)
+    {
+        mRetryWaitInterval = kMaxRetryWaitInterval;
+    }
+}
+
+uint32_t Client::GetBoundedLeaseInterval(uint32_t aInterval, uint32_t aDefaultInterval) const
+{
+    uint32_t boundedInterval = aDefaultInterval;
+
+    if (aInterval != 0)
+    {
+        boundedInterval = OT_MIN(aInterval, static_cast<uint32_t>(kMaxLease));
+    }
+
+    return boundedInterval;
+}
+
+bool Client::ShouldRenewEarly(const Service &aService) const
+{
+    // Check if we reached the service renew time or close to it. The
+    // "early renew interval" is used to allow early refresh. It is
+    // calculated as a factor of the `mAcceptedLeaseInterval`. The
+    // "early lease renew factor" is given as a fraction (numerator and
+    // denominator). If the denominator is set to zero (i.e., factor is
+    // set to infinity), then service is always included in all SRP
+    // update messages.
+
+    bool shouldRenew;
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_EARLY_LEASE_RENEW_FACTOR_DENOMINATOR != 0
+    uint32_t earlyRenewInterval =
+        Time::SecToMsec(mAcceptedLeaseInterval) / kEarlyLeaseRenewFactorDenominator * kEarlyLeaseRenewFactorNumerator;
+
+    shouldRenew = (aService.GetLeaseRenewTime() <= TimerMilli::GetNow() + earlyRenewInterval);
+#else
+    OT_UNUSED_VARIABLE(aService);
+    shouldRenew = true;
+#endif
+
+    return shouldRenew;
+}
+
+void Client::HandleTimer(Timer &aTimer)
+{
+    aTimer.Get<Client>().HandleTimer();
+}
+
+void Client::HandleTimer(void)
+{
+    switch (GetState())
+    {
+    case kStateStopped:
+    case kStatePaused:
+        break;
+
+    case kStateToUpdate:
+    case kStateToRetry:
+        SendUpdate();
+        break;
+
+    case kStateUpdating:
+        LogRetryWaitInterval();
+        otLogInfoSrp("[client] Timed out, no response");
+        GrowRetryWaitInterval();
+        SetState(kStateToUpdate);
+        InvokeCallback(kErrorResponseTimeout);
+        break;
+
+    case kStateUpdated:
+        UpdateState();
+        break;
+    }
+}
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+
+void Client::EnableAutoStartMode(AutoStartCallback aCallback, void *aContext)
+{
+    mAutoStartCallback = aCallback;
+    mAutoStartContext  = aContext;
+
+    VerifyOrExit(!mAutoStartModeEnabled);
+    mAutoStartModeEnabled = true;
+    ProcessAutoStart();
+
+exit:
+    return;
+}
+
+void Client::ProcessAutoStart(void)
+{
+    uint16_t                                numServers = 0;
+    NetworkData::Service::SrpServer::Info   selectedServer;
+    NetworkData::Service::SrpServer::Info   server;
+    NetworkData::Service::Manager::Iterator iterator;
+
+    VerifyOrExit(mAutoStartModeEnabled);
+
+    // If the client is not running we check if there is any SRP sever
+    // info in Network Data and select one randomly and then start the
+    // client. If the client is already running with a server that was
+    // selected by the auto-start feature, we verify that the selected
+    // server is still present in the Network Data.
+
+    VerifyOrExit(!IsRunning() || mAutoStartDidSelectServer);
+
+    while (Get<NetworkData::Service::Manager>().GetNextSrpServerInfo(iterator, server) == kErrorNone)
+    {
+        numServers++;
+
+        // Choose a server randomly (with uniform distribution) from
+        // the list of servers. As we iterate through server entries,
+        // with probability `1/numServers`, we choose to switch the
+        // current selected server with the new entry. This approach
+        // results in a uniform/same probability of selection among
+        // all server entries.
+
+        if ((numServers == 1) || (Random::NonCrypto::GetUint16InRange(0, numServers) == 0))
+        {
+            selectedServer = server;
+        }
+
+        if (IsRunning() && mAutoStartDidSelectServer && (GetServerAddress() == server.mSockAddr))
+        {
+            ExitNow();
+        }
+    }
+
+    if (IsRunning())
+    {
+        otLogInfoSrp("[client] Server %s is no longer present in net data", GetServerAddress().ToString().AsCString());
+        Stop(kRequesterAuto);
+    }
+
+    VerifyOrExit(numServers > 0);
+    IgnoreError(Start(selectedServer.mSockAddr, kRequesterAuto));
+
+exit:
+    return;
+}
+
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+
+const char *Client::ItemStateToString(ItemState aState)
+{
+    static const char *const kItemStateStrings[] = {
+        "ToAdd",      // kToAdd      (0)
+        "Adding",     // kAdding     (1)
+        "ToRefresh",  // kToRefresh  (2)
+        "Refreshing", // kRefreshing (3)
+        "ToRemove",   // kToRemove   (4)
+        "Removing",   // kRemoving   (5)
+        "Registered", // kRegistered (6)
+        "Removed",    // kRemoved    (7)
+    };
+
+    return kItemStateStrings[aState];
+}
+
+#if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_SRP == 1)
+
+const char *Client::StateToString(State aState)
+{
+    static const char *const kStateStrings[] = {
+        "Stopped",  // kStateStopped  (0)
+        "Paused",   // kStatePaused   (1)
+        "ToUpdate", // kStateToUpdate (2)
+        "Updating", // kStateUpdating (3)
+        "Updated",  // kStateUpdated  (4)
+        "ToRetry",  // kStateToRetry  (5)
+    };
+
+    static_assert(kStateStopped == 0, "kStateStopped value is not correct");
+    static_assert(kStatePaused == 1, "kStatePaused value is not correct");
+    static_assert(kStateToUpdate == 2, "kStateToUpdate value is not correct");
+    static_assert(kStateUpdating == 3, "kStateUpdating value is not correct");
+    static_assert(kStateUpdated == 4, "kStateUpdated value is not correct");
+    static_assert(kStateToRetry == 5, "kStateToRetry value is not correct");
+
+    return kStateStrings[aState];
+}
+
+void Client::LogRetryWaitInterval(void) const
+{
+    enum : uint16_t
+    {
+        kLogInMsecLimit = 5000, // Max interval (in msec) to log the value in msec unit
+        kMsecInSec      = 1000,
+    };
+
+    uint32_t interval = GetRetryWaitInterval();
+
+    otLogInfoSrp("[client] Retry interval %u %s", (interval < kLogInMsecLimit) ? interval : Time::MsecToSec(interval),
+                 (interval < kLogInMsecLimit) ? "ms" : "sec");
+}
+
+#endif // #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_SRP == 1)
+
+} // namespace Srp
+} // namespace ot
+
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
diff --git a/src/core/net/srp_client.hpp b/src/core/net/srp_client.hpp
new file mode 100644
index 0000000..939558a
--- /dev/null
+++ b/src/core/net/srp_client.hpp
@@ -0,0 +1,800 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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.
+ */
+
+#ifndef SRP_CLIENT_HPP_
+#define SRP_CLIENT_HPP_
+
+#include "openthread-core-config.h"
+
+#include <openthread/srp_client.h>
+
+#include "common/clearable.hpp"
+#include "common/linked_list.hpp"
+#include "common/locator.hpp"
+#include "common/message.hpp"
+#include "common/non_copyable.hpp"
+#include "common/notifier.hpp"
+#include "common/timer.hpp"
+#include "crypto/ecdsa.hpp"
+#include "net/dns_types.hpp"
+#include "net/ip6.hpp"
+#include "net/udp6.hpp"
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+
+/**
+ * @file
+ *   This file includes definitions for the SRP (Service Registration Protocol) client.
+ */
+
+namespace ot {
+namespace Srp {
+
+#if !OPENTHREAD_CONFIG_ECDSA_ENABLE
+#error "SRP Client feature requires ECDSA support (OPENTHREAD_CONFIG_ECDSA_ENABLE)."
+#endif
+
+/**
+ * This class implements SRP client.
+ *
+ */
+class Client : public InstanceLocator, private NonCopyable
+{
+    friend class ot::Notifier;
+
+public:
+    /**
+     * This enumeration types represents an SRP client item (service or host info) state.
+     *
+     */
+    enum ItemState : uint8_t
+    {
+        kToAdd      = OT_SRP_CLIENT_ITEM_STATE_TO_ADD,     ///< Item to be added/registered.
+        kAdding     = OT_SRP_CLIENT_ITEM_STATE_ADDING,     ///< Item is being added/registered.
+        kToRefresh  = OT_SRP_CLIENT_ITEM_STATE_TO_REFRESH, ///< Item to be refreshed (renew lease).
+        kRefreshing = OT_SRP_CLIENT_ITEM_STATE_REFRESHING, ///< Item is being refreshed.
+        kToRemove   = OT_SRP_CLIENT_ITEM_STATE_TO_REMOVE,  ///< Item to be removed.
+        kRemoving   = OT_SRP_CLIENT_ITEM_STATE_REMOVING,   ///< Item is being removed.
+        kRegistered = OT_SRP_CLIENT_ITEM_STATE_REGISTERED, ///< Item is registered with server.
+        kRemoved    = OT_SRP_CLIENT_ITEM_STATE_REMOVED,    ///< Item is removed.
+    };
+
+    /**
+     * This function pointer type defines the callback used by SRP client to notify user of a changes/events/errors.
+     *
+     * Please see `otSrpClientCallback` for more details.
+     *
+     */
+    typedef otSrpClientCallback Callback;
+
+    /**
+     * This type represents an SRP client host info.
+     *
+     */
+    class HostInfo : public otSrpClientHostInfo, public Clearable<HostInfo>
+    {
+        friend class Client;
+
+    public:
+        /**
+         * This method initializes the `HostInfo` object.
+         *
+         */
+        void Init(void);
+
+        /**
+         * This method clears the `HostInfo` object.
+         *
+         */
+        void Clear(void);
+
+        /**
+         * This method gets the host name (label) string.
+         *
+         * @returns The host name (label) string, or nullptr if not yet set.
+         *
+         */
+        const char *GetName(void) const { return mName; }
+
+        /**
+         * This method gets the number of host IPv6 addresses.
+         *
+         * @returns The number of host IPv6 addresses.
+         *
+         */
+        uint8_t GetNumAddresses(void) const { return mNumAddresses; }
+
+        /**
+         * This method gets the host IPv6 address at a given index.
+         *
+         * @param[in] aIndex  The index to get (MUST be smaller than `GetNumAddresses()`).
+         *
+         * @returns  The host IPv6 address at index @p aIndex.
+         *
+         */
+        const Ip6::Address &GetAddress(uint8_t aIndex) const
+        {
+            return static_cast<const Ip6::Address &>(mAddresses[aIndex]);
+        }
+
+        /**
+         * This method gets the state of `HostInfo`.
+         *
+         * @returns The `HostInfo` state.
+         *
+         */
+        ItemState GetState(void) const { return static_cast<ItemState>(mState); }
+
+    private:
+        void SetName(const char *aName) { mName = aName; }
+        void SetState(ItemState aState);
+        void SetAddresses(const Ip6::Address *aAddresses, uint8_t aNumAddresses);
+    };
+
+    /**
+     * This type represents an SRP client service.
+     *
+     */
+    class Service : public otSrpClientService, public LinkedListEntry<Service>
+    {
+        friend class Client;
+
+    public:
+        /**
+         * This method initializes and validates the `Service` object and its fields.
+         *
+         * @retval kErrorNone         Successfully initialized and validated the `Service` object.
+         * @retval kErrorInvalidArgs  The info in `Service` object is not valid (e.g. null name or bad `TxtEntry`).
+         *
+         */
+        Error Init(void);
+
+        /**
+         * This method gets the service name labels string.
+         *
+         * @returns The service name label string (e.g., "_chip._udp", not the full domain name).
+         *
+         */
+        const char *GetName(void) const { return mName; }
+
+        /**
+         * This method gets the service instance name label (not the full name).
+         *
+         * @returns The service instance name label string.
+         *
+         */
+        const char *GetInstanceName(void) const { return mInstanceName; }
+
+        /**
+         * This method gets the service port number.
+         *
+         * @returns The service port number.
+         *
+         */
+        uint16_t GetPort(void) const { return mPort; }
+
+        /**
+         * This method gets the service priority.
+         *
+         * @returns The service priority.
+         *
+         */
+        uint16_t GetPriority(void) const { return mPriority; }
+
+        /**
+         * This method gets the service weight.
+         *
+         * @returns The service weight.
+         *
+         */
+        uint16_t GetWeight(void) const { return mWeight; }
+
+        /**
+         * This method gets the array of service TXT entries.
+         *
+         * @returns A pointer to an array of service TXT entries.
+         *
+         */
+        const Dns::TxtEntry *GetTxtEntries(void) const { return static_cast<const Dns::TxtEntry *>(mTxtEntries); }
+
+        /**
+         * This method gets the number of entries in the service TXT entry array.
+         *
+         * @returns The number of entries in the service TXT entry array.
+         *
+         */
+        uint8_t GetNumTxtEntries(void) const { return mNumTxtEntries; }
+
+        /**
+         * This method get the state of service.
+         *
+         * @returns The service state.
+         *
+         */
+        ItemState GetState(void) const { return static_cast<ItemState>(mState); }
+
+    private:
+        void      SetState(ItemState aState);
+        TimeMilli GetLeaseRenewTime(void) const { return TimeMilli(mData); }
+        void      SetLeaseRenewTime(TimeMilli aTime) { mData = aTime.GetValue(); }
+    };
+
+    /**
+     * This constructor initializes the SRP `Client` object.
+     *
+     * @param[in]  aInstance  A reference to the OpenThread instance.
+     *
+     */
+    explicit Client(Instance &aInstance);
+
+    /**
+     * This method starts the SRP client operation.
+     *
+     * SRP client will prepare and send "SRP Update" message to the SRP server once all the following conditions are
+     * met:
+     *
+     *  - The SRP client is started - `Start()` is called
+     *  - Host name is set - `SetHostName()` is called.
+     *  - At least one host IPv6 address is set - `SetHostAddresses()` is called.
+     *  - At least one service is added - `AddService()` is called.
+     *
+     * It does not matter in which order these methods are called. When all conditions are met, the SRP client will
+     * wait for a short delay before preparing an "SRP Update" message and sending it to server. This delay allows for
+     * user to add multiple services and/or IPv6 addresses before the first SRP Update message is sent (ensuring a
+     * single SRP Update is sent containing all the info).
+     *
+     * @param[in] aServerSockAddr  The socket address (IPv6 address and port number) of the SRP server.
+     *
+     * @retval kErrorNone     SRP client operation started successfully or it is already running with same server
+     *                        socket address and callback.
+     * @retval kErrorBusy     SRP client is busy running with a different socket address.
+     * @retval kErrorFailed   Failed to open/connect the client's UDP socket.
+     *
+     */
+    Error Start(const Ip6::SockAddr &aServerSockAddr) { return Start(aServerSockAddr, kRequesterUser); }
+
+    /**
+     * This method stops the SRP client operation.
+     *
+     * This method stops any further interactions with the SRP server. Note that it does not remove or clear host info
+     * and/or list of services. It marks all services to be added/removed again once the client is started again.
+     *
+     * If `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE` (auto-start feature) is enabled, a call to this method
+     * also disables the auto-start mode.
+     *
+     */
+    void Stop(void) { Stop(kRequesterUser); }
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+    /**
+     * This function pointer type defines the callback used by SRP client to notify user when it is auto-started or
+     * stopped.
+     *
+     */
+    typedef otSrpClientAutoStartCallback AutoStartCallback;
+
+    /**
+     * This method enables the auto-start mode.
+     *
+     * Config option `OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE` specifies the default auto-start mode
+     * (whether it is enabled or disabled at the start of OT stack).
+     *
+     * When auto-start is enabled, the SRP client will monitor the Thread Network Data for SRP Server Service entries
+     * and automatically start and stop the client when an SRP server is detected.
+     *
+     * If multiple SRP servers are found, a random one will be selected. If the selected SRP server is no longer
+     * detected (not longer present in the Thread Network Data), the SRP client will be stopped and then it may switch
+     * to another SRP server (if available).
+     *
+     * When the SRP client is explicitly started through a successful call to `Start()`, the given SRP server address
+     * in `Start()` will continue to be used regardless of the state of auto-start mode and whether the same SRP
+     * server address is discovered or not in the Thread Network Data. In this case, only an explicit `Stop()` call
+     * will stop the client.
+     *
+     * @param[in] aCallback   A callback to notify when client is auto-started/stopped. Can be `nullptr` if not needed.
+     * @param[in] aContext    A context to be passed when invoking @p aCallback.
+     *
+     */
+    void EnableAutoStartMode(AutoStartCallback aCallback, void *aContext);
+
+    /**
+     * This method disables the auto-start mode.
+     *
+     * Disabling the auto-start mode will not stop the client if it is already running but the client stops monitoring
+     * the Thread Network Data to verify that the selected SRP server is still present in it.
+     *
+     * Note that a call to `Stop()` will also disable the auto-start mode.
+     *
+     */
+    void DisableAutoStartMode(void) { mAutoStartModeEnabled = false; }
+
+    /**
+     * This method indicates the current state of auto-start mode (enabled or disabled).
+     *
+     * @returns TRUE if the auto-start mode is enabled, FALSE otherwise.
+     *
+     */
+    bool IsAutoStartModeEnabled(void) const { return mAutoStartModeEnabled; }
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+
+    /**
+     * This method indicates whether the SRP client is running or not.
+     *
+     * @returns TRUE if the SRP client is running, FALSE otherwise.
+     *
+     */
+    bool IsRunning(void) const { return (mState != kStateStopped); }
+
+    /**
+     * This method gets the socket address (IPv6 address and port number) of the SRP server which is being used by SRP
+     * client.
+     *
+     * If the client is not running, the address is unspecified (all zero) with zero port number.
+     *
+     * @returns The SRP server's socket address.
+     *
+     */
+    const Ip6::SockAddr &GetServerAddress(void) const { return mSocket.GetPeerName(); }
+
+    /**
+     * This method sets the callback used to notify caller of events/changes.
+     *
+     * The SRP client allows a single callback to be registered. So consecutive calls to this method will overwrite any
+     * previously set callback functions.
+     *
+     * @param[in] aCallback        The callback to notify of events and changes. Can be nullptr if not needed.
+     * @param[in] aContext         An arbitrary context used with @p aCallback.
+     *
+     */
+    void SetCallback(Callback aCallback, void *aContext);
+
+    /**
+     * This method gets the lease interval used in SRP update requests.
+     *
+     * Note that this is lease duration that would be requested by the SRP client. Server may choose to accept a
+     * different lease interval.
+     *
+     * @returns The lease interval (in seconds).
+     *
+     */
+    uint32_t GetLeaseInterval(void) const { return mLeaseInterval; }
+
+    /**
+     * This method sets the lease interval used in SRP update requests.
+     *
+     * Changing the lease interval does not impact the accepted lease interval of already registered services/host-info.
+     * It only changes any future SRP update messages (i.e adding new services and/or refreshes of existing services).
+     *
+     * @param[in] The lease interval (in seconds). If zero, the default value `kDefaultLease` would be used.
+     *
+     */
+    void SetLeaseInterval(uint32_t aInterval) { mLeaseInterval = GetBoundedLeaseInterval(aInterval, kDefaultLease); }
+
+    /**
+     * This method gets the key lease interval used in SRP update requests.
+     *
+     * @returns The key lease interval (in seconds).
+     *
+     */
+    uint32_t GetKeyLeaseInterval(void) const { return mKeyLeaseInterval; }
+
+    /**
+     * This method sets the key lease interval used in SRP update requests.
+     *
+     * Changing the lease interval does not impact the accepted lease interval of already registered services/host-info.
+     * It only changes any future SRP update messages (i.e adding new services and/or refreshes of existing services).
+     *
+     * @param[in] The key lease interval (in seconds). If zero, the default value `kDefaultKeyLease` would be used.
+     *
+     */
+    void SetKeyLeaseInterval(uint32_t aInterval)
+    {
+        mKeyLeaseInterval = GetBoundedLeaseInterval(aInterval, kDefaultKeyLease);
+    }
+
+    /**
+     * This method gets the host info.
+     *
+     * @returns A reference to host info structure.
+     *
+     */
+    const HostInfo &GetHostInfo(void) const { return mHostInfo; }
+
+    /**
+     * This function sets the host name label.
+     *
+     * After a successful call to this function, `Callback` will be called to report the status of host info
+     *  registration with SRP server.
+     *
+     * The host name can be set before client is started or after start but before host info is registered with server
+     * (host info should be in either `kToAdd` or `kRemoved`).
+     *
+     * @param[in] aName       A pointer to host name label string (MUST NOT be NULL). Pointer the string buffer MUST
+     *                        persist and remain valid and constant after return from this function.
+     *
+     * @retval kErrorNone           The host name label was set successfully.
+     * @retval kErrorInvalidArgs    The @p aName is NULL.
+     * @retval kErrorInvalidState   The host name is already set and registered with the server.
+     *
+     */
+    Error SetHostName(const char *aName);
+
+    /**
+     * This method sets/updates the list of host IPv6 address.
+     *
+     * Host IPv6 addresses can be set/changed before start or even during operation of SRP client (e.g. to add/remove
+     * or change a previously registered host address), except when the host info is being removed (client is busy
+     * handling a remove request from an earlier call to `RemoveHostAndServices()` and host info still being in either
+     * `kStateToRemove` or `kStateRemoving` states).
+     *
+     * After a successful call to this method, `Callback` will be called to report the status of the address
+     * registration with SRP server.
+     *
+     * @param[in] aAddresses          A pointer to the an array containing the host IPv6 addresses.
+     * @param[in] aNumAddresses       The number of addresses in the @p aAddresses array.
+     *
+     * @retval kErrorNone           The host IPv6 address list change started successfully. The `Callback` will be
+     *                              called to report the status of registering addresses with server.
+     * @retval kErrorInvalidArgs    The address list is invalid (e.g., must contain at least one address).
+     * @retval kErrorInvalidState   Host is being removed and therefore cannot change host address.
+     *
+     */
+    Error SetHostAddresses(const Ip6::Address *aAddresses, uint8_t aNumAddresses);
+
+    /**
+     * This method adds a service to be registered with server.
+     *
+     * After a successful call to this method, `Callback` will be called to report the status of the service
+     * addition/registration with SRP server.
+     *
+     * @param[in] aService         A `Service` to add (the instance must persist and remain unchanged after
+     *                             successful return from this method).
+     *
+     * @retval kErrorNone          The addition of service started successfully. The `Callback` will be called to
+     *                             report the status.
+     * @retval kErrorAlready       The same service is already in the list.
+     * @retval kErrorInvalidArgs   The service structure is invalid (e.g., bad service name or `TxEntry`).
+     *
+     */
+    Error AddService(Service &aService);
+
+    /**
+     * This method removes a service to be unregistered with server.
+     *
+     * @param[in] aService         A `Service` to remove (the instance must persist and remain unchanged after
+     *                             successful return from this method).
+     *
+     * @retval kErrorNone      The removal of service started successfully. The `Callback` will be called to report
+     *                         the status.
+     * @retval kErrorNotFound  The service could not be found in the list.
+     *
+     */
+
+    Error RemoveService(Service &aService);
+
+    /**
+     * This method gets the list of services being managed by client.
+     *
+     * @returns The list of services.
+     *
+     */
+    const LinkedList<Service> &GetServices(void) const { return mServices; }
+
+    /**
+     * This method starts the remove process of the host info and all services.
+     *
+     * After retuning from this method, `Callback` will be called to report the status of remove request with
+     * SRP server.
+     *
+     * If the host info is to be permanently removed from server, @p aRemoveKeyLease should be set to `true` which
+     * removes the key lease associated with host on server. Otherwise, the key lease record is kept as before, which
+     * ensures that the server holds the host name in reserve for when the client once again able to provide and
+     * register its service(s).
+     *
+     * @param[in] aRemoveKeyLease  A boolean indicating whether or not the host key lease should also be removed.
+     *
+     * @retval kErrorNone      The removal of host and services started successfully. The `Callback` will be called
+     *                         to report the status.
+     * @retval kErrorAlready   The host is already removed.
+     *
+     */
+    Error RemoveHostAndServices(bool aShouldRemoveKeyLease);
+
+    /**
+     * This method clears all host info and all the services.
+     *
+     * Unlike `RemoveHostAndServices()` which sends an update message to server to remove/unregister all the info, this
+     * method clears all the info immediately without any interaction with server.
+     *
+     */
+    void ClearHostAndServices(void);
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE
+    /**
+     * This method gets the domain name being used by SRP client.
+     *
+     * If domain name is not set, "default.service.arpa" will be used.
+     *
+     * @returns The domain name string.
+     *
+     */
+    const char *GetDomainName(void) const { return mDomainName; }
+
+    /**
+     * This method sets the domain name to be used by SRP client.
+     *
+     * This is an optional method. If not set "default.service.arpa" will be used.
+     *
+     * The domain name can be set before client is started or after start but before host info is registered with server
+     * (host info should be in either `kToAdd` or `kToRemove`).
+     *
+     * @param[in] aName      A pointer to the domain name string. If NULL sets it to default "default.service.arpa".
+     *
+     * @retval kErrorNone           The domain name label was set successfully.
+     * @retval kErrorInvalidState   The host info is already registered with server.
+     *
+     */
+    Error SetDomainName(const char *aName);
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE
+
+    /**
+     * This static method converts a `ItemState` to a string.
+     *
+     * @param[in] aState   An `ItemState`.
+     *
+     * @returns A string representation of @p aState.
+     *
+     */
+    static const char *ItemStateToString(ItemState aState);
+
+private:
+    enum : uint8_t
+    {
+        kFastPollsAfterUpdateTx = 11, // Number of fast data polls after SRP Update tx (11x 188ms = ~2 seconds)
+    };
+
+    enum : uint16_t
+    {
+        kUdpPayloadSize = Ip6::Ip6::kMaxDatagramLength - sizeof(Ip6::Udp::Header), // Max UDP payload size
+    };
+
+    enum : uint32_t
+    {
+        // -------------------------------
+        // Lease related constants
+
+        kDefaultLease    = OPENTHREAD_CONFIG_SRP_CLIENT_DEFAULT_LEASE,     // in seconds.
+        kDefaultKeyLease = OPENTHREAD_CONFIG_SRP_CLIENT_DEFAULT_KEY_LEASE, // in seconds.
+
+        // The guard interval determines how much earlier (relative to
+        // the lease expiration time) the client will send an update
+        // to renew the lease.
+        kLeaseRenewGuardInterval = OPENTHREAD_CONFIG_SRP_CLIENT_LEASE_RENEW_GUARD_INTERVAL, // in seconds.
+
+        // Max allowed lease time to avoid timer roll-over (~24.8 days).
+        kMaxLease = (Timer::kMaxDelay / 1000) - 1,
+
+        // Opportunistic early refresh: When sending an SRP update, the
+        // services that are not yet expired but are close, are allowed
+        // to refresh early and are included in the SRP update. This
+        // helps place more services on the same lease refresh schedule
+        // reducing number of messages sent to the SRP server. The
+        // "early lease renewal interval" is used to determine if a
+        // service can renew early. The interval is calculated by
+        // multiplying the accepted lease interval by the"early lease
+        // renewal factor" which is given as a fraction (numerator and
+        // denominator).
+        //
+        // If the factor is set to zero (numerator=0, denominator=1),
+        // the opportunistic early refresh behavior is disabled. If
+        // denominator is set to zero (the factor is set to infinity),
+        // then all services (including previously registered ones)
+        // are always included in SRP update message.
+
+        kEarlyLeaseRenewFactorNumerator   = OPENTHREAD_CONFIG_SRP_CLIENT_EARLY_LEASE_RENEW_FACTOR_NUMERATOR,
+        kEarlyLeaseRenewFactorDenominator = OPENTHREAD_CONFIG_SRP_CLIENT_EARLY_LEASE_RENEW_FACTOR_DENOMINATOR,
+
+        // -------------------------------
+        // When there is a change (e.g., a new service is added/removed)
+        // that requires an update, the SRP client will wait for a short
+        // delay as specified by `kUpdateTxDelay` before sending an SRP
+        // update to server. This allows the user to provide more change
+        // that are then all sent in same update message.
+        kUpdateTxDelay = OPENTHREAD_CONFIG_SRP_CLIENT_UPDATE_TX_DELAY, // in msec.
+
+        // -------------------------------
+        // Retry related constants
+        //
+        // If the preparation or transmission of an SRP update message
+        // fails (e.g., no buffer to allocate the message), SRP client
+        // will retry after a short interval `kTxFailureRetryInterval`
+        // up to `kMaxTxFailureRetries` attempts. After this, the retry
+        // wait interval will be used (which keeps growing on each failure
+        // - please see bellow).
+        //
+        // If the update message is sent successfully but there is no
+        // response from server or if server rejects the update, the
+        // client will retransmit the update message after some wait
+        // interval. The wait interval starts from the minimum value and
+        // is increased by the growth factor on back-to-back failures up
+        // to the max value. The growth factor is given as a fraction
+        // (e.g., for 1.5, we can use 15 as the numerator and 10 as the
+        // denominator). A random jitter is added to the retry interval.
+        // If the current wait interval value is smaller than the jitter
+        // interval, then wait interval value itself is used as the
+        // jitter value. For example, with jitter interval of 2 seconds
+        // if the current retry interval is 800ms, then a random wait
+        // interval in [0,2*800] ms will be used.
+
+        kTxFailureRetryInterval               = 250, // in ms
+        kMaxTxFailureRetries                  = 8,   // num of quick retries after tx failure
+        kMinRetryWaitInterval                 = OPENTHREAD_CONFIG_SRP_CLIENT_MIN_RETRY_WAIT_INTERVAL, // in ms
+        kMaxRetryWaitInterval                 = OPENTHREAD_CONFIG_SRP_CLIENT_MAX_RETRY_WAIT_INTERVAL, // in ms
+        kRetryIntervalGrowthFactorNumerator   = OPENTHREAD_CONFIG_SRP_CLIENT_RETRY_INTERVAL_GROWTH_FACTOR_NUMERATOR,
+        kRetryIntervalGrowthFactorDenominator = OPENTHREAD_CONFIG_SRP_CLIENT_RETRY_INTERVAL_GROWTH_FACTOR_DENOMINATOR,
+    };
+
+    enum : uint16_t
+    {
+        kTxFailureRetryJitter = 10,                                                      // in ms
+        kRetryIntervalJitter  = OPENTHREAD_CONFIG_SRP_CLIENT_RETRY_WAIT_INTERVAL_JITTER, // in ms
+    };
+
+    static_assert(kDefaultLease <= static_cast<uint32_t>(kMaxLease), "kDefaultLease is larger than max");
+    static_assert(kDefaultKeyLease <= static_cast<uint32_t>(kMaxLease), "kDefaultKeyLease is larger than max");
+
+    enum State : uint8_t
+    {
+        kStateStopped,  // Client is stopped.
+        kStatePaused,   // Client is paused (due to device being detached).
+        kStateToUpdate, // Waiting to send SRP update
+        kStateUpdating, // SRP update is sent, waiting for response from server.
+        kStateUpdated,  // SRP update response received from server.
+        kStateToRetry,  // SRP update tx failed, waiting to retry.
+    };
+
+    enum : bool
+    {
+        kAutoStartDefaultMode = OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_DEFAULT_MODE,
+    };
+
+    // This enumeration type is used by the private `Start()` and
+    // `Stop()` methods to indicate whether it is being requested by the
+    // user or by the auto-start feature.
+    enum Requester
+    {
+        kRequesterUser,
+#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+        kRequesterAuto,
+#endif
+    };
+
+    struct Info : public Clearable<Info>
+    {
+        enum : uint16_t
+        {
+            kUnknownOffset = 0, // Unknown offset value (used when offset is not yet set).
+        };
+
+        uint16_t                     mDomainNameOffset; // Offset of domain name serialization
+        uint16_t                     mHostNameOffset;   // Offset of host name serialization.
+        uint16_t                     mRecordCount;      // Number of resource records in Update section.
+        Crypto::Ecdsa::P256::KeyPair mKeyPair;          // The ECDSA key pair.
+    };
+
+    Error        Start(const Ip6::SockAddr &aServerSockAddr, Requester aRequester);
+    void         Stop(Requester aRequester);
+    void         Resume(void);
+    void         Pause(void);
+    void         HandleNotifierEvents(Events aEvents);
+    void         HandleRoleChanged(void);
+    void         UpdateServiceStateToRemove(Service &aService);
+    State        GetState(void) const { return mState; }
+    void         SetState(State aState);
+    void         ChangeHostAndServiceStates(const ItemState *aNewStates);
+    void         InvokeCallback(Error aError) const;
+    void         InvokeCallback(Error aError, const HostInfo &aHostInfo, const Service *aRemovedServices) const;
+    void         ClearHostInfoAndServices(void);
+    void         HandleHostInfoOrServiceChange(void);
+    void         SendUpdate(void);
+    Error        PrepareUpdateMessage(Message &aMessage);
+    Error        ReadOrGenerateKey(Crypto::Ecdsa::P256::KeyPair &aKeyPair);
+    Error        AppendServiceInstructions(Service &aService, Message &aMessage, Info &aInfo);
+    Error        AppendHostDescriptionInstruction(Message &aMessage, Info &aInfo) const;
+    Error        AppendDeleteAllRrsets(Message &aMessage) const;
+    Error        AppendHostName(Message &aMessage, Info &aInfo, bool aDoNotCompress = false) const;
+    Error        AppendUpdateLeaseOptRecord(Message &aMessage) const;
+    Error        AppendSignature(Message &aMessage, Info &aInfo);
+    void         UpdateRecordLengthInMessage(Dns::ResourceRecord &aRecord, uint16_t aOffset, Message &aMessage) const;
+    static void  HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
+    void         ProcessResponse(Message &aMessage);
+    void         HandleUpdateDone(void);
+    void         GetRemovedServices(LinkedList<Service> &aRemovedServices);
+    static Error ReadResourceRecord(const Message &aMessage, uint16_t &aOffset, Dns::ResourceRecord &aRecord);
+    Error        ProcessOptRecord(const Message &aMessage, uint16_t aOffset, const Dns::OptRecord &aOptRecord);
+    void         UpdateState(void);
+    uint32_t     GetRetryWaitInterval(void) const { return mRetryWaitInterval; }
+    void         ResetRetryWaitInterval(void) { mRetryWaitInterval = kMinRetryWaitInterval; }
+    void         GrowRetryWaitInterval(void);
+    uint32_t     GetBoundedLeaseInterval(uint32_t aInterval, uint32_t aDefaultInterval) const;
+    bool         ShouldRenewEarly(const Service &aService) const;
+    static void  HandleTimer(Timer &aTimer);
+    void         HandleTimer(void);
+#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+    void ProcessAutoStart(void);
+#endif
+
+#if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_SRP == 1)
+    static const char *StateToString(State aState);
+    void               LogRetryWaitInterval(void) const;
+#else
+    void LogRetryWaitInterval(void) const {}
+#endif
+
+    static const char kDefaultDomainName[];
+
+    static_assert(kMaxTxFailureRetries < 16, "kMaxTxFailureRetries exceed the range of mTxFailureRetryCount (4-bit)");
+
+    State   mState;
+    uint8_t mTxFailureRetryCount : 4;
+    bool    mShouldRemoveKeyLease : 1;
+#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+    bool mAutoStartModeEnabled : 1;
+    bool mAutoStartDidSelectServer : 1;
+#endif
+
+    uint16_t mUpdateMessageId;
+    uint32_t mRetryWaitInterval;
+
+    TimeMilli mLeaseRenewTime;
+    uint32_t  mAcceptedLeaseInterval;
+    uint32_t  mLeaseInterval;
+    uint32_t  mKeyLeaseInterval;
+
+    Ip6::Udp::Socket mSocket;
+
+    Callback mCallback;
+    void *   mCallbackContext;
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE
+    AutoStartCallback mAutoStartCallback;
+    void *            mAutoStartContext;
+#endif
+
+    const char *        mDomainName;
+    HostInfo            mHostInfo;
+    LinkedList<Service> mServices;
+    TimerMilli          mTimer;
+};
+
+} // namespace Srp
+} // namespace ot
+
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+
+#endif // SRP_CLIENT_HPP_
diff --git a/src/core/net/srp_server.cpp b/src/core/net/srp_server.cpp
new file mode 100644
index 0000000..c60630e
--- /dev/null
+++ b/src/core/net/srp_server.cpp
@@ -0,0 +1,1703 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file includes implementation for SRP server.
+ */
+
+#include "srp_server.hpp"
+
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+
+#include "common/instance.hpp"
+#include "common/locator-getters.hpp"
+#include "common/logging.hpp"
+#include "common/new.hpp"
+#include "net/dns_types.hpp"
+#include "thread/network_data_service.hpp"
+#include "thread/thread_netif.hpp"
+
+namespace ot {
+namespace Srp {
+
+static const char kDefaultDomain[] = "default.service.arpa.";
+
+static Dns::UpdateHeader::Response ErrorToDnsResponseCode(Error aError)
+{
+    Dns::UpdateHeader::Response responseCode;
+
+    switch (aError)
+    {
+    case kErrorNone:
+        responseCode = Dns::UpdateHeader::kResponseSuccess;
+        break;
+    case kErrorNoBufs:
+        responseCode = Dns::UpdateHeader::kResponseServerFailure;
+        break;
+    case kErrorParse:
+        responseCode = Dns::UpdateHeader::kResponseFormatError;
+        break;
+    case kErrorDuplicated:
+        responseCode = Dns::UpdateHeader::kResponseNameExists;
+        break;
+    default:
+        responseCode = Dns::UpdateHeader::kResponseRefused;
+        break;
+    }
+
+    return responseCode;
+}
+
+Server::Server(Instance &aInstance)
+    : InstanceLocator(aInstance)
+    , mSocket(aInstance)
+    , mServiceUpdateHandler(nullptr)
+    , mServiceUpdateHandlerContext(nullptr)
+    , mDomain(nullptr)
+    , mMinLease(kDefaultMinLease)
+    , mMaxLease(kDefaultMaxLease)
+    , mMinKeyLease(kDefaultMinKeyLease)
+    , mMaxKeyLease(kDefaultMaxKeyLease)
+    , mLeaseTimer(aInstance, HandleLeaseTimer)
+    , mOutstandingUpdatesTimer(aInstance, HandleOutstandingUpdatesTimer)
+    , mEnabled(false)
+{
+    IgnoreError(SetDomain(kDefaultDomain));
+}
+
+Server::~Server(void)
+{
+    Instance::HeapFree(mDomain);
+}
+
+void Server::SetServiceHandler(otSrpServerServiceUpdateHandler aServiceHandler, void *aServiceHandlerContext)
+{
+    mServiceUpdateHandler        = aServiceHandler;
+    mServiceUpdateHandlerContext = aServiceHandlerContext;
+}
+
+bool Server::IsRunning(void) const
+{
+    return mSocket.IsBound();
+}
+
+void Server::SetEnabled(bool aEnabled)
+{
+    VerifyOrExit(mEnabled != aEnabled);
+
+    mEnabled = aEnabled;
+
+    if (!mEnabled)
+    {
+        Stop();
+    }
+    else if (Get<Mle::MleRouter>().IsAttached())
+    {
+        Start();
+    }
+
+exit:
+    return;
+}
+
+Error Server::SetLeaseRange(uint32_t aMinLease, uint32_t aMaxLease, uint32_t aMinKeyLease, uint32_t aMaxKeyLease)
+{
+    Error error = kErrorNone;
+
+    // TODO: Support longer LEASE.
+    // We use milliseconds timer for LEASE & KEY-LEASE, this is to avoid overflow.
+    VerifyOrExit(aMaxKeyLease <= Time::MsecToSec(TimerMilli::kMaxDelay), error = kErrorInvalidArgs);
+    VerifyOrExit(aMinLease <= aMaxLease, error = kErrorInvalidArgs);
+    VerifyOrExit(aMinKeyLease <= aMaxKeyLease, error = kErrorInvalidArgs);
+    VerifyOrExit(aMinLease <= aMinKeyLease, error = kErrorInvalidArgs);
+    VerifyOrExit(aMaxLease <= aMaxKeyLease, error = kErrorInvalidArgs);
+
+    mMinLease    = aMinLease;
+    mMaxLease    = aMaxLease;
+    mMinKeyLease = aMinKeyLease;
+    mMaxKeyLease = aMaxKeyLease;
+
+exit:
+    return error;
+}
+
+uint32_t Server::GrantLease(uint32_t aLease) const
+{
+    OT_ASSERT(mMinLease <= mMaxLease);
+
+    return (aLease == 0) ? 0 : OT_MAX(mMinLease, OT_MIN(mMaxLease, aLease));
+}
+
+uint32_t Server::GrantKeyLease(uint32_t aKeyLease) const
+{
+    OT_ASSERT(mMinKeyLease <= mMaxKeyLease);
+
+    return (aKeyLease == 0) ? 0 : OT_MAX(mMinKeyLease, OT_MIN(mMaxKeyLease, aKeyLease));
+}
+
+const char *Server::GetDomain(void) const
+{
+    return mDomain;
+}
+
+Error Server::SetDomain(const char *aDomain)
+{
+    Error  error             = kErrorNone;
+    char * buf               = nullptr;
+    size_t appendTrailingDot = 0;
+    size_t length            = strlen(aDomain);
+
+    VerifyOrExit(!mEnabled, error = kErrorInvalidState);
+
+    VerifyOrExit(length > 0 && length < Dns::Name::kMaxNameSize, error = kErrorInvalidArgs);
+    if (aDomain[length - 1] != '.')
+    {
+        appendTrailingDot = 1;
+    }
+
+    buf = static_cast<char *>(Instance::HeapCAlloc(1, length + appendTrailingDot + 1));
+    VerifyOrExit(buf != nullptr, error = kErrorNoBufs);
+
+    strcpy(buf, aDomain);
+    if (appendTrailingDot)
+    {
+        buf[length]     = '.';
+        buf[length + 1] = '\0';
+    }
+    Instance::HeapFree(mDomain);
+    mDomain = buf;
+
+exit:
+    if (error != kErrorNone)
+    {
+        Instance::HeapFree(buf);
+    }
+    return error;
+}
+
+const Server::Host *Server::GetNextHost(const Server::Host *aHost)
+{
+    return (aHost == nullptr) ? mHosts.GetHead() : aHost->GetNext();
+}
+
+// This method adds a SRP service host and takes ownership of it.
+// The caller MUST make sure that there is no existing host with the same hostname.
+void Server::AddHost(Host *aHost)
+{
+    OT_ASSERT(mHosts.FindMatching(aHost->GetFullName()) == nullptr);
+    IgnoreError(mHosts.Add(*aHost));
+}
+
+void Server::RemoveHost(Host *aHost, bool aRetainName, bool aNotifyServiceHandler)
+{
+    VerifyOrExit(aHost != nullptr);
+
+    aHost->mLease = 0;
+    aHost->ClearResources();
+
+    if (aRetainName)
+    {
+        otLogInfoSrp("[server] remove host '%s' (but retain its name)", aHost->mFullName);
+    }
+    else
+    {
+        aHost->mKeyLease = 0;
+        IgnoreError(mHosts.Remove(*aHost));
+        otLogInfoSrp("[server] fully remove host '%s'", aHost->mFullName);
+    }
+
+    if (aNotifyServiceHandler && mServiceUpdateHandler != nullptr)
+    {
+        mServiceUpdateHandler(aHost, kDefaultEventsHandlerTimeout, mServiceUpdateHandlerContext);
+        // We don't wait for the reply from the service update handler,
+        // but always remove the host (and its services) regardless of
+        // host/service update result. Because removing a host should fail
+        // only when there is system failure of the platform mDNS implementation
+        // and in which case the host is not expected to be still registered.
+    }
+
+    if (!aRetainName)
+    {
+        aHost->Free();
+    }
+
+exit:
+    return;
+}
+
+const Server::Service *Server::FindService(const char *aFullName) const
+{
+    const Service *service = nullptr;
+
+    for (const Host *host = mHosts.GetHead(); host != nullptr; host = host->GetNext())
+    {
+        service = host->FindService(aFullName);
+        if (service != nullptr)
+        {
+            break;
+        }
+    }
+
+    return service;
+}
+
+bool Server::HasNameConflictsWith(Host &aHost) const
+{
+    bool           hasConflicts = false;
+    const Service *service      = nullptr;
+    const Host *   existingHost = mHosts.FindMatching(aHost.GetFullName());
+
+    if (existingHost != nullptr && *aHost.GetKey() != *existingHost->GetKey())
+    {
+        ExitNow(hasConflicts = true);
+    }
+
+    // Check not only services of this host but all hosts.
+    while ((service = aHost.GetNextService(service)) != nullptr)
+    {
+        const Service *existingService = FindService(service->mFullName);
+        if (existingService != nullptr && *service->GetHost().GetKey() != *existingService->GetHost().GetKey())
+        {
+            ExitNow(hasConflicts = true);
+        }
+    }
+
+exit:
+    return hasConflicts;
+}
+
+void Server::HandleServiceUpdateResult(const Host *aHost, Error aError)
+{
+    UpdateMetadata *update = mOutstandingUpdates.FindMatching(aHost);
+
+    if (update != nullptr)
+    {
+        HandleServiceUpdateResult(update, aError);
+    }
+    else
+    {
+        otLogInfoSrp("[server] delayed SRP host update result, the SRP update has been committed");
+    }
+}
+
+void Server::HandleServiceUpdateResult(UpdateMetadata *aUpdate, Error aError)
+{
+    CommitSrpUpdate(aError, aUpdate->GetDnsHeader(), aUpdate->GetHost(), aUpdate->GetMessageInfo());
+
+    IgnoreError(mOutstandingUpdates.Remove(*aUpdate));
+    aUpdate->Free();
+
+    if (mOutstandingUpdates.IsEmpty())
+    {
+        mOutstandingUpdatesTimer.Stop();
+    }
+    else
+    {
+        mOutstandingUpdatesTimer.StartAt(mOutstandingUpdates.GetTail()->GetExpireTime(), 0);
+    }
+}
+
+void Server::CommitSrpUpdate(Error                    aError,
+                             const Dns::UpdateHeader &aDnsHeader,
+                             Host &                   aHost,
+                             const Ip6::MessageInfo & aMessageInfo)
+{
+    Host *   existingHost;
+    uint32_t hostLease;
+    uint32_t hostKeyLease;
+    uint32_t grantedLease;
+    uint32_t grantedKeyLease;
+
+    SuccessOrExit(aError);
+
+    hostLease       = aHost.GetLease();
+    hostKeyLease    = aHost.GetKeyLease();
+    grantedLease    = GrantLease(hostLease);
+    grantedKeyLease = GrantKeyLease(hostKeyLease);
+
+    aHost.SetLease(grantedLease);
+    aHost.SetKeyLease(grantedKeyLease);
+
+    existingHost = mHosts.FindMatching(aHost.GetFullName());
+
+    if (aHost.GetLease() == 0)
+    {
+        if (aHost.GetKeyLease() == 0)
+        {
+            otLogInfoSrp("[server] remove key of host %s", aHost.GetFullName());
+            RemoveHost(existingHost, /* aRetainName */ false, /* aNotifyServiceHandler */ false);
+        }
+        else if (existingHost != nullptr)
+        {
+            Service *service = nullptr;
+
+            existingHost->SetKeyLease(aHost.GetKeyLease());
+            RemoveHost(existingHost, /* aRetainName */ true, /* aNotifyServiceHandler */ false);
+            while ((service = existingHost->GetNextService(service)) != nullptr)
+            {
+                existingHost->RemoveService(service, /* aRetainName */ true, /* aNotifyServiceHandler */ false);
+            }
+        }
+
+        aHost.Free();
+    }
+    else if (existingHost != nullptr)
+    {
+        const Service *service = nullptr;
+
+        // Merge current updates into existing host.
+
+        otLogInfoSrp("[server] update host %s", existingHost->GetFullName());
+
+        existingHost->CopyResourcesFrom(aHost);
+        while ((service = aHost.GetNextService(service)) != nullptr)
+        {
+            Service *existingService = existingHost->FindService(service->mFullName);
+
+            if (service->mIsDeleted)
+            {
+                existingHost->RemoveService(existingService, /* aRetainName */ true, /* aNotifyServiceHandler */ false);
+            }
+            else
+            {
+                Service *newService = existingHost->AddService(service->mFullName);
+
+                VerifyOrExit(newService != nullptr, aError = kErrorNoBufs);
+                SuccessOrExit(aError = newService->CopyResourcesFrom(*service));
+                otLogInfoSrp("[server] %s service %s", (existingService != nullptr) ? "update existing" : "add new",
+                             service->mFullName);
+            }
+        }
+
+        aHost.Free();
+    }
+    else
+    {
+        otLogInfoSrp("[server] add new host %s", aHost.GetFullName());
+        AddHost(&aHost);
+    }
+
+    // Re-schedule the lease timer.
+    HandleLeaseTimer();
+
+exit:
+    if (aError == kErrorNone && !(grantedLease == hostLease && grantedKeyLease == hostKeyLease))
+    {
+        SendResponse(aDnsHeader, grantedLease, grantedKeyLease, aMessageInfo);
+    }
+    else
+    {
+        SendResponse(aDnsHeader, ErrorToDnsResponseCode(aError), aMessageInfo);
+    }
+}
+
+void Server::Start(void)
+{
+    Error error = kErrorNone;
+
+    VerifyOrExit(!IsRunning());
+
+    SuccessOrExit(error = mSocket.Open(HandleUdpReceive, this));
+    SuccessOrExit(error = mSocket.Bind(kUdpPort, OT_NETIF_THREAD));
+
+    SuccessOrExit(error = PublishServerData());
+
+    otLogInfoSrp("[server] start listening on port %hu", mSocket.GetSockName().mPort);
+
+exit:
+    if (error != kErrorNone)
+    {
+        otLogCritSrp("[server] failed to start: %s", ErrorToString(error));
+        // Cleanup any resources we may have allocated.
+        Stop();
+    }
+}
+
+void Server::Stop(void)
+{
+    VerifyOrExit(IsRunning());
+
+    UnpublishServerData();
+
+    while (!mHosts.IsEmpty())
+    {
+        RemoveHost(mHosts.GetHead(), /* aRetainName */ false, /* aNotifyServiceHandler */ true);
+    }
+
+    // TODO: We should cancel any oustanding service updates, but current
+    // OTBR mDNS publisher cannot properly handle it.
+    while (!mOutstandingUpdates.IsEmpty())
+    {
+        mOutstandingUpdates.Pop()->Free();
+    }
+
+    mLeaseTimer.Stop();
+    mOutstandingUpdatesTimer.Stop();
+
+    otLogInfoSrp("[server] stop listening on %hu", mSocket.GetSockName().mPort);
+    IgnoreError(mSocket.Close());
+
+exit:
+    return;
+}
+
+void Server::HandleNotifierEvents(Events aEvents)
+{
+    VerifyOrExit(mEnabled);
+    VerifyOrExit(aEvents.Contains(kEventThreadRoleChanged));
+
+    if (Get<Mle::MleRouter>().IsAttached())
+    {
+        Start();
+    }
+    else
+    {
+        Stop();
+    }
+
+exit:
+    return;
+}
+
+Error Server::PublishServerData(void)
+{
+    NetworkData::Service::SrpServer::ServerData serverData;
+
+    OT_ASSERT(mSocket.IsBound());
+
+    serverData.SetPort(mSocket.GetSockName().GetPort());
+
+    return Get<NetworkData::Service::Manager>().Add<NetworkData::Service::SrpServer>(serverData);
+}
+
+void Server::UnpublishServerData(void)
+{
+    Error error = Get<NetworkData::Service::Manager>().Remove<NetworkData::Service::SrpServer>();
+
+    if (error != kErrorNone)
+    {
+        otLogWarnSrp("[server] failed to unpublish SRP service: %s", ErrorToString(error));
+    }
+}
+
+const Server::UpdateMetadata *Server::FindOutstandingUpdate(const Ip6::MessageInfo &aMessageInfo,
+                                                            uint16_t                aDnsMessageId)
+{
+    const UpdateMetadata *ret = nullptr;
+
+    for (const UpdateMetadata *update = mOutstandingUpdates.GetHead(); update != nullptr; update = update->GetNext())
+    {
+        if (aDnsMessageId == update->GetDnsHeader().GetMessageId() &&
+            aMessageInfo.GetPeerAddr() == update->GetMessageInfo().GetPeerAddr() &&
+            aMessageInfo.GetPeerPort() == update->GetMessageInfo().GetPeerPort())
+        {
+            ExitNow(ret = update);
+        }
+    }
+
+exit:
+    return ret;
+}
+
+void Server::HandleDnsUpdate(Message &                aMessage,
+                             const Ip6::MessageInfo & aMessageInfo,
+                             const Dns::UpdateHeader &aDnsHeader,
+                             uint16_t                 aOffset)
+{
+    Error     error = kErrorNone;
+    Dns::Zone zone;
+    Host *    host = nullptr;
+
+    otLogInfoSrp("[server] receive DNS update from %s", aMessageInfo.GetPeerAddr().ToString().AsCString());
+
+    SuccessOrExit(error = ProcessZoneSection(aMessage, aDnsHeader, aOffset, zone));
+
+    if (FindOutstandingUpdate(aMessageInfo, aDnsHeader.GetMessageId()) != nullptr)
+    {
+        otLogInfoSrp("[server] drop duplicated SRP update request: messageId=%hu", aDnsHeader.GetMessageId());
+
+        // Silently drop duplicate requests.
+        // This could rarely happen, because the outstanding SRP update timer should
+        // be shorter than the SRP update retransmission timer.
+        ExitNow(error = kErrorNone);
+    }
+
+    // Per 2.3.2 of SRP draft 6, no prerequisites should be included in a SRP update.
+    VerifyOrExit(aDnsHeader.GetPrerequisiteRecordCount() == 0, error = kErrorFailed);
+
+    host = Host::New(GetInstance());
+    VerifyOrExit(host != nullptr, error = kErrorNoBufs);
+    SuccessOrExit(error = ProcessUpdateSection(*host, aMessage, aDnsHeader, zone, aOffset));
+
+    // Parse lease time and validate signature.
+    SuccessOrExit(error = ProcessAdditionalSection(host, aMessage, aDnsHeader, aOffset));
+
+    HandleUpdate(aDnsHeader, host, aMessageInfo);
+
+exit:
+    if (error != kErrorNone)
+    {
+        if (host != nullptr)
+        {
+            host->Free();
+        }
+        SendResponse(aDnsHeader, ErrorToDnsResponseCode(error), aMessageInfo);
+    }
+}
+
+Error Server::ProcessZoneSection(const Message &          aMessage,
+                                 const Dns::UpdateHeader &aDnsHeader,
+                                 uint16_t &               aOffset,
+                                 Dns::Zone &              aZone) const
+{
+    Error     error = kErrorNone;
+    char      name[Dns::Name::kMaxNameSize];
+    Dns::Zone zone;
+
+    VerifyOrExit(aDnsHeader.GetZoneRecordCount() == 1, error = kErrorParse);
+
+    SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, name, sizeof(name)));
+    // TODO: return `Dns::kResponseNotAuth` for not authorized zone names.
+    VerifyOrExit(strcmp(name, GetDomain()) == 0, error = kErrorSecurity);
+    SuccessOrExit(error = aMessage.Read(aOffset, zone));
+    aOffset += sizeof(zone);
+
+    VerifyOrExit(zone.GetType() == Dns::ResourceRecord::kTypeSoa, error = kErrorParse);
+    aZone = zone;
+
+exit:
+    return error;
+}
+
+Error Server::ProcessUpdateSection(Host &                   aHost,
+                                   const Message &          aMessage,
+                                   const Dns::UpdateHeader &aDnsHeader,
+                                   const Dns::Zone &        aZone,
+                                   uint16_t &               aOffset) const
+{
+    Error error = kErrorNone;
+
+    // Process Service Discovery, Host and Service Description Instructions with
+    // 3 times iterations over all DNS update RRs. The order of those processes matters.
+
+    // 0. Enumerate over all Service Discovery Instructions before processing any other records.
+    // So that we will know whether a name is a hostname or service instance name when processing
+    // a "Delete All RRsets from a name" record.
+    error = ProcessServiceDiscoveryInstructions(aHost, aMessage, aDnsHeader, aZone, aOffset);
+    SuccessOrExit(error);
+
+    // 1. Enumerate over all RRs to build the Host Description Instruction.
+    error = ProcessHostDescriptionInstruction(aHost, aMessage, aDnsHeader, aZone, aOffset);
+    SuccessOrExit(error);
+
+    // 2. Enumerate over all RRs to build the Service Description Insutructions.
+    error = ProcessServiceDescriptionInstructions(aHost, aMessage, aDnsHeader, aZone, aOffset);
+    SuccessOrExit(error);
+
+    // 3. Verify that there are no name conflicts.
+    VerifyOrExit(!HasNameConflictsWith(aHost), error = kErrorDuplicated);
+
+exit:
+    return error;
+}
+
+Error Server::ProcessHostDescriptionInstruction(Host &                   aHost,
+                                                const Message &          aMessage,
+                                                const Dns::UpdateHeader &aDnsHeader,
+                                                const Dns::Zone &        aZone,
+                                                uint16_t                 aOffset) const
+{
+    Error error;
+
+    OT_ASSERT(aHost.GetFullName() == nullptr);
+
+    for (uint16_t i = 0; i < aDnsHeader.GetUpdateRecordCount(); ++i)
+    {
+        char                name[Dns::Name::kMaxNameSize];
+        Dns::ResourceRecord record;
+
+        SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, name, sizeof(name)));
+        // TODO: return `Dns::kResponseNotZone` for names not in the zone.
+        VerifyOrExit(Dns::Name::IsSubDomainOf(name, GetDomain()), error = kErrorSecurity);
+        SuccessOrExit(error = aMessage.Read(aOffset, record));
+
+        if (record.GetClass() == Dns::ResourceRecord::kClassAny)
+        {
+            Service *service;
+
+            // Delete All RRsets from a name.
+            VerifyOrExit(IsValidDeleteAllRecord(record), error = kErrorFailed);
+
+            service = aHost.FindService(name);
+            if (service == nullptr)
+            {
+                // A "Delete All RRsets from a name" RR can only apply to a Service or Host Description.
+
+                if (aHost.GetFullName())
+                {
+                    VerifyOrExit(aHost.Matches(name), error = kErrorFailed);
+                }
+                else
+                {
+                    SuccessOrExit(error = aHost.SetFullName(name));
+                }
+                aHost.ClearResources();
+            }
+
+            aOffset += record.GetSize();
+            continue;
+        }
+
+        if (record.GetType() == Dns::ResourceRecord::kTypeAaaa)
+        {
+            Dns::AaaaRecord aaaaRecord;
+
+            VerifyOrExit(record.GetClass() == aZone.GetClass(), error = kErrorFailed);
+            if (aHost.GetFullName() == nullptr)
+            {
+                SuccessOrExit(error = aHost.SetFullName(name));
+            }
+            else
+            {
+                VerifyOrExit(aHost.Matches(name), error = kErrorFailed);
+            }
+
+            SuccessOrExit(error = aMessage.Read(aOffset, aaaaRecord));
+            VerifyOrExit(aaaaRecord.IsValid(), error = kErrorParse);
+
+            // Tolerate kErrorDrop for AAAA Resources.
+            VerifyOrExit(aHost.AddIp6Address(aaaaRecord.GetAddress()) != kErrorNoBufs, error = kErrorNoBufs);
+
+            aOffset += aaaaRecord.GetSize();
+        }
+        else if (record.GetType() == Dns::ResourceRecord::kTypeKey)
+        {
+            // We currently support only ECDSA P-256.
+            Dns::Ecdsa256KeyRecord key;
+
+            VerifyOrExit(record.GetClass() == aZone.GetClass(), error = kErrorFailed);
+            VerifyOrExit(aHost.GetKey() == nullptr, error = kErrorFailed);
+
+            SuccessOrExit(error = aMessage.Read(aOffset, key));
+            VerifyOrExit(key.IsValid(), error = kErrorParse);
+
+            aHost.SetKey(key);
+
+            aOffset += record.GetSize();
+        }
+        else
+        {
+            aOffset += record.GetSize();
+        }
+    }
+
+    // Verify that we have a complete Host Description Instruction.
+
+    VerifyOrExit(aHost.GetFullName() != nullptr, error = kErrorFailed);
+    VerifyOrExit(aHost.GetKey() != nullptr, error = kErrorFailed);
+    {
+        uint8_t hostAddressesNum;
+
+        aHost.GetAddresses(hostAddressesNum);
+
+        // There MUST be at least one valid address if we have nonzero lease.
+        VerifyOrExit(aHost.GetLease() > 0 || hostAddressesNum > 0, error = kErrorFailed);
+    }
+
+exit:
+    return error;
+}
+
+Error Server::ProcessServiceDiscoveryInstructions(Host &                   aHost,
+                                                  const Message &          aMessage,
+                                                  const Dns::UpdateHeader &aDnsHeader,
+                                                  const Dns::Zone &        aZone,
+                                                  uint16_t                 aOffset) const
+{
+    Error error = kErrorNone;
+
+    for (uint16_t i = 0; i < aDnsHeader.GetUpdateRecordCount(); ++i)
+    {
+        char                name[Dns::Name::kMaxNameSize];
+        Dns::ResourceRecord record;
+        char                serviceName[Dns::Name::kMaxNameSize];
+        Service *           service;
+
+        SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, name, sizeof(name)));
+        VerifyOrExit(Dns::Name::IsSubDomainOf(name, GetDomain()), error = kErrorSecurity);
+        SuccessOrExit(error = aMessage.Read(aOffset, record));
+
+        aOffset += sizeof(record);
+
+        if (record.GetType() == Dns::ResourceRecord::kTypePtr)
+        {
+            SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, serviceName, sizeof(serviceName)));
+            VerifyOrExit(Dns::Name::IsSubDomainOf(name, GetDomain()), error = kErrorSecurity);
+        }
+        else
+        {
+            aOffset += record.GetLength();
+            continue;
+        }
+
+        VerifyOrExit(record.GetClass() == Dns::ResourceRecord::kClassNone || record.GetClass() == aZone.GetClass(),
+                     error = kErrorFailed);
+
+        // TODO: check if the RR name and the full service name matches.
+
+        service = aHost.FindService(serviceName);
+        VerifyOrExit(service == nullptr, error = kErrorFailed);
+        service = aHost.AddService(serviceName);
+        VerifyOrExit(service != nullptr, error = kErrorNoBufs);
+
+        // This RR is a "Delete an RR from an RRset" update when the CLASS is NONE.
+        service->mIsDeleted = (record.GetClass() == Dns::ResourceRecord::kClassNone);
+    }
+
+exit:
+    return error;
+}
+
+Error Server::ProcessServiceDescriptionInstructions(Host &                   aHost,
+                                                    const Message &          aMessage,
+                                                    const Dns::UpdateHeader &aDnsHeader,
+                                                    const Dns::Zone &        aZone,
+                                                    uint16_t &               aOffset) const
+{
+    Service *service;
+    Error    error = kErrorNone;
+
+    for (uint16_t i = 0; i < aDnsHeader.GetUpdateRecordCount(); ++i)
+    {
+        char                name[Dns::Name::kMaxNameSize];
+        Dns::ResourceRecord record;
+
+        SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, name, sizeof(name)));
+        VerifyOrExit(Dns::Name::IsSubDomainOf(name, GetDomain()), error = kErrorSecurity);
+        SuccessOrExit(error = aMessage.Read(aOffset, record));
+
+        if (record.GetClass() == Dns::ResourceRecord::kClassAny)
+        {
+            // Delete All RRsets from a name.
+            VerifyOrExit(IsValidDeleteAllRecord(record), error = kErrorFailed);
+            service = aHost.FindService(name);
+            if (service != nullptr)
+            {
+                service->ClearResources();
+            }
+
+            aOffset += record.GetSize();
+            continue;
+        }
+
+        if (record.GetType() == Dns::ResourceRecord::kTypeSrv)
+        {
+            Dns::SrvRecord srvRecord;
+            char           hostName[Dns::Name::kMaxNameSize];
+            uint16_t       hostNameLength = sizeof(hostName);
+
+            VerifyOrExit(record.GetClass() == aZone.GetClass(), error = kErrorFailed);
+            SuccessOrExit(error = aMessage.Read(aOffset, srvRecord));
+            aOffset += sizeof(srvRecord);
+
+            SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, hostName, hostNameLength));
+            VerifyOrExit(Dns::Name::IsSubDomainOf(name, GetDomain()), error = kErrorSecurity);
+            VerifyOrExit(aHost.Matches(hostName), error = kErrorFailed);
+
+            service = aHost.FindService(name);
+            VerifyOrExit(service != nullptr && !service->mIsDeleted, error = kErrorFailed);
+
+            // Make sure that this is the first SRV RR for this service.
+            VerifyOrExit(service->mPort == 0, error = kErrorFailed);
+            service->mPriority = srvRecord.GetPriority();
+            service->mWeight   = srvRecord.GetWeight();
+            service->mPort     = srvRecord.GetPort();
+        }
+        else if (record.GetType() == Dns::ResourceRecord::kTypeTxt)
+        {
+            VerifyOrExit(record.GetClass() == aZone.GetClass(), error = kErrorFailed);
+
+            service = aHost.FindService(name);
+            VerifyOrExit(service != nullptr && !service->mIsDeleted, error = kErrorFailed);
+
+            aOffset += sizeof(record);
+            SuccessOrExit(error = service->SetTxtDataFromMessage(aMessage, aOffset, record.GetLength()));
+            aOffset += record.GetLength();
+        }
+        else
+        {
+            aOffset += record.GetSize();
+        }
+    }
+
+    service = nullptr;
+    while ((service = aHost.GetNextService(service)) != nullptr)
+    {
+        VerifyOrExit(service->mIsDeleted || (service->mTxtData != nullptr && service->mPort != 0),
+                     error = kErrorFailed);
+    }
+
+exit:
+    return error;
+}
+
+bool Server::IsValidDeleteAllRecord(const Dns::ResourceRecord &aRecord)
+{
+    return aRecord.GetClass() == Dns::ResourceRecord::kClassAny && aRecord.GetType() == Dns::ResourceRecord::kTypeAny &&
+           aRecord.GetTtl() == 0 && aRecord.GetLength() == 0;
+}
+
+Error Server::ProcessAdditionalSection(Host *                   aHost,
+                                       const Message &          aMessage,
+                                       const Dns::UpdateHeader &aDnsHeader,
+                                       uint16_t &               aOffset) const
+{
+    Error            error = kErrorNone;
+    Dns::OptRecord   optRecord;
+    Dns::LeaseOption leaseOption;
+    Dns::SigRecord   sigRecord;
+    char             name[2]; // The root domain name (".") is expected.
+    uint16_t         sigOffset;
+    uint16_t         sigRdataOffset;
+    char             signerName[Dns::Name::kMaxNameSize];
+    uint16_t         signatureLength;
+
+    VerifyOrExit(aDnsHeader.GetAdditionalRecordCount() == 2, error = kErrorFailed);
+
+    // EDNS(0) Update Lease Option.
+
+    SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, name, sizeof(name)));
+    SuccessOrExit(error = aMessage.Read(aOffset, optRecord));
+    SuccessOrExit(error = aMessage.Read(aOffset + sizeof(optRecord), leaseOption));
+    VerifyOrExit(leaseOption.IsValid(), error = kErrorFailed);
+    VerifyOrExit(optRecord.GetSize() == sizeof(optRecord) + sizeof(leaseOption), error = kErrorParse);
+
+    aOffset += optRecord.GetSize();
+
+    aHost->SetLease(leaseOption.GetLeaseInterval());
+    aHost->SetKeyLease(leaseOption.GetKeyLeaseInterval());
+
+    // SIG(0).
+
+    sigOffset = aOffset;
+    SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, name, sizeof(name)));
+    SuccessOrExit(error = aMessage.Read(aOffset, sigRecord));
+    VerifyOrExit(sigRecord.IsValid(), error = kErrorParse);
+
+    sigRdataOffset = aOffset + sizeof(Dns::ResourceRecord);
+    aOffset += sizeof(sigRecord);
+
+    // TODO: Verify that the signature doesn't expire. This is not
+    // implemented because the end device may not be able to get
+    // the synchronized date/time.
+
+    SuccessOrExit(error = Dns::Name::ReadName(aMessage, aOffset, signerName, sizeof(signerName)));
+
+    signatureLength = sigRecord.GetLength() - (aOffset - sigRdataOffset);
+    aOffset += signatureLength;
+
+    // Verify the signature. Currently supports only ECDSA.
+
+    VerifyOrExit(sigRecord.GetAlgorithm() == Dns::KeyRecord::kAlgorithmEcdsaP256Sha256, error = kErrorFailed);
+    VerifyOrExit(sigRecord.GetTypeCovered() == 0, error = kErrorFailed);
+    VerifyOrExit(signatureLength == Crypto::Ecdsa::P256::Signature::kSize, error = kErrorParse);
+
+    SuccessOrExit(error = VerifySignature(*aHost->GetKey(), aMessage, aDnsHeader, sigOffset, sigRdataOffset,
+                                          sigRecord.GetLength(), signerName));
+
+exit:
+    return error;
+}
+
+Error Server::VerifySignature(const Dns::Ecdsa256KeyRecord &aKey,
+                              const Message &               aMessage,
+                              Dns::UpdateHeader             aDnsHeader,
+                              uint16_t                      aSigOffset,
+                              uint16_t                      aSigRdataOffset,
+                              uint16_t                      aSigRdataLength,
+                              const char *                  aSignerName) const
+{
+    Error                          error;
+    uint16_t                       offset = aMessage.GetOffset();
+    uint16_t                       signatureOffset;
+    Crypto::Sha256                 sha256;
+    Crypto::Sha256::Hash           hash;
+    Crypto::Ecdsa::P256::Signature signature;
+    Message *                      signerNameMessage = nullptr;
+
+    VerifyOrExit(aSigRdataLength >= Crypto::Ecdsa::P256::Signature::kSize, error = kErrorInvalidArgs);
+
+    sha256.Start();
+
+    // SIG RDATA less signature.
+    sha256.Update(aMessage, aSigRdataOffset, sizeof(Dns::SigRecord) - sizeof(Dns::ResourceRecord));
+
+    // The uncompressed (canonical) form of the signer name should be used for signature
+    // verification. See https://tools.ietf.org/html/rfc2931#section-3.1 for details.
+    signerNameMessage = Get<Ip6::Udp>().NewMessage(0);
+    VerifyOrExit(signerNameMessage != nullptr, error = kErrorNoBufs);
+    SuccessOrExit(error = Dns::Name::AppendName(aSignerName, *signerNameMessage));
+    sha256.Update(*signerNameMessage, signerNameMessage->GetOffset(), signerNameMessage->GetLength());
+
+    // We need the DNS header before appending the SIG RR.
+    aDnsHeader.SetAdditionalRecordCount(aDnsHeader.GetAdditionalRecordCount() - 1);
+    sha256.Update(aDnsHeader);
+    sha256.Update(aMessage, offset + sizeof(aDnsHeader), aSigOffset - offset - sizeof(aDnsHeader));
+
+    sha256.Finish(hash);
+
+    signatureOffset = aSigRdataOffset + aSigRdataLength - Crypto::Ecdsa::P256::Signature::kSize;
+    SuccessOrExit(error = aMessage.Read(signatureOffset, signature));
+
+    error = aKey.GetKey().Verify(hash, signature);
+
+exit:
+    FreeMessage(signerNameMessage);
+    return error;
+}
+
+void Server::HandleUpdate(const Dns::UpdateHeader &aDnsHeader, Host *aHost, const Ip6::MessageInfo &aMessageInfo)
+{
+    Error error = kErrorNone;
+
+    if (aHost->GetLease() == 0)
+    {
+        Host *existingHost = mHosts.FindMatching(aHost->GetFullName());
+
+        aHost->ClearResources();
+
+        // The client may not include all services it has registered and we should append
+        // those services for current SRP update.
+        if (existingHost != nullptr)
+        {
+            Service *existingService = nullptr;
+
+            while ((existingService = existingHost->GetNextService(existingService)) != nullptr)
+            {
+                if (!existingService->mIsDeleted)
+                {
+                    Service *service = aHost->AddService(existingService->mFullName);
+                    VerifyOrExit(service != nullptr, error = kErrorNoBufs);
+                    service->mIsDeleted = true;
+                }
+            }
+        }
+    }
+
+exit:
+    if (error != kErrorNone)
+    {
+        CommitSrpUpdate(error, aDnsHeader, *aHost, aMessageInfo);
+    }
+    else if (mServiceUpdateHandler != nullptr)
+    {
+        UpdateMetadata *update = UpdateMetadata::New(GetInstance(), aDnsHeader, aHost, aMessageInfo);
+
+        IgnoreError(mOutstandingUpdates.Add(*update));
+        mOutstandingUpdatesTimer.StartAt(mOutstandingUpdates.GetTail()->GetExpireTime(), 0);
+
+        mServiceUpdateHandler(aHost, kDefaultEventsHandlerTimeout, mServiceUpdateHandlerContext);
+    }
+    else
+    {
+        CommitSrpUpdate(kErrorNone, aDnsHeader, *aHost, aMessageInfo);
+    }
+}
+
+void Server::SendResponse(const Dns::UpdateHeader &   aHeader,
+                          Dns::UpdateHeader::Response aResponseCode,
+                          const Ip6::MessageInfo &    aMessageInfo)
+{
+    Error             error;
+    Message *         response = nullptr;
+    Dns::UpdateHeader header;
+
+    response = mSocket.NewMessage(0);
+    VerifyOrExit(response != nullptr, error = kErrorNoBufs);
+
+    header.SetMessageId(aHeader.GetMessageId());
+    header.SetType(Dns::UpdateHeader::kTypeResponse);
+    header.SetQueryType(aHeader.GetQueryType());
+    header.SetResponseCode(aResponseCode);
+    SuccessOrExit(error = response->Append(header));
+
+    SuccessOrExit(error = mSocket.SendTo(*response, aMessageInfo));
+
+    if (aResponseCode != Dns::UpdateHeader::kResponseSuccess)
+    {
+        otLogInfoSrp("[server] send fail response: %d", aResponseCode);
+    }
+    else
+    {
+        otLogInfoSrp("[server] send success response");
+    }
+
+exit:
+    if (error != kErrorNone)
+    {
+        otLogWarnSrp("[server] failed to send response: %s", ErrorToString(error));
+        FreeMessage(response);
+    }
+}
+
+void Server::SendResponse(const Dns::UpdateHeader &aHeader,
+                          uint32_t                 aLease,
+                          uint32_t                 aKeyLease,
+                          const Ip6::MessageInfo & aMessageInfo)
+{
+    Error             error;
+    Message *         response = nullptr;
+    Dns::UpdateHeader header;
+    Dns::OptRecord    optRecord;
+    Dns::LeaseOption  leaseOption;
+
+    response = mSocket.NewMessage(0);
+    VerifyOrExit(response != nullptr, error = kErrorNoBufs);
+
+    header.SetMessageId(aHeader.GetMessageId());
+    header.SetType(Dns::UpdateHeader::kTypeResponse);
+    header.SetQueryType(aHeader.GetQueryType());
+    header.SetResponseCode(Dns::UpdateHeader::kResponseSuccess);
+    header.SetAdditionalRecordCount(1);
+    SuccessOrExit(error = response->Append(header));
+
+    // Append the root domain (".").
+    SuccessOrExit(error = Dns::Name::AppendTerminator(*response));
+
+    optRecord.Init();
+    optRecord.SetUdpPayloadSize(kUdpPayloadSize);
+    optRecord.SetDnsSecurityFlag();
+    optRecord.SetLength(sizeof(Dns::LeaseOption));
+    SuccessOrExit(error = response->Append(optRecord));
+
+    leaseOption.Init();
+    leaseOption.SetLeaseInterval(aLease);
+    leaseOption.SetKeyLeaseInterval(aKeyLease);
+    SuccessOrExit(error = response->Append(leaseOption));
+
+    SuccessOrExit(error = mSocket.SendTo(*response, aMessageInfo));
+
+    otLogInfoSrp("[server] send response with granted lease: %u and key lease: %u", aLease, aKeyLease);
+
+exit:
+    if (error != kErrorNone)
+    {
+        otLogWarnSrp("[server] failed to send response: %s", ErrorToString(error));
+        FreeMessage(response);
+    }
+}
+
+void Server::HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
+{
+    static_cast<Server *>(aContext)->HandleUdpReceive(*static_cast<Message *>(aMessage),
+                                                      *static_cast<const Ip6::MessageInfo *>(aMessageInfo));
+}
+
+void Server::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
+{
+    Error             error;
+    Dns::UpdateHeader dnsHeader;
+    uint16_t          offset = aMessage.GetOffset();
+
+    SuccessOrExit(error = aMessage.Read(offset, dnsHeader));
+    offset += sizeof(dnsHeader);
+
+    // Handles only queries.
+    VerifyOrExit(dnsHeader.GetType() == Dns::UpdateHeader::Type::kTypeQuery, error = kErrorDrop);
+
+    switch (dnsHeader.GetQueryType())
+    {
+    case Dns::UpdateHeader::kQueryTypeUpdate:
+        HandleDnsUpdate(aMessage, aMessageInfo, dnsHeader, offset);
+        break;
+    default:
+        error = kErrorDrop;
+        break;
+    }
+
+exit:
+    if (error != kErrorNone)
+    {
+        otLogInfoSrp("[server] failed to handle DNS message: %s", ErrorToString(error));
+    }
+}
+
+void Server::HandleLeaseTimer(Timer &aTimer)
+{
+    aTimer.Get<Server>().HandleLeaseTimer();
+}
+
+void Server::HandleLeaseTimer(void)
+{
+    TimeMilli now                = TimerMilli::GetNow();
+    TimeMilli earliestExpireTime = now.GetDistantFuture();
+    Host *    host               = mHosts.GetHead();
+
+    while (host != nullptr)
+    {
+        Host *nextHost = host->GetNext();
+
+        if (host->GetKeyExpireTime() <= now)
+        {
+            otLogInfoSrp("[server] KEY LEASE of host %s expired", host->GetFullName());
+
+            // Removes the whole host and all services if the KEY RR expired.
+            RemoveHost(host, /* aRetainName */ false, /* aNotifyServiceHandler */ true);
+        }
+        else if (host->IsDeleted())
+        {
+            // The host has been deleted, but the hostname & service instance names retain.
+
+            Service *service;
+
+            earliestExpireTime = OT_MIN(earliestExpireTime, host->GetKeyExpireTime());
+
+            // Check if any service instance name expired.
+            service = host->GetNextService(nullptr);
+            while (service != nullptr)
+            {
+                OT_ASSERT(service->mIsDeleted);
+
+                Service *nextService = service->GetNext();
+
+                if (service->GetKeyExpireTime() <= now)
+                {
+                    otLogInfoSrp("[server] KEY LEASE of service %s expired", service->mFullName);
+                    host->RemoveService(service, /* aRetainName */ false, /* aNotifyServiceHandler */ true);
+                }
+                else
+                {
+                    earliestExpireTime = OT_MIN(earliestExpireTime, service->GetKeyExpireTime());
+                }
+
+                service = nextService;
+            }
+        }
+        else if (host->GetExpireTime() <= now)
+        {
+            Service *service = nullptr;
+
+            otLogInfoSrp("[server] LEASE of host %s expired", host->GetFullName());
+
+            // If the host expired, delete all resources of this host and its services.
+            RemoveHost(host, /* aRetainName */ true, /* aNotifyServiceHandler */ true);
+            while ((service = host->GetNextService(service)) != nullptr)
+            {
+                host->RemoveService(service, /* aRetainName */ true, /* aNotifyServiceHandler */ true);
+            }
+
+            earliestExpireTime = OT_MIN(earliestExpireTime, host->GetKeyExpireTime());
+        }
+        else
+        {
+            // The host doesn't expire, check if any service expired or is explicitly removed.
+
+            OT_ASSERT(!host->IsDeleted());
+
+            Service *service = host->GetNextService(nullptr);
+
+            earliestExpireTime = OT_MIN(earliestExpireTime, host->GetExpireTime());
+
+            while (service != nullptr)
+            {
+                Service *nextService = service->GetNext();
+
+                if (service->mIsDeleted)
+                {
+                    // The service has been deleted but the name retains.
+                    earliestExpireTime = OT_MIN(earliestExpireTime, service->GetKeyExpireTime());
+                }
+                else if (service->GetExpireTime() <= now)
+                {
+                    otLogInfoSrp("[server] LEASE of service %s expired", service->mFullName);
+
+                    // The service is expired, delete it.
+                    host->RemoveService(service, /* aRetainName */ true, /* aNotifyServiceHandler */ true);
+                    earliestExpireTime = OT_MIN(earliestExpireTime, service->GetKeyExpireTime());
+                }
+                else
+                {
+                    earliestExpireTime = OT_MIN(earliestExpireTime, service->GetExpireTime());
+                }
+
+                service = nextService;
+            }
+        }
+
+        host = nextHost;
+    }
+
+    if (earliestExpireTime != now.GetDistantFuture())
+    {
+        if (!mLeaseTimer.IsRunning() || earliestExpireTime <= mLeaseTimer.GetFireTime())
+        {
+            otLogInfoSrp("[server] lease timer is scheduled for %u seconds", Time::MsecToSec(earliestExpireTime - now));
+            mLeaseTimer.StartAt(earliestExpireTime, 0);
+        }
+    }
+    else
+    {
+        otLogInfoSrp("[server] lease timer is stopped");
+        mLeaseTimer.Stop();
+    }
+}
+
+void Server::HandleOutstandingUpdatesTimer(Timer &aTimer)
+{
+    aTimer.Get<Server>().HandleOutstandingUpdatesTimer();
+}
+
+void Server::HandleOutstandingUpdatesTimer(void)
+{
+    otLogInfoSrp("[server] outstanding service update timeout");
+    while (!mOutstandingUpdates.IsEmpty() && mOutstandingUpdates.GetTail()->GetExpireTime() <= TimerMilli::GetNow())
+    {
+        HandleServiceUpdateResult(mOutstandingUpdates.GetTail(), kErrorResponseTimeout);
+    }
+}
+
+Server::Service *Server::Service::New(const char *aFullName)
+{
+    void *   buf;
+    Service *service = nullptr;
+
+    buf = Instance::HeapCAlloc(1, sizeof(Service));
+    VerifyOrExit(buf != nullptr);
+
+    service = new (buf) Service();
+    if (service->SetFullName(aFullName) != kErrorNone)
+    {
+        service->Free();
+        service = nullptr;
+    }
+
+exit:
+    return service;
+}
+
+void Server::Service::Free(void)
+{
+    Instance::HeapFree(mFullName);
+    Instance::HeapFree(mTxtData);
+    Instance::HeapFree(this);
+}
+
+Server::Service::Service(void)
+    : mFullName(nullptr)
+    , mPriority(0)
+    , mWeight(0)
+    , mPort(0)
+    , mTxtLength(0)
+    , mTxtData(nullptr)
+    , mHost(nullptr)
+    , mNext(nullptr)
+    , mTimeLastUpdate(TimerMilli::GetNow())
+{
+}
+
+Error Server::Service::SetFullName(const char *aFullName)
+{
+    OT_ASSERT(aFullName != nullptr);
+
+    Error error    = kErrorNone;
+    char *nameCopy = static_cast<char *>(Instance::HeapCAlloc(1, strlen(aFullName) + 1));
+
+    VerifyOrExit(nameCopy != nullptr, error = kErrorNoBufs);
+    strcpy(nameCopy, aFullName);
+
+    Instance::HeapFree(mFullName);
+    mFullName = nameCopy;
+
+exit:
+    return error;
+}
+
+TimeMilli Server::Service::GetExpireTime(void) const
+{
+    OT_ASSERT(!mIsDeleted);
+    OT_ASSERT(!GetHost().IsDeleted());
+
+    return mTimeLastUpdate + Time::SecToMsec(GetHost().GetLease());
+}
+
+TimeMilli Server::Service::GetKeyExpireTime(void) const
+{
+    return mTimeLastUpdate + Time::SecToMsec(GetHost().GetKeyLease());
+}
+
+Error Server::Service::SetTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength)
+{
+    Error    error = kErrorNone;
+    uint8_t *txtData;
+
+    txtData = static_cast<uint8_t *>(Instance::HeapCAlloc(1, aTxtDataLength));
+    VerifyOrExit(txtData != nullptr, error = kErrorNoBufs);
+
+    memcpy(txtData, aTxtData, aTxtDataLength);
+
+    Instance::HeapFree(mTxtData);
+    mTxtData   = txtData;
+    mTxtLength = aTxtDataLength;
+
+    // If a TXT RR is associated to this service, the service will retain.
+    mIsDeleted = false;
+
+exit:
+    return error;
+}
+
+Error Server::Service::SetTxtDataFromMessage(const Message &aMessage, uint16_t aOffset, uint16_t aLength)
+{
+    Error    error = kErrorNone;
+    uint8_t *txtData;
+
+    txtData = static_cast<uint8_t *>(Instance::HeapCAlloc(1, aLength));
+    VerifyOrExit(txtData != nullptr, error = kErrorNoBufs);
+    VerifyOrExit(aMessage.ReadBytes(aOffset, txtData, aLength) == aLength, error = kErrorParse);
+    VerifyOrExit(Dns::TxtRecord::VerifyTxtData(txtData, aLength), error = kErrorParse);
+
+    Instance::HeapFree(mTxtData);
+    mTxtData   = txtData;
+    mTxtLength = aLength;
+
+    mIsDeleted = false;
+
+exit:
+    if (error != kErrorNone)
+    {
+        Instance::HeapFree(txtData);
+    }
+
+    return error;
+}
+
+void Server::Service::ClearResources(void)
+{
+    mPort = 0;
+    Instance::HeapFree(mTxtData);
+    mTxtData   = nullptr;
+    mTxtLength = 0;
+}
+
+Error Server::Service::CopyResourcesFrom(const Service &aService)
+{
+    Error error;
+
+    SuccessOrExit(error = SetTxtData(aService.mTxtData, aService.mTxtLength));
+    mPriority = aService.mPriority;
+    mWeight   = aService.mWeight;
+    mPort     = aService.mPort;
+
+    mIsDeleted      = false;
+    mTimeLastUpdate = TimerMilli::GetNow();
+
+exit:
+    return error;
+}
+
+bool Server::Service::Matches(const char *aFullName) const
+{
+    return (mFullName != nullptr) && (strcmp(mFullName, aFullName) == 0);
+}
+
+bool Server::Service::MatchesServiceName(const char *aServiceName) const
+{
+    uint8_t i = static_cast<uint8_t>(strlen(mFullName));
+    uint8_t j = static_cast<uint8_t>(strlen(aServiceName));
+
+    while (i > 0 && j > 0 && mFullName[i - 1] == aServiceName[j - 1])
+    {
+        i--;
+        j--;
+    }
+
+    return j == 0 && i > 0 && mFullName[i - 1] == '.';
+}
+
+Server::Host *Server::Host::New(Instance &aInstance)
+{
+    void *buf;
+    Host *host = nullptr;
+
+    buf = Instance::HeapCAlloc(1, sizeof(Host));
+    VerifyOrExit(buf != nullptr);
+
+    host = new (buf) Host(aInstance);
+
+exit:
+    return host;
+}
+
+void Server::Host::Free(void)
+{
+    FreeAllServices();
+    Instance::HeapFree(mFullName);
+    Instance::HeapFree(this);
+}
+
+Server::Host::Host(Instance &aInstance)
+    : InstanceLocator(aInstance)
+    , mFullName(nullptr)
+    , mAddressesNum(0)
+    , mNext(nullptr)
+    , mLease(0)
+    , mKeyLease(0)
+    , mTimeLastUpdate(TimerMilli::GetNow())
+{
+    mKey.Clear();
+}
+
+Error Server::Host::SetFullName(const char *aFullName)
+{
+    OT_ASSERT(aFullName != nullptr);
+
+    Error error    = kErrorNone;
+    char *nameCopy = static_cast<char *>(Instance::HeapCAlloc(1, strlen(aFullName) + 1));
+
+    VerifyOrExit(nameCopy != nullptr, error = kErrorNoBufs);
+    strcpy(nameCopy, aFullName);
+
+    if (mFullName != nullptr)
+    {
+        Instance::HeapFree(mFullName);
+    }
+    mFullName = nameCopy;
+
+exit:
+    return error;
+}
+
+void Server::Host::SetKey(Dns::Ecdsa256KeyRecord &aKey)
+{
+    OT_ASSERT(aKey.IsValid());
+
+    mKey = aKey;
+}
+
+void Server::Host::SetLease(uint32_t aLease)
+{
+    mLease = aLease;
+}
+
+void Server::Host::SetKeyLease(uint32_t aKeyLease)
+{
+    mKeyLease = aKeyLease;
+}
+
+TimeMilli Server::Host::GetExpireTime(void) const
+{
+    OT_ASSERT(!IsDeleted());
+
+    return mTimeLastUpdate + Time::SecToMsec(mLease);
+}
+
+TimeMilli Server::Host::GetKeyExpireTime(void) const
+{
+    return mTimeLastUpdate + Time::SecToMsec(mKeyLease);
+}
+
+// Add a new service entry to the host, do nothing if there is already
+// such services with the same name.
+Server::Service *Server::Host::AddService(const char *aFullName)
+{
+    Service *service = FindService(aFullName);
+
+    VerifyOrExit(service == nullptr);
+
+    service = Service::New(aFullName);
+    if (service != nullptr)
+    {
+        IgnoreError(mServices.Add(*service));
+        service->mHost = this;
+    }
+
+exit:
+    return service;
+}
+
+void Server::Host::RemoveService(Service *aService, bool aRetainName, bool aNotifyServiceHandler)
+{
+    const Server &server = Get<Server>();
+
+    VerifyOrExit(aService != nullptr);
+
+    aService->mIsDeleted = true;
+
+    if (aRetainName)
+    {
+        aService->ClearResources();
+        otLogInfoSrp("[server] remove service '%s' (but retain its name)", aService->mFullName);
+    }
+    else
+    {
+        otLogInfoSrp("[server] fully remove service '%s'", aService->mFullName);
+    }
+
+    IgnoreError(mServices.Remove(*aService));
+
+    if (aNotifyServiceHandler && server.mServiceUpdateHandler != nullptr)
+    {
+        LinkedList<Service> remainingServices = mServices;
+
+        mServices.Clear();
+        IgnoreError(mServices.Add(*aService));
+
+        server.mServiceUpdateHandler(this, kDefaultEventsHandlerTimeout, server.mServiceUpdateHandlerContext);
+        // We don't wait for the reply from the service update handler,
+        // but always remove the service regardless of service update result.
+        // Because removing a service should fail only when there is system
+        // failure of the platform mDNS implementation and in which case the
+        // service is not expected to be still registered.
+
+        mServices = remainingServices;
+    }
+
+    if (aRetainName)
+    {
+        IgnoreError(mServices.Add(*aService));
+    }
+    else
+    {
+        aService->Free();
+    }
+
+exit:
+    return;
+}
+
+void Server::Host::FreeAllServices(void)
+{
+    while (!mServices.IsEmpty())
+    {
+        RemoveService(mServices.GetHead(), /* aRetainName */ false, /* aNotifyServiceHandler */ false);
+    }
+}
+
+void Server::Host::ClearResources(void)
+{
+    mAddressesNum = 0;
+}
+
+void Server::Host::CopyResourcesFrom(const Host &aHost)
+{
+    memcpy(mAddresses, aHost.mAddresses, aHost.mAddressesNum * sizeof(mAddresses[0]));
+    mAddressesNum = aHost.mAddressesNum;
+    mKey          = aHost.mKey;
+    mLease        = aHost.mLease;
+    mKeyLease     = aHost.mKeyLease;
+
+    mTimeLastUpdate = TimerMilli::GetNow();
+}
+
+Server::Service *Server::Host::FindService(const char *aFullName)
+{
+    return mServices.FindMatching(aFullName);
+}
+
+const Server::Service *Server::Host::FindService(const char *aFullName) const
+{
+    return const_cast<Host *>(this)->FindService(aFullName);
+}
+
+Error Server::Host::AddIp6Address(const Ip6::Address &aIp6Address)
+{
+    Error error = kErrorNone;
+
+    if (aIp6Address.IsMulticast() || aIp6Address.IsUnspecified() || aIp6Address.IsLoopback())
+    {
+        // We don't like those address because they cannot be used
+        // for communication with exterior devices.
+        ExitNow(error = kErrorDrop);
+    }
+
+    for (const Ip6::Address &addr : mAddresses)
+    {
+        if (aIp6Address == addr)
+        {
+            // Drop duplicate addresses.
+            ExitNow(error = kErrorDrop);
+        }
+    }
+
+    if (mAddressesNum >= kMaxAddressesNum)
+    {
+        otLogWarnSrp("[server] too many addresses for host %s", GetFullName());
+        ExitNow(error = kErrorNoBufs);
+    }
+
+    mAddresses[mAddressesNum++] = aIp6Address;
+
+exit:
+    return error;
+}
+
+bool Server::Host::Matches(const char *aName) const
+{
+    return mFullName != nullptr && strcmp(mFullName, aName) == 0;
+}
+
+Server::UpdateMetadata *Server::UpdateMetadata::New(Instance &               aInstance,
+                                                    const Dns::UpdateHeader &aHeader,
+                                                    Host *                   aHost,
+                                                    const Ip6::MessageInfo & aMessageInfo)
+{
+    void *          buf;
+    UpdateMetadata *update = nullptr;
+
+    buf = aInstance.HeapCAlloc(1, sizeof(UpdateMetadata));
+    VerifyOrExit(buf != nullptr);
+
+    update = new (buf) UpdateMetadata(aInstance, aHeader, aHost, aMessageInfo);
+
+exit:
+    return update;
+}
+
+void Server::UpdateMetadata::Free(void)
+{
+    Instance::HeapFree(this);
+}
+
+Server::UpdateMetadata::UpdateMetadata(Instance &               aInstance,
+                                       const Dns::UpdateHeader &aHeader,
+                                       Host *                   aHost,
+                                       const Ip6::MessageInfo & aMessageInfo)
+    : InstanceLocator(aInstance)
+    , mExpireTime(TimerMilli::GetNow() + kDefaultEventsHandlerTimeout)
+    , mDnsHeader(aHeader)
+    , mHost(aHost)
+    , mMessageInfo(aMessageInfo)
+    , mNext(nullptr)
+{
+}
+
+} // namespace Srp
+} // namespace ot
+
+#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
diff --git a/src/core/net/srp_server.hpp b/src/core/net/srp_server.hpp
new file mode 100644
index 0000000..6efb04d
--- /dev/null
+++ b/src/core/net/srp_server.hpp
@@ -0,0 +1,648 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file includes definitions for SRP server.
+ */
+
+#ifndef NET_SRP_SERVER_HPP_
+#define NET_SRP_SERVER_HPP_
+
+#include "openthread-core-config.h"
+
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+
+#if !OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
+#error "OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE is required for OPENTHREAD_CONFIG_SRP_SERVER_ENABLE"
+#endif
+
+#if !OPENTHREAD_CONFIG_ECDSA_ENABLE
+#error "OPENTHREAD_CONFIG_ECDSA_ENABLE is required for OPENTHREAD_CONFIG_SRP_SERVER_ENABLE"
+#endif
+
+#include <openthread/ip6.h>
+#include <openthread/srp_server.h>
+
+#include "common/clearable.hpp"
+#include "common/linked_list.hpp"
+#include "common/locator.hpp"
+#include "common/non_copyable.hpp"
+#include "common/notifier.hpp"
+#include "common/timer.hpp"
+#include "crypto/ecdsa.hpp"
+#include "net/dns_types.hpp"
+#include "net/ip6.hpp"
+#include "net/ip6_address.hpp"
+#include "net/udp6.hpp"
+
+namespace ot {
+namespace Srp {
+
+/**
+ * This class implements the SRP server.
+ *
+ */
+class Server : public InstanceLocator, private NonCopyable
+{
+    friend class ot::Notifier;
+
+public:
+    enum : uint16_t
+    {
+        kUdpPort = OPENTHREAD_CONFIG_SRP_SERVER_UDP_PORT, ///< The SRP Server UDP listening port.
+    };
+
+    class Host;
+    class Service;
+
+    /**
+     * This class implements a server-side SRP service.
+     *
+     */
+    class Service : public LinkedListEntry<Service>, private NonCopyable
+    {
+        friend class LinkedListEntry<Service>;
+        friend class Server;
+
+    public:
+        /**
+         * This method creates a new Service object with given full name.
+         *
+         * @param[in]  aFullName  The full name of the service instance.
+         *
+         * @returns  A pointer to the newly created Service object, nullptr if
+         *           cannot allocate memory for the object.
+         *
+         */
+        static Service *New(const char *aFullName);
+
+        /**
+         * This method frees the Service object.
+         *
+         */
+        void Free(void);
+
+        /**
+         * This method tells if the SRP service has been deleted.
+         *
+         * A SRP service can be deleted but retains its name for future uses.
+         * In this case, the service instance is not removed from the SRP server/registry.
+         * It is guaranteed that all services are deleted if the host is deleted.
+         *
+         * @returns  TRUE if the service has been deleted, FALSE if not.
+         *
+         */
+        bool IsDeleted(void) const { return mIsDeleted; }
+
+        /**
+         * This method returns the full name of the service.
+         *
+         * @returns  A pointer to the null-terminated service name string.
+         *
+         */
+        const char *GetFullName(void) const { return mFullName; }
+
+        /**
+         * This method returns the port of the service instance.
+         *
+         * @returns  The port of the service.
+         *
+         */
+        uint16_t GetPort(void) const { return mPort; }
+
+        /**
+         * This method returns the weight of the service instance.
+         *
+         * @returns  The weight of the service.
+         *
+         */
+        uint16_t GetWeight(void) const { return mWeight; }
+
+        /**
+         * This method returns the priority of the service instance.
+         *
+         * @param[in]  aService  A pointer to the SRP service.
+         *
+         * @returns  The priority of the service.
+         *
+         */
+        uint16_t GetPriority(void) const { return mPriority; }
+
+        /**
+         * This method returns the TXT record data of the service instance.
+         *
+         * @returns A pointer to the buffer containing the TXT record data.
+         *
+         */
+        const uint8_t *GetTxtData(void) const { return mTxtData; }
+
+        /**
+         * This method returns the TXT recored data length of the service instance.
+         *
+         * @return The TXT record data length (number of bytes in buffer returned from `GetTxtData()`).
+         *
+         */
+        uint16_t GetTxtDataLength(void) const { return mTxtLength; }
+
+        /**
+         * This method returns the host which the service instance reside on.
+         *
+         * @returns  A reference to the host instance.
+         *
+         */
+        const Host &GetHost(void) const { return *static_cast<const Host *>(mHost); }
+
+        /**
+         * This method returns the expire time (in milliseconds) of the service.
+         *
+         * @returns  The service expire time in milliseconds.
+         *
+         */
+        TimeMilli GetExpireTime(void) const;
+
+        /**
+         * This method returns the key expire time (in milliseconds) of the service.
+         *
+         * @returns  The service key expire time in milliseconds.
+         *
+         */
+        TimeMilli GetKeyExpireTime(void) const;
+
+        /**
+         * This method tells whether this service matches a given full name.
+         *
+         * @param[in]  aFullName  The full name.
+         *
+         * @returns  TRUE if the servce matches the full name, FALSE if doesn't match.
+         *
+         */
+        bool Matches(const char *aFullName) const;
+
+        /**
+         * This method tells whether this service matches a given service name <Service>.<Domain>.
+         *
+         * @param[in] aServiceName  The full service name to match.
+         *
+         * @retval  TRUE   If the service matches the full service name.
+         * @retval  FALSE  If the service does not match the full service name.
+         *
+         */
+        bool MatchesServiceName(const char *aServiceName) const;
+
+    private:
+        explicit Service(void);
+        Error SetFullName(const char *aFullName);
+        Error SetTxtData(const uint8_t *aTxtData, uint16_t aTxtDataLength);
+        Error SetTxtDataFromMessage(const Message &aMessage, uint16_t aOffset, uint16_t aLength);
+        Error CopyResourcesFrom(const Service &aService);
+        void  ClearResources(void);
+
+        char *           mFullName;
+        uint16_t         mPriority;
+        uint16_t         mWeight;
+        uint16_t         mPort;
+        uint16_t         mTxtLength;
+        uint8_t *        mTxtData;
+        otSrpServerHost *mHost;
+        Service *        mNext;
+        TimeMilli        mTimeLastUpdate;
+        bool             mIsDeleted;
+    };
+
+    /**
+     * This class implements the Host which registers services on the SRP server.
+     *
+     */
+    class Host : public LinkedListEntry<Host>, public InstanceLocator, private NonCopyable
+    {
+        friend class LinkedListEntry<Host>;
+        friend class Server;
+
+    public:
+        /**
+         * This method creates a new Host object.
+         *
+         * @param[in]  aInstance  A reference to the OpenThread instance.
+         *
+         * @returns  A pointer to the newly created Host object, nullptr if
+         *           cannot allocate memory for the object.
+         *
+         */
+        static Host *New(Instance &aInstance);
+
+        /**
+         * This method Frees the Host object.
+         *
+         */
+        void Free(void);
+
+        /**
+         * This method tells whether the Host object has been deleted.
+         *
+         * The Host object retains event if the host has been deleted by the SRP client,
+         * because the host name may retain.
+         *
+         * @returns  TRUE if the host is deleted, FALSE if the host is not deleted.
+         *
+         */
+        bool IsDeleted(void) const { return (mLease == 0); }
+
+        /**
+         * This method returns the full name of the host.
+         *
+         * @returns  A pointer to the null-terminated full host name.
+         *
+         */
+        const char *GetFullName(void) const { return mFullName; }
+
+        /**
+         * This method returns adrersses of the host.
+         *
+         * @param[out]  aAddressesNum  The number of the addresses.
+         *
+         * @returns  A pointer to the addresses array.
+         *
+         */
+        const Ip6::Address *GetAddresses(uint8_t &aAddressesNum) const
+        {
+            aAddressesNum = mAddressesNum;
+            return mAddresses;
+        }
+
+        /**
+         * This method returns the LEASE time of the host.
+         *
+         * @returns  The LEASE time in seconds.
+         *
+         */
+        uint32_t GetLease(void) const { return mLease; }
+
+        /**
+         * This method returns the KEY-LEASE time of the key of the host.
+         *
+         * @returns  The KEY-LEASE time in seconds.
+         *
+         */
+        uint32_t GetKeyLease(void) const { return mKeyLease; }
+
+        /**
+         * This method returns the KEY resource of the host.
+         *
+         * @returns  A pointer to the ECDSA P 256 public key if there is valid one.
+         *           nullptr if no valid key exists.
+         *
+         */
+        const Dns::Ecdsa256KeyRecord *GetKey(void) const { return mKey.IsValid() ? &mKey : nullptr; }
+
+        /**
+         * This method returns the expire time (in milliseconds) of the host.
+         *
+         * @returns  The expire time in milliseconds.
+         *
+         */
+        TimeMilli GetExpireTime(void) const;
+
+        /**
+         * This method returns the expire time (in milliseconds) of the key of the host.
+         *
+         * @returns  The expire time of the key in milliseconds.
+         *
+         */
+        TimeMilli GetKeyExpireTime(void) const;
+
+        /**
+         * This method returns the next service of the host.
+         *
+         * @param[in]  aService  A pointer to current service.
+         *
+         * @returns  A pointer to the next service or NULL if no more services exist.
+         *
+         */
+        const Service *GetNextService(const Service *aService) const
+        {
+            return aService ? aService->GetNext() : mServices.GetHead();
+        }
+
+        /**
+         * This method tells whether the host matches a given full name.
+         *
+         * @param[in]  aFullName  The full name.
+         *
+         * @returns  A boolean that indicates whether the host matches the given name.
+         *
+         */
+        bool Matches(const char *aName) const;
+
+    private:
+        enum : uint8_t
+        {
+            kMaxAddressesNum = OPENTHREAD_CONFIG_SRP_SERVER_MAX_ADDRESSES_NUM,
+        };
+
+        explicit Host(Instance &aInstance);
+        Error    SetFullName(const char *aFullName);
+        void     SetKey(Dns::Ecdsa256KeyRecord &aKey);
+        void     SetLease(uint32_t aLease);
+        void     SetKeyLease(uint32_t aKeyLease);
+        Service *GetNextService(Service *aService) { return aService ? aService->GetNext() : mServices.GetHead(); }
+        Service *AddService(const char *aFullName);
+        void     RemoveService(Service *aService, bool aRetainName, bool aNotifyServiceHandler);
+        void     FreeAllServices(void);
+        void     ClearResources(void);
+        void     CopyResourcesFrom(const Host &aHost);
+        Service *FindService(const char *aFullName);
+        const Service *FindService(const char *aFullName) const;
+        Error          AddIp6Address(const Ip6::Address &aIp6Address);
+
+        char *       mFullName;
+        Ip6::Address mAddresses[kMaxAddressesNum];
+        uint8_t      mAddressesNum;
+        Host *       mNext;
+
+        Dns::Ecdsa256KeyRecord mKey;
+        uint32_t               mLease;    // The LEASE time in seconds.
+        uint32_t               mKeyLease; // The KEY-LEASE time in seconds.
+        TimeMilli              mTimeLastUpdate;
+        LinkedList<Service>    mServices;
+    };
+
+    /**
+     * This constructor initializes the SRP server object.
+     *
+     * @param[in]  aInstance  A reference to the OpenThread instance.
+     *
+     */
+    explicit Server(Instance &aInstance);
+    ~Server(void);
+
+    /**
+     * This method sets the SRP service events handler.
+     *
+     * @param[in]  aServiceHandler         A service events handler.
+     * @param[in]  aServiceHandlerContext  A pointer to arbitrary context information.
+     *
+     * @note  The handler SHOULD call HandleServiceUpdateResult to report the result of its processing.
+     *        Otherwise, a SRP update will be considered failed.
+     *
+     * @sa  HandleServiceUpdateResult
+     *
+     */
+    void SetServiceHandler(otSrpServerServiceUpdateHandler aServiceHandler, void *aServiceHandlerContext);
+
+    /**
+     * This method returns the domain authorized to the SRP server.
+     *
+     * If the domain if not set by SetDomain, "default.service.arpa." will be returned.
+     * A trailing dot is always appended even if the domain is set without it.
+     *
+     * @returns A pointer to the dot-joined domain string.
+     *
+     */
+    const char *GetDomain(void) const;
+
+    /**
+     * This method sets the domain on the SRP server.
+     *
+     * A trailing dot will be appended to @p aDomain if it is not already there.
+     * This method should only be called before the SRP server is enabled.
+     *
+     * @param[in]  aDomain  The domain to be set. MUST NOT be nullptr.
+     *
+     * @retval  kErrorNone          Successfully set the domain to @p aDomain.
+     * @retval  kErrorInvalidState  The SRP server is already enabled and the Domain cannot be changed.
+     * @retval  kErrorInvalidArgs   The argument @p aDomain is not a valid DNS domain name.
+     * @retval  kErrorNoBufs        There is no memory to store content of @p aDomain.
+     *
+     */
+    Error SetDomain(const char *aDomain);
+
+    /**
+     * This method tells whether the SRP server is currently running.
+     *
+     * @returns  A boolean that indicates whether the server is running.
+     *
+     */
+    bool IsRunning(void) const;
+
+    /**
+     * This method enables/disables the SRP server.
+     *
+     * @param[in]  aEnabled  A boolean to enable/disable the SRP server.
+     *
+     */
+    void SetEnabled(bool aEnabled);
+
+    /**
+     * This method sets LEASE & KEY-LEASE range that is acceptable by the SRP server.
+     *
+     * When a LEASE time is requested from a client, the granted value will be
+     * limited in range [aMinLease, aMaxLease]; and a KEY-LEASE will be granted
+     * in range [aMinKeyLease, aMaxKeyLease].
+     *
+     * @param[in]  aMinLease     The minimum LEASE interval in seconds.
+     * @param[in]  aMaxLease     The maximum LEASE interval in seconds.
+     * @param[in]  aMinKeyLease  The minimum KEY-LEASE interval in seconds.
+     * @param[in]  aMaxKeyLease  The maximum KEY-LEASE interval in seconds.
+     *
+     * @retval  kErrorNone         Successfully set the LEASE and KEY-LEASE ranges.
+     * @retval  kErrorInvalidArgs  The LEASE or KEY-LEASE range is not valid.
+     *
+     */
+    Error SetLeaseRange(uint32_t aMinLease, uint32_t aMaxLease, uint32_t aMinKeyLease, uint32_t aMaxKeyLease);
+
+    /**
+     * This method returns the next registered SRP host.
+     *
+     * @param[in]  aHost  The current SRP host; use nullptr to get the first SRP host.
+     *
+     * @returns  A pointer to the next SRP host or nullptr if no more SRP hosts can be found.
+     *
+     */
+    const Host *GetNextHost(const Host *aHost);
+
+    /**
+     * This method receives the service update result from service handler set by
+     * SetServiceHandler.
+     *
+     * @param[in]  aHost   A pointer to the Host object which contains the SRP service updates.
+     * @param[in]  aError  The service update result.
+     *
+     */
+    void HandleServiceUpdateResult(const Host *aHost, Error aError);
+
+private:
+    enum : uint16_t
+    {
+        kUdpPayloadSize = Ip6::Ip6::kMaxDatagramLength - sizeof(Ip6::Udp::Header), // Max UDP payload size
+    };
+
+    enum : uint32_t
+    {
+        kDefaultMinLease             = 60u * 30,        // Default minimum lease time, 30 min (in seconds).
+        kDefaultMaxLease             = 3600u * 2,       // Default maximum lease time, 2 hours (in seconds).
+        kDefaultMinKeyLease          = 3600u * 24,      // Default minimum key-lease time, 1 day (in seconds).
+        kDefaultMaxKeyLease          = 3600u * 24 * 14, // Default maximum key-lease time, 14 days (in seconds).
+        kDefaultEventsHandlerTimeout = OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_UPDATE_TIMEOUT,
+    };
+
+    /**
+     * This class includes metadata for processing a SRP update (register, deregister)
+     * and sending DNS response to the client.
+     *
+     */
+    class UpdateMetadata : public InstanceLocator, public LinkedListEntry<UpdateMetadata>
+    {
+        friend class LinkedListEntry<UpdateMetadata>;
+
+    public:
+        static UpdateMetadata *  New(Instance &               aInstance,
+                                     const Dns::UpdateHeader &aHeader,
+                                     Host *                   aHost,
+                                     const Ip6::MessageInfo & aMessageInfo);
+        void                     Free(void);
+        TimeMilli                GetExpireTime(void) const { return mExpireTime; }
+        const Dns::UpdateHeader &GetDnsHeader(void) const { return mDnsHeader; }
+        Host &                   GetHost(void) { return *mHost; }
+        const Ip6::MessageInfo & GetMessageInfo(void) const { return mMessageInfo; }
+        bool                     Matches(const Host *aHost) const { return mHost == aHost; }
+
+    private:
+        UpdateMetadata(Instance &               aInstance,
+                       const Dns::UpdateHeader &aHeader,
+                       Host *                   aHost,
+                       const Ip6::MessageInfo & aMessageInfo);
+
+        TimeMilli         mExpireTime;
+        Dns::UpdateHeader mDnsHeader;
+        Host *            mHost;        // The host will be updated. The UpdateMetadata has no ownership of this host.
+        Ip6::MessageInfo  mMessageInfo; // The message info of the DNS update request.
+        UpdateMetadata *  mNext;
+    };
+
+    void     Start(void);
+    void     Stop(void);
+    void     HandleNotifierEvents(Events aEvents);
+    Error    PublishServerData(void);
+    void     UnpublishServerData(void);
+    uint32_t GrantLease(uint32_t aLease) const;
+    uint32_t GrantKeyLease(uint32_t aKeyLease) const;
+
+    void  CommitSrpUpdate(Error                    aError,
+                          const Dns::UpdateHeader &aDnsHeader,
+                          Host &                   aHost,
+                          const Ip6::MessageInfo & aMessageInfo);
+    void  HandleDnsUpdate(Message &                aMessage,
+                          const Ip6::MessageInfo & aMessageInfo,
+                          const Dns::UpdateHeader &aDnsHeader,
+                          uint16_t                 aOffset);
+    Error ProcessUpdateSection(Host &                   aHost,
+                               const Message &          aMessage,
+                               const Dns::UpdateHeader &aDnsHeader,
+                               const Dns::Zone &        aZone,
+                               uint16_t &               aOffset) const;
+    Error ProcessAdditionalSection(Host *                   aHost,
+                                   const Message &          aMessage,
+                                   const Dns::UpdateHeader &aDnsHeader,
+                                   uint16_t &               aOffset) const;
+    Error VerifySignature(const Dns::Ecdsa256KeyRecord &aKey,
+                          const Message &               aMessage,
+                          Dns::UpdateHeader             aDnsHeader,
+                          uint16_t                      aSigOffset,
+                          uint16_t                      aSigRdataOffset,
+                          uint16_t                      aSigRdataLength,
+                          const char *                  aSignerName) const;
+    Error ProcessZoneSection(const Message &          aMessage,
+                             const Dns::UpdateHeader &aDnsHeader,
+                             uint16_t &               aOffset,
+                             Dns::Zone &              aZone) const;
+    Error ProcessHostDescriptionInstruction(Host &                   aHost,
+                                            const Message &          aMessage,
+                                            const Dns::UpdateHeader &aDnsHeader,
+                                            const Dns::Zone &        aZone,
+                                            uint16_t                 aOffset) const;
+    Error ProcessServiceDiscoveryInstructions(Host &                   aHost,
+                                              const Message &          aMessage,
+                                              const Dns::UpdateHeader &aDnsHeader,
+                                              const Dns::Zone &        aZone,
+                                              uint16_t                 aOffset) const;
+    Error ProcessServiceDescriptionInstructions(Host &                   aHost,
+                                                const Message &          aMessage,
+                                                const Dns::UpdateHeader &aDnsHeader,
+                                                const Dns::Zone &        aZone,
+                                                uint16_t &               aOffset) const;
+
+    static bool    IsValidDeleteAllRecord(const Dns::ResourceRecord &aRecord);
+    const Service *FindService(const char *aFullName) const;
+
+    void        HandleUpdate(const Dns::UpdateHeader &aDnsHeader, Host *aHost, const Ip6::MessageInfo &aMessageInfo);
+    void        AddHost(Host *aHost);
+    void        RemoveHost(Host *aHost, bool aRetainName, bool aNotifyServiceHandler);
+    bool        HasNameConflictsWith(Host &aHost) const;
+    void        SendResponse(const Dns::UpdateHeader &   aHeader,
+                             Dns::UpdateHeader::Response aResponseCode,
+                             const Ip6::MessageInfo &    aMessageInfo);
+    void        SendResponse(const Dns::UpdateHeader &aHeader,
+                             uint32_t                 aLease,
+                             uint32_t                 aKeyLease,
+                             const Ip6::MessageInfo & aMessageInfo);
+    static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
+    void        HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    static void HandleLeaseTimer(Timer &aTimer);
+    void        HandleLeaseTimer(void);
+    static void HandleOutstandingUpdatesTimer(Timer &aTimer);
+    void        HandleOutstandingUpdatesTimer(void);
+
+    void                  HandleServiceUpdateResult(UpdateMetadata *aUpdate, Error aError);
+    const UpdateMetadata *FindOutstandingUpdate(const Ip6::MessageInfo &aMessageInfo, uint16_t aDnsMessageId);
+
+    Ip6::Udp::Socket                mSocket;
+    otSrpServerServiceUpdateHandler mServiceUpdateHandler;
+    void *                          mServiceUpdateHandlerContext;
+
+    char *mDomain;
+
+    uint32_t mMinLease;    // The minimum lease time in seconds.
+    uint32_t mMaxLease;    // The maximum lease time in seconds.
+    uint32_t mMinKeyLease; // The minimum key-lease time in seconds.
+    uint32_t mMaxKeyLease; // The maximum key-lease time in seconds.
+
+    LinkedList<Host> mHosts;
+    TimerMilli       mLeaseTimer;
+
+    TimerMilli                 mOutstandingUpdatesTimer;
+    LinkedList<UpdateMetadata> mOutstandingUpdates;
+
+    bool mEnabled;
+};
+
+} // namespace Srp
+} // namespace ot
+
+#endif // OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+#endif // NET_SRP_SERVER_HPP_
diff --git a/src/core/net/udp6.cpp b/src/core/net/udp6.cpp
index af2f962..a015041 100644
--- a/src/core/net/udp6.cpp
+++ b/src/core/net/udp6.cpp
@@ -82,70 +82,50 @@
     return Get<Udp>().NewMessage(aReserved, aSettings);
 }
 
-otError Udp::Socket::Open(otUdpReceive aHandler, void *aContext)
+Error Udp::Socket::Open(otUdpReceive aHandler, void *aContext)
 {
     return Get<Udp>().Open(*this, aHandler, aContext);
 }
 
-otError Udp::Socket::Bind(const SockAddr &aSockAddr)
+Error Udp::Socket::Bind(const SockAddr &aSockAddr, otNetifIdentifier aNetifIdentifier)
 {
-    return Get<Udp>().Bind(*this, aSockAddr);
+    return Get<Udp>().Bind(*this, aSockAddr, aNetifIdentifier);
 }
 
-otError Udp::Socket::Bind(uint16_t aPort)
+Error Udp::Socket::Bind(uint16_t aPort, otNetifIdentifier aNetifIdentifier)
 {
-    return Bind(SockAddr(aPort));
+    return Bind(SockAddr(aPort), aNetifIdentifier);
 }
 
-otError Udp::Socket::BindToNetif(otNetifIdentifier aNetifIdentifier)
-{
-    OT_UNUSED_VARIABLE(aNetifIdentifier);
-
-    otError error = OT_ERROR_NONE;
-
-#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
-    SuccessOrExit(error = otPlatUdpBindToNetif(this, aNetifIdentifier));
-#endif
-
-#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
-    Get<Udp>().BindToNetif(*this, aNetifIdentifier);
-#endif
-
-#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
-exit:
-#endif
-    return error;
-}
-
-otError Udp::Socket::Connect(const SockAddr &aSockAddr)
+Error Udp::Socket::Connect(const SockAddr &aSockAddr)
 {
     return Get<Udp>().Connect(*this, aSockAddr);
 }
 
-otError Udp::Socket::Connect(uint16_t aPort)
+Error Udp::Socket::Connect(uint16_t aPort)
 {
     return Connect(SockAddr(aPort));
 }
 
-otError Udp::Socket::Close(void)
+Error Udp::Socket::Close(void)
 {
     return Get<Udp>().Close(*this);
 }
 
-otError Udp::Socket::SendTo(Message &aMessage, const MessageInfo &aMessageInfo)
+Error Udp::Socket::SendTo(Message &aMessage, const MessageInfo &aMessageInfo)
 {
     return Get<Udp>().SendTo(*this, aMessage, aMessageInfo);
 }
 
 #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
-otError Udp::Socket::JoinNetifMulticastGroup(otNetifIdentifier aNetifIdentifier, const Address &aAddress)
+Error Udp::Socket::JoinNetifMulticastGroup(otNetifIdentifier aNetifIdentifier, const Address &aAddress)
 {
     OT_UNUSED_VARIABLE(aNetifIdentifier);
     OT_UNUSED_VARIABLE(aAddress);
 
-    otError error = OT_ERROR_NOT_IMPLEMENTED;
+    Error error = kErrorNotImplemented;
 
-    VerifyOrExit(aAddress.IsMulticast(), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aAddress.IsMulticast(), error = kErrorInvalidArgs);
 
 #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
     error = otPlatUdpJoinMulticastGroup(this, aNetifIdentifier, &aAddress);
@@ -155,14 +135,14 @@
     return error;
 }
 
-otError Udp::Socket::LeaveNetifMulticastGroup(otNetifIdentifier aNetifIdentifier, const Address &aAddress)
+Error Udp::Socket::LeaveNetifMulticastGroup(otNetifIdentifier aNetifIdentifier, const Address &aAddress)
 {
     OT_UNUSED_VARIABLE(aNetifIdentifier);
     OT_UNUSED_VARIABLE(aAddress);
 
-    otError error = OT_ERROR_NOT_IMPLEMENTED;
+    Error error = kErrorNotImplemented;
 
-    VerifyOrExit(aAddress.IsMulticast(), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aAddress.IsMulticast(), error = kErrorInvalidArgs);
 
 #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
     error = otPlatUdpLeaveMulticastGroup(this, aNetifIdentifier, &aAddress);
@@ -186,14 +166,14 @@
 {
 }
 
-otError Udp::AddReceiver(Receiver &aReceiver)
+Error Udp::AddReceiver(Receiver &aReceiver)
 {
     return mReceivers.Add(aReceiver);
 }
 
-otError Udp::RemoveReceiver(Receiver &aReceiver)
+Error Udp::RemoveReceiver(Receiver &aReceiver)
 {
-    otError error;
+    Error error;
 
     SuccessOrExit(error = mReceivers.Remove(aReceiver));
     aReceiver.SetNext(nullptr);
@@ -202,9 +182,9 @@
     return error;
 }
 
-otError Udp::Open(SocketHandle &aSocket, otUdpReceive aHandler, void *aContext)
+Error Udp::Open(SocketHandle &aSocket, otUdpReceive aHandler, void *aContext)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     aSocket.GetSockName().Clear();
     aSocket.GetPeerName().Clear();
@@ -222,12 +202,25 @@
     return error;
 }
 
-otError Udp::Bind(SocketHandle &aSocket, const SockAddr &aSockAddr)
+Error Udp::Bind(SocketHandle &aSocket, const SockAddr &aSockAddr, otNetifIdentifier aNetifIdentifier)
 {
-    otError error = OT_ERROR_NONE;
+    OT_UNUSED_VARIABLE(aNetifIdentifier);
+
+    Error error = kErrorNone;
+
+#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
+    SuccessOrExit(error = otPlatUdpBindToNetif(&aSocket, aNetifIdentifier));
+#endif
+
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
+    if (aNetifIdentifier == OT_NETIF_BACKBONE)
+    {
+        SetBackboneSocket(aSocket);
+    }
+#endif
 
     VerifyOrExit(aSockAddr.GetAddress().IsUnspecified() || Get<ThreadNetif>().HasUnicastAddress(aSockAddr.GetAddress()),
-                 error = OT_ERROR_INVALID_ARGS);
+                 error = kErrorInvalidArgs);
 
     aSocket.mSockName = aSockAddr;
 
@@ -239,7 +232,7 @@
 #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
             error = otPlatUdpBind(&aSocket);
 #endif
-        } while (error != OT_ERROR_NONE);
+        } while (error != kErrorNone);
     }
 #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
     else if (ShouldUsePlatformUdp(aSocket))
@@ -253,14 +246,6 @@
 }
 
 #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
-void Udp::BindToNetif(SocketHandle &aSocket, otNetifIdentifier aNetifIdentifier)
-{
-    if (aNetifIdentifier == OT_NETIF_BACKBONE)
-    {
-        SetBackboneSocket(aSocket);
-    }
-}
-
 void Udp::SetBackboneSocket(SocketHandle &aSocket)
 {
     RemoveSocket(aSocket);
@@ -297,15 +282,15 @@
 }
 #endif
 
-otError Udp::Connect(SocketHandle &aSocket, const SockAddr &aSockAddr)
+Error Udp::Connect(SocketHandle &aSocket, const SockAddr &aSockAddr)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     aSocket.mPeerName = aSockAddr;
 
     if (!aSocket.IsBound())
     {
-        SuccessOrExit(error = Bind(aSocket, aSocket.GetSockName()));
+        SuccessOrExit(error = Bind(aSocket, aSocket.GetSockName(), OT_NETIF_THREAD));
     }
 
 #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
@@ -319,9 +304,9 @@
     return error;
 }
 
-otError Udp::Close(SocketHandle &aSocket)
+Error Udp::Close(SocketHandle &aSocket)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
 #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
     error = otPlatUdpClose(&aSocket);
@@ -336,26 +321,26 @@
     return error;
 }
 
-otError Udp::SendTo(SocketHandle &aSocket, Message &aMessage, const MessageInfo &aMessageInfo)
+Error Udp::SendTo(SocketHandle &aSocket, Message &aMessage, const MessageInfo &aMessageInfo)
 {
-    otError     error = OT_ERROR_NONE;
+    Error       error = kErrorNone;
     MessageInfo messageInfoLocal;
 
     VerifyOrExit((aMessageInfo.GetSockPort() == 0) || (aSocket.GetSockName().mPort == aMessageInfo.GetSockPort()),
-                 error = OT_ERROR_INVALID_ARGS);
+                 error = kErrorInvalidArgs);
 
     messageInfoLocal = aMessageInfo;
 
     if (messageInfoLocal.GetPeerAddr().IsUnspecified())
     {
-        VerifyOrExit(!aSocket.GetPeerName().GetAddress().IsUnspecified(), error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit(!aSocket.GetPeerName().GetAddress().IsUnspecified(), error = kErrorInvalidArgs);
 
         messageInfoLocal.SetPeerAddr(aSocket.GetPeerName().GetAddress());
     }
 
     if (messageInfoLocal.mPeerPort == 0)
     {
-        VerifyOrExit(aSocket.GetPeerName().mPort != 0, error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit(aSocket.GetPeerName().mPort != 0, error = kErrorInvalidArgs);
         messageInfoLocal.mPeerPort = aSocket.GetPeerName().mPort;
     }
 
@@ -366,7 +351,7 @@
 
     if (!aSocket.IsBound())
     {
-        SuccessOrExit(error = Bind(aSocket, aSocket.GetSockName()));
+        SuccessOrExit(error = Bind(aSocket, aSocket.GetSockName(), OT_NETIF_THREAD));
     }
 
     messageInfoLocal.SetSockPort(aSocket.GetSockName().mPort);
@@ -374,15 +359,6 @@
 #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
     if (ShouldUsePlatformUdp(aSocket))
     {
-        // Replace anycast address with a valid unicast address since response messages typically copy the peer address
-        if (Get<Mle::Mle>().IsAnycastLocator(messageInfoLocal.GetSockAddr()))
-        {
-            const NetifUnicastAddress *netifAddr = Get<Ip6>().SelectSourceAddress(messageInfoLocal);
-
-            VerifyOrExit(netifAddr != nullptr, error = OT_ERROR_INVALID_ARGS);
-            messageInfoLocal.SetSockAddr(netifAddr->GetAddress());
-        }
-
         SuccessOrExit(error = otPlatUdpSend(&aSocket, &aMessage, &messageInfoLocal));
     }
     else
@@ -450,14 +426,14 @@
     return Get<Ip6>().NewMessage(sizeof(Header) + aReserved, aSettings);
 }
 
-otError Udp::SendDatagram(Message &aMessage, MessageInfo &aMessageInfo, uint8_t aIpProto)
+Error Udp::SendDatagram(Message &aMessage, MessageInfo &aMessageInfo, uint8_t aIpProto)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
 #if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE
     if (aMessageInfo.IsHostInterface())
     {
-        VerifyOrExit(mUdpForwarder != nullptr, error = OT_ERROR_NO_ROUTE);
+        VerifyOrExit(mUdpForwarder != nullptr, error = kErrorNoRoute);
         mUdpForwarder(&aMessage, aMessageInfo.mPeerPort, &aMessageInfo.GetPeerAddr(), aMessageInfo.mSockPort,
                       mUdpForwarderContext);
         // message is consumed by the callback
@@ -482,10 +458,10 @@
     return error;
 }
 
-otError Udp::HandleMessage(Message &aMessage, MessageInfo &aMessageInfo)
+Error Udp::HandleMessage(Message &aMessage, MessageInfo &aMessageInfo)
 {
-    otError error = OT_ERROR_NONE;
-    Header  udpHeader;
+    Error  error = kErrorNone;
+    Header udpHeader;
 
     SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), udpHeader));
 
@@ -548,7 +524,6 @@
     return;
 }
 
-#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
 bool Udp::ShouldUsePlatformUdp(uint16_t aPort) const
 {
     return (aPort != Mle::kUdpPort && aPort != Tmf::kUdpPort
@@ -558,6 +533,7 @@
     );
 }
 
+#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
 bool Udp::ShouldUsePlatformUdp(const Udp::SocketHandle &aSocket) const
 {
     return (ShouldUsePlatformUdp(aSocket.mSockName.mPort)
@@ -566,7 +542,7 @@
 #endif
     );
 }
-#endif
+#endif // OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
 
 } // namespace Ip6
 } // namespace ot
diff --git a/src/core/net/udp6.hpp b/src/core/net/udp6.hpp
index cd201ae..1992cb4 100644
--- a/src/core/net/udp6.hpp
+++ b/src/core/net/udp6.hpp
@@ -60,6 +60,10 @@
  *
  */
 
+#if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE && OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE
+#error "OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE and OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE must not both be set."
+#endif
+
 /**
  * This class implements core UDP message handling.
  *
@@ -161,94 +165,85 @@
          * @param[in]  aHandler  A pointer to a function that is called when receiving UDP messages.
          * @param[in]  aContext  A pointer to arbitrary context information.
          *
-         * @retval OT_ERROR_NONE     Successfully opened the socket.
-         * @retval OT_ERROR_FAILED   Failed to open the socket.
+         * @retval kErrorNone     Successfully opened the socket.
+         * @retval kErrorFailed   Failed to open the socket.
          *
          */
-        otError Open(otUdpReceive aHandler, void *aContext);
+        Error Open(otUdpReceive aHandler, void *aContext);
 
         /**
          * This method binds the UDP socket.
          *
-         * @param[in]  aSockAddr    A reference to the socket address.
-         *
-         * @retval OT_ERROR_NONE            Successfully bound the socket.
-         * @retval OT_ERROR_INVALID_ARGS    Unable to bind to Thread network interface with the given address.
-         * @retval OT_ERROR_FAILED          Failed to bind UDP Socket.
-         *
-         */
-        otError Bind(const SockAddr &aSockAddr);
-
-        /**
-         * This method binds the UDP socket to a specified network interface.
-         *
+         * @param[in]  aSockAddr            A reference to the socket address.
          * @param[in]  aNetifIdentifier     The network interface identifier.
          *
-         * @retval OT_ERROR_NONE    Successfully bound to the network interface.
-         * @retval OT_ERROR_FAILED  Failed to bind to the network interface.
+         * @retval kErrorNone            Successfully bound the socket.
+         * @retval kErrorInvalidArgs     Unable to bind to Thread network interface with the given address.
+         * @retval kErrorFailed          Failed to bind UDP Socket.
          *
          */
-        otError BindToNetif(otNetifIdentifier aNetifIdentifier);
+        Error Bind(const SockAddr &aSockAddr, otNetifIdentifier aNetifIdentifier = OT_NETIF_THREAD);
 
         /**
          * This method binds the UDP socket.
          *
-         * @param[in]  aPort        A port number.
+         * @param[in]  aPort                A port number.
+         * @param[in]  aNetifIdentifier     The network interface identifier.
          *
-         * @retval OT_ERROR_NONE            Successfully bound the socket.
-         * @retval OT_ERROR_FAILED          Failed to bind UDP Socket.
+         * @retval kErrorNone            Successfully bound the socket.
+         * @retval kErrorFailed          Failed to bind UDP Socket.
          *
          */
-        otError Bind(uint16_t aPort);
+        Error Bind(uint16_t aPort, otNetifIdentifier aNetifIdentifier = OT_NETIF_THREAD);
 
         /**
          * This method binds the UDP socket.
          *
-         * @retval OT_ERROR_NONE    Successfully bound the socket.
-         * @retval OT_ERROR_FAILED  Failed to bind UDP Socket.
+         * @retval kErrorNone    Successfully bound the socket.
+         * @retval kErrorFailed  Failed to bind UDP Socket.
          *
          */
-        otError Bind(void) { return Bind(0); }
+        Error Bind(void) { return Bind(0); }
 
         /**
          * This method connects the UDP socket.
          *
          * @param[in]  aSockAddr  A reference to the socket address.
          *
-         * @retval OT_ERROR_NONE    Successfully connected the socket.
-         * @retval OT_ERROR_FAILED  Failed to connect UDP Socket.
+         * @retval kErrorNone    Successfully connected the socket.
+         * @retval kErrorFailed  Failed to connect UDP Socket.
          *
          */
-        otError Connect(const SockAddr &aSockAddr);
+        Error Connect(const SockAddr &aSockAddr);
 
         /**
          * This method connects the UDP socket.
          *
          * @param[in]  aPort        A port number.
          *
-         * @retval OT_ERROR_NONE    Successfully connected the socket.
-         * @retval OT_ERROR_FAILED  Failed to connect UDP Socket.
+         * @retval kErrorNone    Successfully connected the socket.
+         * @retval kErrorFailed  Failed to connect UDP Socket.
          *
          */
-        otError Connect(uint16_t aPort);
+        Error Connect(uint16_t aPort);
 
         /**
          * This method connects the UDP socket.
          *
-         * @retval OT_ERROR_NONE    Successfully connected the socket.
-         * @retval OT_ERROR_FAILED  Failed to connect UDP Socket.
+         * @retval kErrorNone    Successfully connected the socket.
+         * @retval kErrorFailed  Failed to connect UDP Socket.
          *
          */
-        otError Connect(void) { return Connect(0); }
+        Error Connect(void) { return Connect(0); }
 
         /**
          * This method closes the UDP socket.
          *
-         * @retval OT_ERROR_NONE    Successfully closed the UDP socket.
-         * @retval OT_ERROR_FAILED  Failed to close UDP Socket.
+         * @retval kErrorNone    Successfully closed the UDP socket.
+         * @retval kErrorFailed  Failed to close UDP Socket.
          *
          */
-        otError Close(void);
+        Error Close(void);
 
         /**
          * This method sends a UDP message.
@@ -256,12 +251,12 @@
          * @param[in]  aMessage      The message to send.
          * @param[in]  aMessageInfo  The message info associated with @p aMessage.
          *
-         * @retval OT_ERROR_NONE          Successfully sent the UDP message.
-         * @retval OT_ERROR_INVALID_ARGS  If no peer is specified in @p aMessageInfo or by Connect().
-         * @retval OT_ERROR_NO_BUFS       Insufficient available buffer to add the UDP and IPv6 headers.
+         * @retval kErrorNone         Successfully sent the UDP message.
+         * @retval kErrorInvalidArgs  If no peer is specified in @p aMessageInfo or by Connect().
+         * @retval kErrorNoBufs       Insufficient available buffer to add the UDP and IPv6 headers.
          *
          */
-        otError SendTo(Message &aMessage, const MessageInfo &aMessageInfo);
+        Error SendTo(Message &aMessage, const MessageInfo &aMessageInfo);
 
 #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
         /**
@@ -270,11 +265,11 @@
          * @param[in]  aNetifIdentifier     The network interface identifier.
          * @param[in]  aAddress             The multicast group address.
          *
-         * @retval  OT_ERROR_NONE   Successfully joined the multicast group.
-         * @retval  OT_ERROR_FAILED Failed to join the multicast group.
+         * @retval  kErrorNone    Successfully joined the multicast group.
+         * @retval  kErrorFailed  Failed to join the multicast group.
          *
          */
-        otError JoinNetifMulticastGroup(otNetifIdentifier aNetifIdentifier, const Address &aAddress);
+        Error JoinNetifMulticastGroup(otNetifIdentifier aNetifIdentifier, const Address &aAddress);
 
         /**
          * This method configures the UDP socket to leave a multicast group on a Host network interface.
@@ -282,11 +277,11 @@
          * @param[in]  aNetifIdentifier     The network interface identifier.
          * @param[in]  aAddress             The multicast group address.
          *
-         * @retval  OT_ERROR_NONE   Successfully left the multicast group.
-         * @retval  OT_ERROR_FAILED Failed to leave the multicast group.
+         * @retval  kErrorNone   Successfully left the multicast group.
+         * @retval  kErrorFailed Failed to leave the multicast group.
          *
          */
-        otError LeaveNetifMulticastGroup(otNetifIdentifier aNetifIdentifier, const Address &aAddress);
+        Error LeaveNetifMulticastGroup(otNetifIdentifier aNetifIdentifier, const Address &aAddress);
 #endif
     };
 
@@ -421,22 +416,22 @@
      *
      * @param[in]  aReceiver  A reference to the UDP receiver.
      *
-     * @retval OT_ERROR_NONE    Successfully added the UDP receiver.
-     * @retval OT_ERROR_ALREADY The UDP receiver was already added.
+     * @retval kErrorNone    Successfully added the UDP receiver.
+     * @retval kErrorAlready The UDP receiver was already added.
      *
      */
-    otError AddReceiver(Receiver &aReceiver);
+    Error AddReceiver(Receiver &aReceiver);
 
     /**
      * This method removes a UDP receiver.
      *
      * @param[in]  aReceiver  A reference to the UDP receiver.
      *
-     * @retval OT_ERROR_NONE        Successfully removed the UDP receiver.
-     * @retval OT_ERROR_NOT_FOUND   The UDP receiver was not added.
+     * @retval kErrorNone       Successfully removed the UDP receiver.
+     * @retval kErrorNotFound   The UDP receiver was not added.
      *
      */
-    otError RemoveReceiver(Receiver &aReceiver);
+    Error RemoveReceiver(Receiver &aReceiver);
 
     /**
      * This method opens a UDP socket.
@@ -445,33 +440,25 @@
      * @param[in]  aHandler  A pointer to a function that is called when receiving UDP messages.
      * @param[in]  aContext  A pointer to arbitrary context information.
      *
-     * @retval OT_ERROR_NONE     Successfully opened the socket.
-     * @retval OT_ERROR_FAILED   Failed to open the socket.
+     * @retval kErrorNone     Successfully opened the socket.
+     * @retval kErrorFailed   Failed to open the socket.
      *
      */
-    otError Open(SocketHandle &aSocket, otUdpReceive aHandler, void *aContext);
+    Error Open(SocketHandle &aSocket, otUdpReceive aHandler, void *aContext);
 
     /**
      * This method binds a UDP socket.
      *
      * @param[in]  aSocket          A reference to the socket.
      * @param[in]  aSockAddr        A reference to the socket address.
+     * @param[in]  aNetifIdentifier The network interface identifier.
      *
-     * @retval OT_ERROR_NONE            Successfully bound the socket.
-     * @retval OT_ERROR_INVALID_ARGS    Unable to bind to Thread network interface with the given address.
-     * @retval OT_ERROR_FAILED          Failed to bind UDP Socket.
+     * @retval kErrorNone            Successfully bound the socket.
+     * @retval kErrorInvalidArgs     Unable to bind to Thread network interface with the given address.
+     * @retval kErrorFailed          Failed to bind UDP Socket.
      *
      */
-    otError Bind(SocketHandle &aSocket, const SockAddr &aSockAddr);
-
-    /**
-     * This method binds a UDP socket to the Network interface.
-     *
-     * @param[in]  aSocket           A reference to the socket.
-     * @param[in]  aNetifIdentifier  The network interface identifier.
-     *
-     */
-    void BindToNetif(SocketHandle &aSocket, otNetifIdentifier aNetifIdentifier);
+    Error Bind(SocketHandle &aSocket, const SockAddr &aSockAddr, otNetifIdentifier aNetifIdentifier);
 
     /**
      * This method connects a UDP socket.
@@ -479,22 +466,22 @@
      * @param[in]  aSocket    A reference to the socket.
      * @param[in]  aSockAddr  A reference to the socket address.
      *
-     * @retval OT_ERROR_NONE    Successfully connected the socket.
-     * @retval OT_ERROR_FAILED  Failed to connect UDP Socket.
+     * @retval kErrorNone    Successfully connected the socket.
+     * @retval kErrorFailed  Failed to connect UDP Socket.
      *
      */
-    otError Connect(SocketHandle &aSocket, const SockAddr &aSockAddr);
+    Error Connect(SocketHandle &aSocket, const SockAddr &aSockAddr);
 
     /**
      * This method closes the UDP socket.
      *
      * @param[in]  aSocket    A reference to the socket.
      *
-     * @retval OT_ERROR_NONE    Successfully closed the UDP socket.
-     * @retval OT_ERROR_FAILED  Failed to close UDP Socket.
+     * @retval kErrorNone    Successfully closed the UDP socket.
+     * @retval kErrorFailed  Failed to close UDP Socket.
      *
      */
-    otError Close(SocketHandle &aSocket);
+    Error Close(SocketHandle &aSocket);
 
     /**
      * This method sends a UDP message using a socket.
@@ -503,12 +490,12 @@
      * @param[in]  aMessage      The message to send.
      * @param[in]  aMessageInfo  The message info associated with @p aMessage.
      *
-     * @retval OT_ERROR_NONE          Successfully sent the UDP message.
-     * @retval OT_ERROR_INVALID_ARGS  If no peer is specified in @p aMessageInfo or by Connect().
-     * @retval OT_ERROR_NO_BUFS       Insufficient available buffer to add the UDP and IPv6 headers.
+     * @retval kErrorNone         Successfully sent the UDP message.
+     * @retval kErrorInvalidArgs  If no peer is specified in @p aMessageInfo or by Connect().
+     * @retval kErrorNoBufs       Insufficient available buffer to add the UDP and IPv6 headers.
      *
      */
-    otError SendTo(SocketHandle &aSocket, Message &aMessage, const MessageInfo &aMessageInfo);
+    Error SendTo(SocketHandle &aSocket, Message &aMessage, const MessageInfo &aMessageInfo);
 
     /**
      * This method returns a new ephemeral port.
@@ -536,11 +523,11 @@
      * @param[in]  aMessageInfo  A reference to the message info associated with @p aMessage.
      * @param[in]  aIpProto      The Internet Protocol value.
      *
-     * @retval OT_ERROR_NONE     Successfully enqueued the message into an output interface.
-     * @retval OT_ERROR_NO_BUFS  Insufficient available buffer to add the IPv6 headers.
+     * @retval kErrorNone    Successfully enqueued the message into an output interface.
+     * @retval kErrorNoBufs  Insufficient available buffer to add the IPv6 headers.
      *
      */
-    otError SendDatagram(Message &aMessage, MessageInfo &aMessageInfo, uint8_t aIpProto);
+    Error SendDatagram(Message &aMessage, MessageInfo &aMessageInfo, uint8_t aIpProto);
 
     /**
      * This method handles a received UDP message.
@@ -548,11 +535,11 @@
      * @param[in]  aMessage      A reference to the UDP message to process.
      * @param[in]  aMessageInfo  A reference to the message info associated with @p aMessage.
      *
-     * @retval OT_ERROR_NONE  Successfully processed the UDP message.
-     * @retval OT_ERROR_DROP  Could not fully process the UDP message.
+     * @retval kErrorNone  Successfully processed the UDP message.
+     * @retval kErrorDrop  Could not fully process the UDP message.
      *
      */
-    otError HandleMessage(Message &aMessage, MessageInfo &aMessageInfo);
+    Error HandleMessage(Message &aMessage, MessageInfo &aMessageInfo);
 
     /**
      * This method handles a received UDP message with offset set to the payload.
@@ -586,6 +573,17 @@
     }
 #endif
 
+    /**
+     * This method returns whether a udp port belongs to the platform or the stack.
+     *
+     * @param[in]   aPort       The udp port
+     *
+     * @retval True when the port belongs to the platform.
+     * @retval False when the port belongs to the stack.
+     *
+     */
+    bool ShouldUsePlatformUdp(uint16_t aPort) const;
+
 private:
     enum
     {
@@ -596,7 +594,6 @@
     void AddSocket(SocketHandle &aSocket);
     void RemoveSocket(SocketHandle &aSocket);
 #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
-    bool ShouldUsePlatformUdp(uint16_t aPort) const;
     bool ShouldUsePlatformUdp(const SocketHandle &aSocket) const;
 #endif
 
diff --git a/src/core/openthread-core-config.h b/src/core/openthread-core-config.h
index 18f8245..4e86be6 100644
--- a/src/core/openthread-core-config.h
+++ b/src/core/openthread-core-config.h
@@ -47,7 +47,7 @@
 #endif
 
 #ifndef OPENTHREAD_CONFIG_THREAD_VERSION
-#define OPENTHREAD_CONFIG_THREAD_VERSION OT_THREAD_VERSION_1_1
+#define OPENTHREAD_CONFIG_THREAD_VERSION OT_THREAD_VERSION_1_2
 #endif
 
 #include "config/openthread-core-default-config.h"
@@ -65,6 +65,8 @@
 #include "config/dhcp6_server.h"
 #include "config/diag.h"
 #include "config/dns_client.h"
+#include "config/dnssd_server.h"
+#include "config/dtls.h"
 #include "config/ip6.h"
 #include "config/joiner.h"
 #include "config/link_quality.h"
@@ -73,19 +75,15 @@
 #include "config/mac.h"
 #include "config/mle.h"
 #include "config/parent_search.h"
+#include "config/ping_sender.h"
 #include "config/platform.h"
 #include "config/radio_link.h"
 #include "config/sntp_client.h"
+#include "config/srp_client.h"
+#include "config/srp_server.h"
 #include "config/time_sync.h"
 #include "config/tmf.h"
 
-#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE || OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE || \
-    OPENTHREAD_CONFIG_COMMISSIONER_ENABLE || OPENTHREAD_CONFIG_JOINER_ENABLE
-#define OPENTHREAD_CONFIG_DTLS_ENABLE 1
-#else
-#define OPENTHREAD_CONFIG_DTLS_ENABLE 0
-#endif
-
 #undef OPENTHREAD_CORE_CONFIG_H_IN
 
 #include "config/openthread-core-config-check.h"
diff --git a/src/core/radio.cmake b/src/core/radio.cmake
index 1a78f10..ffa0c7b 100644
--- a/src/core/radio.cmake
+++ b/src/core/radio.cmake
@@ -47,11 +47,13 @@
 
 target_sources(openthread-radio PRIVATE
     api/diags_api.cpp
+    api/error_api.cpp
     api/instance_api.cpp
     api/link_raw_api.cpp
     api/logging_api.cpp
     api/random_noncrypto_api.cpp
     api/tasklet_api.cpp
+    common/error.cpp
     common/instance.cpp
     common/logging.cpp
     common/random_manager.cpp
diff --git a/src/core/radio/radio.cpp b/src/core/radio/radio.cpp
index 9ac0f96..b519e54 100644
--- a/src/core/radio/radio.cpp
+++ b/src/core/radio/radio.cpp
@@ -53,7 +53,7 @@
 #endif
 }
 
-otError Radio::Transmit(Mac::TxFrame &aFrame)
+Error Radio::Transmit(Mac::TxFrame &aFrame)
 {
 #if (OPENTHREAD_MTD || OPENTHREAD_FTD) && OPENTHREAD_CONFIG_OTNS_ENABLE
     Get<Utils::Otns>().EmitTransmit(aFrame);
diff --git a/src/core/radio/radio.hpp b/src/core/radio/radio.hpp
index bdec579..3af3fb1 100644
--- a/src/core/radio/radio.hpp
+++ b/src/core/radio/radio.hpp
@@ -117,12 +117,12 @@
          * This callback method handles a "Receive Done" event from radio platform.
          *
          * @param[in]  aFrame    A pointer to the received frame or nullptr if the receive operation failed.
-         * @param[in]  aError    OT_ERROR_NONE when successfully received a frame,
-         *                       OT_ERROR_ABORT when reception was aborted and a frame was not received,
-         *                       OT_ERROR_NO_BUFS when a frame could not be received due to lack of rx buffer space.
+         * @param[in]  aError    kErrorNone when successfully received a frame,
+         *                       kErrorAbort when reception was aborted and a frame was not received,
+         *                       kErrorNoBufs when a frame could not be received due to lack of rx buffer space.
          *
          */
-        void HandleReceiveDone(Mac::RxFrame *aFrame, otError aError);
+        void HandleReceiveDone(Mac::RxFrame *aFrame, Error aError);
 
         /**
          * This callback method handles a "Transmit Started" event from radio platform.
@@ -137,13 +137,13 @@
          *
          * @param[in]  aFrame     The frame that was transmitted.
          * @param[in]  aAckFrame  A pointer to the ACK frame, nullptr if no ACK was received.
-         * @param[in]  aError     OT_ERROR_NONE when the frame was transmitted,
-         *                        OT_ERROR_NO_ACK when the frame was transmitted but no ACK was received,
-         *                        OT_ERROR_CHANNEL_ACCESS_FAILURE tx could not take place due to activity on the
-         *                        channel, OT_ERROR_ABORT when transmission was aborted for other reasons.
+         * @param[in]  aError     kErrorNone when the frame was transmitted,
+         *                        kErrorNoAck when the frame was transmitted but no ACK was received,
+         *                        kErrorChannelAccessFailure tx could not take place due to activity on the
+         *                        channel, kErrorAbort when transmission was aborted for other reasons.
          *
          */
-        void HandleTransmitDone(Mac::TxFrame &aFrame, Mac::RxFrame *aAckFrame, otError aError);
+        void HandleTransmitDone(Mac::TxFrame &aFrame, Mac::RxFrame *aAckFrame, Error aError);
 
         /**
          * This callback method handles "Energy Scan Done" event from radio platform.
@@ -162,24 +162,24 @@
          * This callback method handles a "Receive Done" event from radio platform when diagnostics mode is enabled.
          *
          * @param[in]  aFrame    A pointer to the received frame or nullptr if the receive operation failed.
-         * @param[in]  aError    OT_ERROR_NONE when successfully received a frame,
-         *                       OT_ERROR_ABORT when reception was aborted and a frame was not received,
-         *                       OT_ERROR_NO_BUFS when a frame could not be received due to lack of rx buffer space.
+         * @param[in]  aError    kErrorNone when successfully received a frame,
+         *                       kErrorAbort when reception was aborted and a frame was not received,
+         *                       kErrorNoBufs when a frame could not be received due to lack of rx buffer space.
          *
          */
-        void HandleDiagsReceiveDone(Mac::RxFrame *aFrame, otError aError);
+        void HandleDiagsReceiveDone(Mac::RxFrame *aFrame, Error aError);
 
         /**
          * This callback method handles a "Transmit Done" event from radio platform when diagnostics mode is enabled.
          *
          * @param[in]  aFrame     The frame that was transmitted.
-         * @param[in]  aError     OT_ERROR_NONE when the frame was transmitted,
-         *                        OT_ERROR_NO_ACK when the frame was transmitted but no ACK was received,
-         *                        OT_ERROR_CHANNEL_ACCESS_FAILURE tx could not take place due to activity on the
-         *                        channel, OT_ERROR_ABORT when transmission was aborted for other reasons.
+         * @param[in]  aError     kErrorNone when the frame was transmitted,
+         *                        kErrorNoAck when the frame was transmitted but no ACK was received,
+         *                        kErrorChannelAccessFailure tx could not take place due to activity on the
+         *                        channel, kErrorAbort when transmission was aborted for other reasons.
          *
          */
-        void HandleDiagsTransmitDone(Mac::TxFrame &aFrame, otError aError);
+        void HandleDiagsTransmitDone(Mac::TxFrame &aFrame, Error aError);
 #endif
 
     private:
@@ -289,44 +289,44 @@
      *
      * @param[out] aPower    A reference to output the transmit power in dBm.
      *
-     * @retval OT_ERROR_NONE             Successfully retrieved the transmit power.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  Transmit power configuration via dBm is not implemented.
+     * @retval kErrorNone             Successfully retrieved the transmit power.
+     * @retval kErrorNotImplemented   Transmit power configuration via dBm is not implemented.
      *
      */
-    otError GetTransmitPower(int8_t &aPower);
+    Error GetTransmitPower(int8_t &aPower);
 
     /**
      * This method sets the radio's transmit power in dBm.
      *
      * @param[in] aPower     The transmit power in dBm.
      *
-     * @retval OT_ERROR_NONE             Successfully set the transmit power.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  Transmit power configuration via dBm is not implemented.
+     * @retval kErrorNone             Successfully set the transmit power.
+     * @retval kErrorNotImplemented   Transmit power configuration via dBm is not implemented.
      *
      */
-    otError SetTransmitPower(int8_t aPower);
+    Error SetTransmitPower(int8_t aPower);
 
     /**
      * This method gets the radio's CCA ED threshold in dBm.
      *
      * @param[in] aThreshold    The CCA ED threshold in dBm.
      *
-     * @retval OT_ERROR_NONE             A reference to output the CCA ED threshold in dBm.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  CCA ED threshold configuration via dBm is not implemented.
+     * @retval kErrorNone             A reference to output the CCA ED threshold in dBm.
+     * @retval kErrorNotImplemented   CCA ED threshold configuration via dBm is not implemented.
      *
      */
-    otError GetCcaEnergyDetectThreshold(int8_t &aThreshold);
+    Error GetCcaEnergyDetectThreshold(int8_t &aThreshold);
 
     /**
      * This method sets the radio's CCA ED threshold in dBm.
      *
      * @param[in] aThreshold    The CCA ED threshold in dBm.
      *
-     * @retval OT_ERROR_NONE             Successfully set the CCA ED threshold.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  CCA ED threshold configuration via dBm is not implemented.
+     * @retval kErrorNone             Successfully set the CCA ED threshold.
+     * @retval kErrorNotImplemented   CCA ED threshold configuration via dBm is not implemented.
      *
      */
-    otError SetCcaEnergyDetectThreshold(int8_t aThreshold);
+    Error SetCcaEnergyDetectThreshold(int8_t aThreshold);
 
     /**
      * This method gets the status of promiscuous mode.
@@ -360,20 +360,20 @@
     /**
      * This method enables the radio.
      *
-     * @retval OT_ERROR_NONE     Successfully enabled.
-     * @retval OT_ERROR_FAILED   The radio could not be enabled.
+     * @retval kErrorNone     Successfully enabled.
+     * @retval kErrorFailed   The radio could not be enabled.
      *
      */
-    otError Enable(void);
+    Error Enable(void);
 
     /**
      * This method disables the radio.
      *
-     * @retval OT_ERROR_NONE            Successfully transitioned to Disabled.
-     * @retval OT_ERROR_INVALID_STATE   The radio was not in sleep state.
+     * @retval kErrorNone           Successfully transitioned to Disabled.
+     * @retval kErrorInvalidState   The radio was not in sleep state.
      *
      */
-    otError Disable(void);
+    Error Disable(void);
 
     /**
      * This method indicates whether radio is enabled or not.
@@ -386,23 +386,23 @@
     /**
      * This method transitions the radio from Receive to Sleep (turn off the radio).
      *
-     * @retval OT_ERROR_NONE          Successfully transitioned to Sleep.
-     * @retval OT_ERROR_BUSY          The radio was transmitting.
-     * @retval OT_ERROR_INVALID_STATE The radio was disabled.
+     * @retval kErrorNone          Successfully transitioned to Sleep.
+     * @retval kErrorBusy          The radio was transmitting.
+     * @retval kErrorInvalidState  The radio was disabled.
      *
      */
-    otError Sleep(void);
+    Error Sleep(void);
 
     /**
      * This method transitions the radio from Sleep to Receive (turn on the radio).
      *
      * @param[in]  aChannel   The channel to use for receiving.
      *
-     * @retval OT_ERROR_NONE          Successfully transitioned to Receive.
-     * @retval OT_ERROR_INVALID_STATE The radio was disabled or transmitting.
+     * @retval kErrorNone          Successfully transitioned to Receive.
+     * @retval kErrorInvalidState  The radio was disabled or transmitting.
      *
      */
-    otError Receive(uint8_t aChannel);
+    Error Receive(uint8_t aChannel);
 
 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
     /**
@@ -419,12 +419,12 @@
      * @param[in]  aExtAddr      The extended source address of CSL receiver's parent device (when the platforms
      * generate enhanced ack, platforms may need to know acks to which address should include CSL IE).
      *
-     * @retval  OT_ERROR_NOT_SUPPORTED  Radio driver doesn't support CSL.
-     * @retval  OT_ERROR_FAILED         Other platform specific errors.
-     * @retval  OT_ERROR_NONE           Successfully enabled or disabled CSL.
+     * @retval  kErrorNotImplemented Radio driver doesn't support CSL.
+     * @retval  kErrorFailed         Other platform specific errors.
+     * @retval  kErrorNone           Successfully enabled or disabled CSL.
      *
      */
-    otError EnableCsl(uint32_t aCslPeriod, const otExtAddress *aExtAddr);
+    Error EnableCsl(uint32_t aCslPeriod, const otExtAddress *aExtAddr);
 #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
 
     /**
@@ -445,11 +445,11 @@
      *
      * @param[in] aFrame     A reference to the frame to be transmitted.
      *
-     * @retval OT_ERROR_NONE          Successfully transitioned to Transmit.
-     * @retval OT_ERROR_INVALID_STATE The radio was not in the Receive state.
+     * @retval kErrorNone          Successfully transitioned to Transmit.
+     * @retval kErrorInvalidState  The radio was not in the Receive state.
      *
      */
-    otError Transmit(Mac::TxFrame &aFrame);
+    Error Transmit(Mac::TxFrame &aFrame);
 
     /**
      * This method gets the most recent RSSI measurement.
@@ -467,11 +467,11 @@
      * @param[in] aScanChannel   The channel to perform the energy scan on.
      * @param[in] aScanDuration  The duration, in milliseconds, for the channel to be scanned.
      *
-     * @retval OT_ERROR_NONE             Successfully started scanning the channel.
-     * @retval OT_ERROR_NOT_IMPLEMENTED  The radio doesn't support energy scanning.
+     * @retval kErrorNone            Successfully started scanning the channel.
+     * @retval kErrorNotImplemented  The radio doesn't support energy scanning.
      *
      */
-    otError EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration);
+    Error EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration);
 
     /**
      * This method enables/disables source address match feature.
@@ -497,44 +497,44 @@
      *
      * @param[in]  aShortAddress  The short address to be added.
      *
-     * @retval OT_ERROR_NONE      Successfully added short address to the source match table.
-     * @retval OT_ERROR_NO_BUFS   No available entry in the source match table.
+     * @retval kErrorNone     Successfully added short address to the source match table.
+     * @retval kErrorNoBufs   No available entry in the source match table.
      *
      */
-    otError AddSrcMatchShortEntry(Mac::ShortAddress aShortAddress);
+    Error AddSrcMatchShortEntry(Mac::ShortAddress aShortAddress);
 
     /**
      * This method adds an extended address to the source address match table.
      *
      * @param[in]  aExtAddress  The extended address to be added stored in little-endian byte order.
      *
-     * @retval OT_ERROR_NONE      Successfully added extended address to the source match table.
-     * @retval OT_ERROR_NO_BUFS   No available entry in the source match table.
+     * @retval kErrorNone     Successfully added extended address to the source match table.
+     * @retval kErrorNoBufs   No available entry in the source match table.
      *
      */
-    otError AddSrcMatchExtEntry(const Mac::ExtAddress &aExtAddress);
+    Error AddSrcMatchExtEntry(const Mac::ExtAddress &aExtAddress);
 
     /**
      * This method removes a short address from the source address match table.
      *
      * @param[in]  aShortAddress  The short address to be removed.
      *
-     * @retval OT_ERROR_NONE        Successfully removed short address from the source match table.
-     * @retval OT_ERROR_NO_ADDRESS  The short address is not in source address match table.
+     * @retval kErrorNone       Successfully removed short address from the source match table.
+     * @retval kErrorNoAddress  The short address is not in source address match table.
      *
      */
-    otError ClearSrcMatchShortEntry(Mac::ShortAddress aShortAddress);
+    Error ClearSrcMatchShortEntry(Mac::ShortAddress aShortAddress);
 
     /**
      * This method removes an extended address from the source address match table.
      *
      * @param[in]  aExtAddress  The extended address to be removed stored in little-endian byte order.
      *
-     * @retval OT_ERROR_NONE        Successfully removed the extended address from the source match table.
-     * @retval OT_ERROR_NO_ADDRESS  The extended address is not in source address match table.
+     * @retval kErrorNone       Successfully removed the extended address from the source match table.
+     * @retval kErrorNoAddress  The extended address is not in source address match table.
      *
      */
-    otError ClearSrcMatchExtEntry(const Mac::ExtAddress &aExtAddress);
+    Error ClearSrcMatchExtEntry(const Mac::ExtAddress &aExtAddress);
 
     /**
      * This method clears all short addresses from the source address match table.
@@ -580,15 +580,15 @@
      * @param[in]  aShortAddr   The short address of the the probing Initiator.
      * @param[in]  aExtAddr     The extended source address of the probing Initiator.
      *
-     * @retval OT_ERROR_NONE           Successfully enable/disable or update Enhanced-ACK Based Probing for a specific
-     *                                 Initiator.
-     * @retval OT_ERROR_INVALID_ARGS   @p aDataLength or @p aExtAddr is not valid.
-     * @retval OT_ERROR_NOT_SUPPORTED  Radio driver doesn't support Enhanced-ACK Probing.
+     * @retval kErrorNone            Successfully enable/disable or update Enhanced-ACK Based Probing for a specific
+     *                               Initiator.
+     * @retval kErrorInvalidArgs     @p aDataLength or @p aExtAddr is not valid.
+     * @retval kErrorNotImplemented  Radio driver doesn't support Enhanced-ACK Probing.
      *
      */
-    otError ConfigureEnhAckProbing(otLinkMetrics            aLinkMetrics,
-                                   const Mac::ShortAddress &aShortAddress,
-                                   const Mac::ExtAddress &  aExtAddress)
+    Error ConfigureEnhAckProbing(otLinkMetrics            aLinkMetrics,
+                                 const Mac::ShortAddress &aShortAddress,
+                                 const Mac::ExtAddress &  aExtAddress)
     {
         return otPlatRadioConfigureEnhAckProbing(GetInstancePtr(), aLinkMetrics, aShortAddress, &aExtAddress);
     }
@@ -665,22 +665,22 @@
     otPlatRadioSetMacKey(GetInstancePtr(), aKeyIdMode, aKeyId, &aPrevKey, &aCurrKey, &aNextKey);
 }
 
-inline otError Radio::GetTransmitPower(int8_t &aPower)
+inline Error Radio::GetTransmitPower(int8_t &aPower)
 {
     return otPlatRadioGetTransmitPower(GetInstancePtr(), &aPower);
 }
 
-inline otError Radio::SetTransmitPower(int8_t aPower)
+inline Error Radio::SetTransmitPower(int8_t aPower)
 {
     return otPlatRadioSetTransmitPower(GetInstancePtr(), aPower);
 }
 
-inline otError Radio::GetCcaEnergyDetectThreshold(int8_t &aThreshold)
+inline Error Radio::GetCcaEnergyDetectThreshold(int8_t &aThreshold)
 {
     return otPlatRadioGetCcaEnergyDetectThreshold(GetInstancePtr(), &aThreshold);
 }
 
-inline otError Radio::SetCcaEnergyDetectThreshold(int8_t aThreshold)
+inline Error Radio::SetCcaEnergyDetectThreshold(int8_t aThreshold)
 {
     return otPlatRadioSetCcaEnergyDetectThreshold(GetInstancePtr(), aThreshold);
 }
@@ -700,12 +700,12 @@
     return otPlatRadioGetState(GetInstancePtr());
 }
 
-inline otError Radio::Enable(void)
+inline Error Radio::Enable(void)
 {
     return otPlatRadioEnable(GetInstancePtr());
 }
 
-inline otError Radio::Disable(void)
+inline Error Radio::Disable(void)
 {
     return otPlatRadioDisable(GetInstancePtr());
 }
@@ -715,12 +715,12 @@
     return otPlatRadioIsEnabled(GetInstancePtr());
 }
 
-inline otError Radio::Sleep(void)
+inline Error Radio::Sleep(void)
 {
     return otPlatRadioSleep(GetInstancePtr());
 }
 
-inline otError Radio::Receive(uint8_t aChannel)
+inline Error Radio::Receive(uint8_t aChannel)
 {
     return otPlatRadioReceive(GetInstancePtr(), aChannel);
 }
@@ -731,7 +731,7 @@
     otPlatRadioUpdateCslSampleTime(GetInstancePtr(), aCslSampleTime);
 }
 
-inline otError Radio::EnableCsl(uint32_t aCslPeriod, const otExtAddress *aExtAddr)
+inline Error Radio::EnableCsl(uint32_t aCslPeriod, const otExtAddress *aExtAddr)
 {
     return otPlatRadioEnableCsl(GetInstancePtr(), aCslPeriod, aExtAddr);
 }
@@ -747,7 +747,7 @@
     return otPlatRadioGetRssi(GetInstancePtr());
 }
 
-inline otError Radio::EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration)
+inline Error Radio::EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration)
 {
     return otPlatRadioEnergyScan(GetInstancePtr(), aScanChannel, aScanDuration);
 }
@@ -757,22 +757,22 @@
     otPlatRadioEnableSrcMatch(GetInstancePtr(), aEnable);
 }
 
-inline otError Radio::AddSrcMatchShortEntry(Mac::ShortAddress aShortAddress)
+inline Error Radio::AddSrcMatchShortEntry(Mac::ShortAddress aShortAddress)
 {
     return otPlatRadioAddSrcMatchShortEntry(GetInstancePtr(), aShortAddress);
 }
 
-inline otError Radio::AddSrcMatchExtEntry(const Mac::ExtAddress &aExtAddress)
+inline Error Radio::AddSrcMatchExtEntry(const Mac::ExtAddress &aExtAddress)
 {
     return otPlatRadioAddSrcMatchExtEntry(GetInstancePtr(), &aExtAddress);
 }
 
-inline otError Radio::ClearSrcMatchShortEntry(Mac::ShortAddress aShortAddress)
+inline Error Radio::ClearSrcMatchShortEntry(Mac::ShortAddress aShortAddress)
 {
     return otPlatRadioClearSrcMatchShortEntry(GetInstancePtr(), aShortAddress);
 }
 
-inline otError Radio::ClearSrcMatchExtEntry(const Mac::ExtAddress &aExtAddress)
+inline Error Radio::ClearSrcMatchExtEntry(const Mac::ExtAddress &aExtAddress)
 {
     return otPlatRadioClearSrcMatchExtEntry(GetInstancePtr(), &aExtAddress);
 }
@@ -815,24 +815,24 @@
 {
 }
 
-inline otError Radio::GetTransmitPower(int8_t &)
+inline Error Radio::GetTransmitPower(int8_t &)
 {
-    return OT_ERROR_NOT_IMPLEMENTED;
+    return kErrorNotImplemented;
 }
 
-inline otError Radio::SetTransmitPower(int8_t)
+inline Error Radio::SetTransmitPower(int8_t)
 {
-    return OT_ERROR_NOT_IMPLEMENTED;
+    return kErrorNotImplemented;
 }
 
-inline otError Radio::GetCcaEnergyDetectThreshold(int8_t &)
+inline Error Radio::GetCcaEnergyDetectThreshold(int8_t &)
 {
-    return OT_ERROR_NOT_IMPLEMENTED;
+    return kErrorNotImplemented;
 }
 
-inline otError Radio::SetCcaEnergyDetectThreshold(int8_t)
+inline Error Radio::SetCcaEnergyDetectThreshold(int8_t)
 {
-    return OT_ERROR_NOT_IMPLEMENTED;
+    return kErrorNotImplemented;
 }
 
 inline bool Radio::GetPromiscuous(void)
@@ -849,14 +849,14 @@
     return OT_RADIO_STATE_DISABLED;
 }
 
-inline otError Radio::Enable(void)
+inline Error Radio::Enable(void)
 {
-    return OT_ERROR_NONE;
+    return kErrorNone;
 }
 
-inline otError Radio::Disable(void)
+inline Error Radio::Disable(void)
 {
-    return OT_ERROR_INVALID_STATE;
+    return kErrorInvalidState;
 }
 
 inline bool Radio::IsEnabled(void)
@@ -864,14 +864,14 @@
     return true;
 }
 
-inline otError Radio::Sleep(void)
+inline Error Radio::Sleep(void)
 {
-    return OT_ERROR_NONE;
+    return kErrorNone;
 }
 
-inline otError Radio::Receive(uint8_t)
+inline Error Radio::Receive(uint8_t)
 {
-    return OT_ERROR_NONE;
+    return kErrorNone;
 }
 
 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
@@ -879,9 +879,9 @@
 {
 }
 
-inline otError Radio::EnableCsl(uint32_t, const otExtAddress *)
+inline Error Radio::EnableCsl(uint32_t, const otExtAddress *)
 {
-    return OT_ERROR_NOT_SUPPORTED;
+    return kErrorNotImplemented;
 }
 #endif
 
@@ -890,9 +890,9 @@
     return *static_cast<Mac::TxFrame *>(otPlatRadioGetTransmitBuffer(GetInstancePtr()));
 }
 
-inline otError Radio::Transmit(Mac::TxFrame &)
+inline Error Radio::Transmit(Mac::TxFrame &)
 {
-    return OT_ERROR_ABORT;
+    return kErrorAbort;
 }
 
 inline int8_t Radio::GetRssi(void)
@@ -900,33 +900,33 @@
     return OT_RADIO_RSSI_INVALID;
 }
 
-inline otError Radio::EnergyScan(uint8_t, uint16_t)
+inline Error Radio::EnergyScan(uint8_t, uint16_t)
 {
-    return OT_ERROR_NOT_IMPLEMENTED;
+    return kErrorNotImplemented;
 }
 
 inline void Radio::EnableSrcMatch(bool)
 {
 }
 
-inline otError Radio::AddSrcMatchShortEntry(Mac::ShortAddress)
+inline Error Radio::AddSrcMatchShortEntry(Mac::ShortAddress)
 {
-    return OT_ERROR_NONE;
+    return kErrorNone;
 }
 
-inline otError Radio::AddSrcMatchExtEntry(const Mac::ExtAddress &)
+inline Error Radio::AddSrcMatchExtEntry(const Mac::ExtAddress &)
 {
-    return OT_ERROR_NONE;
+    return kErrorNone;
 }
 
-inline otError Radio::ClearSrcMatchShortEntry(Mac::ShortAddress)
+inline Error Radio::ClearSrcMatchShortEntry(Mac::ShortAddress)
 {
-    return OT_ERROR_NONE;
+    return kErrorNone;
 }
 
-inline otError Radio::ClearSrcMatchExtEntry(const Mac::ExtAddress &)
+inline Error Radio::ClearSrcMatchExtEntry(const Mac::ExtAddress &)
 {
-    return OT_ERROR_NONE;
+    return kErrorNone;
 }
 
 inline void Radio::ClearSrcMatchShortEntries(void)
diff --git a/src/core/radio/radio_callbacks.cpp b/src/core/radio/radio_callbacks.cpp
index 11d713c..55b231b 100644
--- a/src/core/radio/radio_callbacks.cpp
+++ b/src/core/radio/radio_callbacks.cpp
@@ -38,7 +38,7 @@
 
 namespace ot {
 
-void Radio::Callbacks::HandleReceiveDone(Mac::RxFrame *aFrame, otError aError)
+void Radio::Callbacks::HandleReceiveDone(Mac::RxFrame *aFrame, Error aError)
 {
     Get<Mac::SubMac>().HandleReceiveDone(aFrame, aError);
 }
@@ -48,7 +48,7 @@
     Get<Mac::SubMac>().HandleTransmitStarted(aFrame);
 }
 
-void Radio::Callbacks::HandleTransmitDone(Mac::TxFrame &aFrame, Mac::RxFrame *aAckFrame, otError aError)
+void Radio::Callbacks::HandleTransmitDone(Mac::TxFrame &aFrame, Mac::RxFrame *aAckFrame, Error aError)
 {
     Get<Mac::SubMac>().HandleTransmitDone(aFrame, aAckFrame, aError);
 }
@@ -59,7 +59,7 @@
 }
 
 #if OPENTHREAD_CONFIG_DIAG_ENABLE
-void Radio::Callbacks::HandleDiagsReceiveDone(Mac::RxFrame *aFrame, otError aError)
+void Radio::Callbacks::HandleDiagsReceiveDone(Mac::RxFrame *aFrame, Error aError)
 {
 #if OPENTHREAD_RADIO
     // Pass it to notify OpenThread `Diags` module on host side.
@@ -69,7 +69,7 @@
 #endif
 }
 
-void Radio::Callbacks::HandleDiagsTransmitDone(Mac::TxFrame &aFrame, otError aError)
+void Radio::Callbacks::HandleDiagsTransmitDone(Mac::TxFrame &aFrame, Error aError)
 {
 #if OPENTHREAD_RADIO
     // Pass it to notify OpenThread `Diags` module on host side.
diff --git a/src/core/radio/radio_platform.cpp b/src/core/radio/radio_platform.cpp
index 76d19e0..c5c6328 100644
--- a/src/core/radio/radio_platform.cpp
+++ b/src/core/radio/radio_platform.cpp
@@ -44,7 +44,7 @@
 
 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
 
-extern "C" void otPlatRadioReceiveDone(otInstance *aInstance, otRadioFrame *aFrame, otError aError)
+extern "C" void otPlatRadioReceiveDone(otInstance *aInstance, otRadioFrame *aFrame, Error aError)
 {
     Instance &    instance = *static_cast<Instance *>(aInstance);
     Mac::RxFrame *rxFrame  = static_cast<Mac::RxFrame *>(aFrame);
@@ -81,7 +81,7 @@
     return;
 }
 
-extern "C" void otPlatRadioTxDone(otInstance *aInstance, otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError)
+extern "C" void otPlatRadioTxDone(otInstance *aInstance, otRadioFrame *aFrame, otRadioFrame *aAckFrame, Error aError)
 {
     Instance &    instance = *static_cast<Instance *>(aInstance);
     Mac::TxFrame &txFrame  = *static_cast<Mac::TxFrame *>(aFrame);
@@ -116,7 +116,7 @@
 }
 
 #if OPENTHREAD_CONFIG_DIAG_ENABLE
-extern "C" void otPlatDiagRadioReceiveDone(otInstance *aInstance, otRadioFrame *aFrame, otError aError)
+extern "C" void otPlatDiagRadioReceiveDone(otInstance *aInstance, otRadioFrame *aFrame, Error aError)
 {
     Instance &    instance = *static_cast<Instance *>(aInstance);
     Mac::RxFrame *rxFrame  = static_cast<Mac::RxFrame *>(aFrame);
@@ -131,7 +131,7 @@
     instance.Get<Radio::Callbacks>().HandleDiagsReceiveDone(rxFrame, aError);
 }
 
-extern "C" void otPlatDiagRadioTransmitDone(otInstance *aInstance, otRadioFrame *aFrame, otError aError)
+extern "C" void otPlatDiagRadioTransmitDone(otInstance *aInstance, otRadioFrame *aFrame, Error aError)
 {
     Instance &    instance = *static_cast<Instance *>(aInstance);
     Mac::TxFrame &txFrame  = *static_cast<Mac::TxFrame *>(aFrame);
@@ -146,7 +146,7 @@
 
 #else // #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
 
-extern "C" void otPlatRadioReceiveDone(otInstance *, otRadioFrame *, otError)
+extern "C" void otPlatRadioReceiveDone(otInstance *, otRadioFrame *, Error)
 {
 }
 
@@ -154,7 +154,7 @@
 {
 }
 
-extern "C" void otPlatRadioTxDone(otInstance *, otRadioFrame *, otRadioFrame *, otError)
+extern "C" void otPlatRadioTxDone(otInstance *, otRadioFrame *, otRadioFrame *, Error)
 {
 }
 
@@ -163,11 +163,11 @@
 }
 
 #if OPENTHREAD_CONFIG_DIAG_ENABLE
-extern "C" void otPlatDiagRadioReceiveDone(otInstance *, otRadioFrame *, otError)
+extern "C" void otPlatDiagRadioReceiveDone(otInstance *, otRadioFrame *, Error)
 {
 }
 
-extern "C" void otPlatDiagRadioTransmitDone(otInstance *, otRadioFrame *, otError)
+extern "C" void otPlatDiagRadioTransmitDone(otInstance *, otRadioFrame *, Error)
 {
 }
 #endif
@@ -242,27 +242,43 @@
     return 0;
 }
 
-OT_TOOL_WEAK otError otPlatRadioGetFemLnaGain(otInstance *aInstance, int8_t *aGain)
+OT_TOOL_WEAK Error otPlatRadioGetFemLnaGain(otInstance *aInstance, int8_t *aGain)
 {
     OT_UNUSED_VARIABLE(aInstance);
     OT_UNUSED_VARIABLE(aGain);
 
-    return OT_ERROR_NOT_IMPLEMENTED;
+    return kErrorNotImplemented;
 }
 
-OT_TOOL_WEAK otError otPlatRadioSetFemLnaGain(otInstance *aInstance, int8_t aGain)
+OT_TOOL_WEAK Error otPlatRadioSetFemLnaGain(otInstance *aInstance, int8_t aGain)
 {
     OT_UNUSED_VARIABLE(aInstance);
     OT_UNUSED_VARIABLE(aGain);
 
-    return OT_ERROR_NOT_IMPLEMENTED;
+    return kErrorNotImplemented;
 }
 
-OT_TOOL_WEAK otError otPlatRadioSetChannelMaxTransmitPower(otInstance *aInstance, uint8_t aChannel, int8_t aMaxPower)
+OT_TOOL_WEAK Error otPlatRadioSetChannelMaxTransmitPower(otInstance *aInstance, uint8_t aChannel, int8_t aMaxPower)
 {
     OT_UNUSED_VARIABLE(aInstance);
     OT_UNUSED_VARIABLE(aChannel);
     OT_UNUSED_VARIABLE(aMaxPower);
 
-    return OT_ERROR_NOT_IMPLEMENTED;
+    return kErrorNotImplemented;
+}
+
+OT_TOOL_WEAK Error otPlatRadioSetRegion(otInstance *aInstance, uint16_t aRegionCode)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+    OT_UNUSED_VARIABLE(aRegionCode);
+
+    return kErrorNotImplemented;
+}
+
+OT_TOOL_WEAK Error otPlatRadioGetRegion(otInstance *aInstance, uint16_t *aRegionCode)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+    OT_UNUSED_VARIABLE(aRegionCode);
+
+    return kErrorNotImplemented;
 }
diff --git a/src/core/radio/trel_interface.cpp b/src/core/radio/trel_interface.cpp
index 5de95be..8c75621 100644
--- a/src/core/radio/trel_interface.cpp
+++ b/src/core/radio/trel_interface.cpp
@@ -81,7 +81,7 @@
     return;
 }
 
-otError Interface::Send(const Packet &aPacket)
+Error Interface::Send(const Packet &aPacket)
 {
     Ip6::Address destIp6Address;
 
@@ -131,7 +131,7 @@
     OT_UNUSED_VARIABLE(aInstance);
     OT_UNUSED_VARIABLE(aEnable);
 
-    return OT_ERROR_NOT_IMPLEMENTED;
+    return ot::kErrorNotImplemented;
 }
 
 #endif // #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
diff --git a/src/core/radio/trel_interface.hpp b/src/core/radio/trel_interface.hpp
index 843b546..ebdd40d 100644
--- a/src/core/radio/trel_interface.hpp
+++ b/src/core/radio/trel_interface.hpp
@@ -92,11 +92,11 @@
      *
      * @param[in] aPacket   A packet to send.
      *
-     * @retval OT_ERROR_NONE   The frame was sent successfully.
-     * @retval OT_ERROR_ABORT  The interface is not ready and send was aborted.
+     * @retval kErrorNone   The frame was sent successfully.
+     * @retval kErrorAbort  The interface is not ready and send was aborted.
      *
      */
-    otError Send(const Packet &aPacket);
+    Error Send(const Packet &aPacket);
 
     /**
      * This method is a callback from platform layer to handle a received packet over the interface.
diff --git a/src/core/radio/trel_link.cpp b/src/core/radio/trel_link.cpp
index 2d935f4..e2e9d56 100644
--- a/src/core/radio/trel_link.cpp
+++ b/src/core/radio/trel_link.cpp
@@ -49,8 +49,8 @@
     , mRxChannel(0)
     , mPanId(Mac::kPanIdBroadcast)
     , mTxPacketNumber(0)
-    , mTxTasklet(aInstance, HandleTxTasklet, this)
-    , mTimer(aInstance, HandleTimer, this)
+    , mTxTasklet(aInstance, HandleTxTasklet)
+    , mTimer(aInstance, HandleTimer)
     , mInterface(aInstance)
 {
     memset(&mTxFrame, 0, sizeof(mTxFrame));
@@ -115,7 +115,7 @@
 
 void Link::HandleTxTasklet(Tasklet &aTasklet)
 {
-    aTasklet.GetOwner<Link>().HandleTxTasklet();
+    aTasklet.Get<Link>().HandleTxTasklet();
 }
 
 void Link::HandleTxTasklet(void)
@@ -143,7 +143,7 @@
     // continue to rx on same channel
     mRxChannel = mTxFrame.GetChannel();
 
-    VerifyOrExit(!mTxFrame.IsEmpty(), InvokeSendDone(OT_ERROR_ABORT));
+    VerifyOrExit(!mTxFrame.IsEmpty(), InvokeSendDone(kErrorAbort));
 
     IgnoreError(mTxFrame.GetDstAddr(destAddr));
 
@@ -171,7 +171,7 @@
         }
     }
 
-    if (mTxFrame.GetDstPanId(destPanId) != OT_ERROR_NONE)
+    if (mTxFrame.GetDstPanId(destPanId) != kErrorNone)
     {
         destPanId = Mac::kPanIdBroadcast;
     }
@@ -203,7 +203,7 @@
     otLogDebgMac("Trel: BeginTransmit() [%s] plen:%d", txPacket.GetHeader().ToString().AsCString(),
                  txPacket.GetPayloadLength());
 
-    VerifyOrExit(mInterface.Send(txPacket) == OT_ERROR_NONE, InvokeSendDone(OT_ERROR_ABORT));
+    VerifyOrExit(mInterface.Send(txPacket) == kErrorNone, InvokeSendDone(kErrorAbort));
 
     if (mTxFrame.GetAckRequest())
     {
@@ -232,13 +232,13 @@
         ackFrame = &mRxFrame;
     }
 
-    InvokeSendDone(OT_ERROR_NONE, ackFrame);
+    InvokeSendDone(kErrorNone, ackFrame);
 
 exit:
     return;
 }
 
-void Link::InvokeSendDone(otError aError, Mac::RxFrame *aAckFrame)
+void Link::InvokeSendDone(Error aError, Mac::RxFrame *aAckFrame)
 {
     SetState(kStateReceive);
 
@@ -248,7 +248,7 @@
 
 void Link::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<Link>().HandleTimer();
+    aTimer.Get<Link>().HandleTimer();
 }
 
 void Link::HandleTimer(void)
@@ -284,7 +284,7 @@
     case Mle::kRoleChild:
         HandleTimer(Get<Mle::MleRouter>().GetParent());
 
-        // Fall through
+        OT_FALL_THROUGH;
 
     case Mle::kRoleRouter:
     case Mle::kRoleLeader:
@@ -303,7 +303,7 @@
     {
         aNeighbor.mTrelPreviousPendingAcks--;
 
-        ReportDeferredAckStatus(aNeighbor, OT_ERROR_NO_ACK);
+        ReportDeferredAckStatus(aNeighbor, kErrorNoAck);
         VerifyOrExit(!aNeighbor.IsStateInvalid());
     }
 
@@ -372,7 +372,7 @@
     mRxFrame.mInfo.mRxInfo.mLqi                   = OT_RADIO_LQI_NONE;
     mRxFrame.mInfo.mRxInfo.mAckedWithFramePending = true;
 
-    Get<Mac::Mac>().HandleReceivedFrame(&mRxFrame, OT_ERROR_NONE);
+    Get<Mac::Mac>().HandleReceivedFrame(&mRxFrame, kErrorNone);
 
 exit:
     return;
@@ -380,7 +380,7 @@
 
 void Link::HandleAck(Packet &aAckPacket)
 {
-    otError      ackError;
+    Error        ackError;
     Mac::Address srcAddress;
     Neighbor *   neighbor;
     uint32_t     ackNumber;
@@ -404,14 +404,14 @@
         // expected one. If it does not, it indicates that some of
         // packets missed their acks.
 
-        ackError = (ackNumber == neighbor->GetExpectedTrelAckNumber()) ? OT_ERROR_NONE : OT_ERROR_NO_ACK;
+        ackError = (ackNumber == neighbor->GetExpectedTrelAckNumber()) ? kErrorNone : kErrorNoAck;
 
         neighbor->DecrementPendingTrelAckCount();
 
         ReportDeferredAckStatus(*neighbor, ackError);
         VerifyOrExit(!neighbor->IsStateInvalid());
 
-    } while (ackError == OT_ERROR_NO_ACK);
+    } while (ackError == kErrorNoAck);
 
 exit:
     return;
@@ -436,10 +436,10 @@
     IgnoreError(mInterface.Send(ackPacket));
 }
 
-void Link::ReportDeferredAckStatus(Neighbor &aNeighbor, otError aError)
+void Link::ReportDeferredAckStatus(Neighbor &aNeighbor, Error aError)
 {
     otLogDebgMac("Trel: ReportDeferredAckStatus(): %s for %s", aNeighbor.GetExtAddress().ToString().AsCString(),
-                 otThreadErrorToString(aError));
+                 ErrorToString(aError));
 
     Get<MeshForwarder>().HandleDeferredAck(aNeighbor, aError);
 }
diff --git a/src/core/radio/trel_link.hpp b/src/core/radio/trel_link.hpp
index 825bb33..4f597ff 100644
--- a/src/core/radio/trel_link.hpp
+++ b/src/core/radio/trel_link.hpp
@@ -168,12 +168,12 @@
 
     void SetState(State aState);
     void BeginTransmit(void);
-    void InvokeSendDone(otError aError) { InvokeSendDone(aError, nullptr); }
-    void InvokeSendDone(otError aError, Mac::RxFrame *aAckFrame);
+    void InvokeSendDone(Error aError) { InvokeSendDone(aError, nullptr); }
+    void InvokeSendDone(Error aError, Mac::RxFrame *aAckFrame);
     void ProcessReceivedPacket(Packet &aPacket);
     void HandleAck(Packet &aAckPacket);
     void SendAck(Packet &aRxPacket);
-    void ReportDeferredAckStatus(Neighbor &aNeighbor, otError aError);
+    void ReportDeferredAckStatus(Neighbor &aNeighbor, Error aError);
     void HandleTimer(Neighbor &aNeighbor);
 
     static void HandleTxTasklet(Tasklet &aTasklet);
diff --git a/src/core/thread/address_resolver.cpp b/src/core/thread/address_resolver.cpp
index 7d2f0e7..14f1314 100644
--- a/src/core/thread/address_resolver.cpp
+++ b/src/core/thread/address_resolver.cpp
@@ -78,7 +78,7 @@
         {
             if (list == &mQueryList)
             {
-                Get<MeshForwarder>().HandleResolved(entry->GetTarget(), OT_ERROR_DROP);
+                Get<MeshForwarder>().HandleResolved(entry->GetTarget(), kErrorDrop);
             }
 
             mCacheEntryPool.Free(*entry);
@@ -86,9 +86,9 @@
     }
 }
 
-otError AddressResolver::GetNextCacheEntry(EntryInfo &aInfo, Iterator &aIterator) const
+Error AddressResolver::GetNextCacheEntry(EntryInfo &aInfo, Iterator &aIterator) const
 {
-    otError               error = OT_ERROR_NONE;
+    Error                 error = kErrorNone;
     const CacheEntryList *list;
     const CacheEntry *    entry;
 
@@ -115,7 +115,7 @@
         }
         else
         {
-            ExitNow(error = OT_ERROR_NOT_FOUND);
+            ExitNow(error = kErrorNotFound);
         }
 
         entry = list->GetHead();
@@ -323,21 +323,21 @@
 
     if (&aList == &mQueryList)
     {
-        Get<MeshForwarder>().HandleResolved(aEntry.GetTarget(), OT_ERROR_DROP);
+        Get<MeshForwarder>().HandleResolved(aEntry.GetTarget(), kErrorDrop);
     }
 
     LogCacheEntryChange(kEntryRemoved, aReason, aEntry, &aList);
 }
 
-otError AddressResolver::UpdateCacheEntry(const Ip6::Address &aEid, Mac::ShortAddress aRloc16)
+Error AddressResolver::UpdateCacheEntry(const Ip6::Address &aEid, Mac::ShortAddress aRloc16)
 {
-    otError         error = OT_ERROR_NONE;
+    Error           error = kErrorNone;
     CacheEntryList *list;
     CacheEntry *    entry;
     CacheEntry *    prev;
 
     entry = FindCacheEntry(aEid, list, prev);
-    VerifyOrExit(entry != nullptr, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(entry != nullptr, error = kErrorNotFound);
 
     if ((list == &mCachedList) || (list == &mSnoopedList))
     {
@@ -356,7 +356,7 @@
         entry->MarkLastTransactionTimeAsInvalid();
         mCachedList.Push(*entry);
 
-        Get<MeshForwarder>().HandleResolved(aEid, OT_ERROR_NONE);
+        Get<MeshForwarder>().HandleResolved(aEid, kErrorNone);
     }
 
     LogCacheEntryChange(kEntryUpdated, kReasonSnoop, *entry);
@@ -436,9 +436,9 @@
     }
 }
 
-otError AddressResolver::Resolve(const Ip6::Address &aEid, Mac::ShortAddress &aRloc16, bool aAllowAddressQuery)
+Error AddressResolver::Resolve(const Ip6::Address &aEid, Mac::ShortAddress &aRloc16, bool aAllowAddressQuery)
 {
-    otError         error = OT_ERROR_NONE;
+    Error           error = kErrorNone;
     CacheEntry *    entry;
     CacheEntry *    prev = nullptr;
     CacheEntryList *list;
@@ -451,10 +451,10 @@
         // allocate a new entry and perform address query. We do not
         // allow first-time address query entries to be evicted till
         // timeout.
-        VerifyOrExit(aAllowAddressQuery, error = OT_ERROR_NOT_FOUND);
+        VerifyOrExit(aAllowAddressQuery, error = kErrorNotFound);
 
         entry = NewCacheEntry(/* aSnoopedEntry */ false);
-        VerifyOrExit(entry != nullptr, error = OT_ERROR_NO_BUFS);
+        VerifyOrExit(entry != nullptr, error = kErrorNoBufs);
 
         entry->SetTarget(aEid);
         entry->SetRloc16(Mac::kShortAddrInvalid);
@@ -483,11 +483,11 @@
     // Note that if `aAllowAddressQuery` is `false` then the `entry` is definitely already in a list, i.e., we cannot
     // not get here with `aAllowAddressQuery` being `false` and `entry` being a newly allocated one, due to the
     // `VerifyOrExit` check that `aAllowAddressQuery` is `true` before allocating a new cache entry.
-    VerifyOrExit(aAllowAddressQuery, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(aAllowAddressQuery, error = kErrorNotFound);
 
     if (list == &mQueryList)
     {
-        ExitNow(error = OT_ERROR_ADDRESS_QUERY);
+        ExitNow(error = kErrorAddressQuery);
     }
 
     if (list == &mQueryRetryList)
@@ -496,14 +496,14 @@
         // Query again only if the timeout (retry delay interval) is
         // expired.
 
-        VerifyOrExit(entry->IsTimeoutZero(), error = OT_ERROR_DROP);
+        VerifyOrExit(entry->IsTimeoutZero(), error = kErrorDrop);
         mQueryRetryList.PopAfter(prev);
     }
 
     entry->SetTimeout(kAddressQueryTimeout);
 
     error = SendAddressQuery(aEid);
-    VerifyOrExit(error == OT_ERROR_NONE, mCacheEntryPool.Free(*entry));
+    VerifyOrExit(error == kErrorNone, mCacheEntryPool.Free(*entry));
 
     if (list == nullptr)
     {
@@ -511,19 +511,19 @@
     }
 
     mQueryList.Push(*entry);
-    error = OT_ERROR_ADDRESS_QUERY;
+    error = kErrorAddressQuery;
 
 exit:
     return error;
 }
 
-otError AddressResolver::SendAddressQuery(const Ip6::Address &aEid)
+Error AddressResolver::SendAddressQuery(const Ip6::Address &aEid)
 {
-    otError          error;
+    Error            error;
     Coap::Message *  message;
     Ip6::MessageInfo messageInfo;
 
-    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewPriorityMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewPriorityMessage()) != nullptr, error = kErrorNoBufs);
 
     message->InitAsNonConfirmablePost();
     SuccessOrExit(error = message->AppendUriPathOptions(UriPath::kAddressQuery));
@@ -583,9 +583,9 @@
 
     switch (Tlv::Find<ThreadLastTransactionTimeTlv>(aMessage, lastTransactionTime))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         break;
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         lastTransactionTime = 0;
         break;
     default:
@@ -622,12 +622,12 @@
 
     LogCacheEntryChange(kEntryUpdated, kReasonReceivedNotification, *entry);
 
-    if (Get<Tmf::TmfAgent>().SendEmptyAck(aMessage, aMessageInfo) == OT_ERROR_NONE)
+    if (Get<Tmf::TmfAgent>().SendEmptyAck(aMessage, aMessageInfo) == kErrorNone)
     {
         otLogInfoArp("Sending address notification acknowledgment");
     }
 
-    Get<MeshForwarder>().HandleResolved(target, OT_ERROR_NONE);
+    Get<MeshForwarder>().HandleResolved(target, kErrorNone);
 
 exit:
     return;
@@ -637,11 +637,11 @@
                                        const Ip6::InterfaceIdentifier &aMeshLocalIid,
                                        const Ip6::Address *            aDestination)
 {
-    otError          error;
+    Error            error;
     Coap::Message *  message;
     Ip6::MessageInfo messageInfo;
 
-    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = kErrorNoBufs);
 
     message->Init(aDestination == nullptr ? Coap::kTypeNonConfirmable : Coap::kTypeConfirmable, Coap::kCodePost);
     SuccessOrExit(error = message->AppendUriPathOptions(UriPath::kAddressError));
@@ -668,10 +668,10 @@
 
 exit:
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         FreeMessage(message);
-        otLogInfoArp("Failed to send address error: %s", otThreadErrorToString(error));
+        otLogInfoArp("Failed to send address error: %s", ErrorToString(error));
     }
 }
 
@@ -683,19 +683,19 @@
 
 void AddressResolver::HandleAddressError(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError                  error = OT_ERROR_NONE;
+    Error                    error = kErrorNone;
     Ip6::Address             target;
     Ip6::InterfaceIdentifier meshLocalIid;
     Mac::ExtAddress          extAddr;
     Ip6::Address             destination;
 
-    VerifyOrExit(aMessage.IsPostRequest(), error = OT_ERROR_DROP);
+    VerifyOrExit(aMessage.IsPostRequest(), error = kErrorDrop);
 
     otLogInfoArp("Received address error notification");
 
     if (aMessage.IsConfirmable() && !aMessageInfo.GetSockAddr().IsMulticast())
     {
-        if (Get<Tmf::TmfAgent>().SendEmptyAck(aMessage, aMessageInfo) == OT_ERROR_NONE)
+        if (Get<Tmf::TmfAgent>().SendEmptyAck(aMessage, aMessageInfo) == kErrorNone)
         {
             otLogInfoArp("Sent address error notification acknowledgment");
         }
@@ -738,7 +738,7 @@
             // Mesh Local EID differs, so check whether Target EID
             // matches a child address and if so remove it.
 
-            if (child.RemoveIp6Address(target) == OT_ERROR_NONE)
+            if (child.RemoveIp6Address(target) == kErrorNone)
             {
                 SuccessOrExit(error = Get<Mle::Mle>().GetLocatorAddress(destination, child.GetRloc16()));
 
@@ -750,9 +750,9 @@
 
 exit:
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogWarnArp("Error while processing address error notification: %s", otThreadErrorToString(error));
+        otLogWarnArp("Error while processing address error notification: %s", ErrorToString(error));
     }
 }
 
@@ -816,11 +816,11 @@
                                                const uint32_t *                aLastTransactionTime,
                                                const Ip6::Address &            aDestination)
 {
-    otError          error;
+    Error            error;
     Coap::Message *  message;
     Ip6::MessageInfo messageInfo;
 
-    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewPriorityMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewPriorityMessage()) != nullptr, error = kErrorNoBufs);
 
     message->InitAsConfirmablePost();
     SuccessOrExit(error = message->AppendUriPathOptions(UriPath::kAddressNotify));
@@ -912,7 +912,7 @@
             otLogInfoArp("Timed out waiting for address notification for %s, retry: %d",
                          entry->GetTarget().ToString().AsCString(), entry->GetTimeout());
 
-            Get<MeshForwarder>().HandleResolved(entry->GetTarget(), OT_ERROR_DROP);
+            Get<MeshForwarder>().HandleResolved(entry->GetTarget(), kErrorDrop);
 
             // When the entry is removed from `mQueryList`
             // we keep the `prev` pointer same as before.
diff --git a/src/core/thread/address_resolver.hpp b/src/core/thread/address_resolver.hpp
index bccf1be..13f0d70 100644
--- a/src/core/thread/address_resolver.hpp
+++ b/src/core/thread/address_resolver.hpp
@@ -99,11 +99,11 @@
      *                           To get the first entry, initialize the iterator by setting all its fields to zero.
      *                           e.g., `memset` the the iterator structure to zero.
      *
-     * @retval OT_ERROR_NONE       Successfully populated @p aInfo with the info for the next EID cache entry.
-     * @retval OT_ERROR_NOT_FOUND  No more entries in the address cache table.
+     * @retval kErrorNone      Successfully populated @p aInfo with the info for the next EID cache entry.
+     * @retval kErrorNotFound  No more entries in the address cache table.
      *
      */
-    otError GetNextCacheEntry(EntryInfo &aInfo, Iterator &aIterator) const;
+    Error GetNextCacheEntry(EntryInfo &aInfo, Iterator &aIterator) const;
 
     /**
      * This method removes the EID-to-RLOC cache entries corresponding to an RLOC16.
@@ -135,11 +135,11 @@
      * @param[in]  aEid               A reference to the EID.
      * @param[in]  aRloc16            The RLOC16 corresponding to @p aEid.
      *
-     * @retval OT_ERROR_NONE           Successfully updates an existing cache entry.
-     * @retval OT_ERROR_NOT_FOUND      No cache entry with @p aEid.
+     * @retval kErrorNone          Successfully updates an existing cache entry.
+     * @retval kErrorNotFound      No cache entry with @p aEid.
      *
      */
-    otError UpdateCacheEntry(const Ip6::Address &aEid, Mac::ShortAddress aRloc16);
+    Error UpdateCacheEntry(const Ip6::Address &aEid, Mac::ShortAddress aRloc16);
 
     /**
      * This method adds a snooped cache entry for a given EID.
@@ -161,14 +161,14 @@
      * @param[out]  aRloc16             The RLOC16 corresponding to @p aEid.
      * @param[in]   aAllowAddressQuery  Allow to initiate Address Query if the mapping is not known.
      *
-     * @retval OT_ERROR_NONE           Successfully provided the RLOC16.
-     * @retval OT_ERROR_ADDRESS_QUERY  Initiated an Address Query if allowed.
-     * @retval OT_ERROR_DROP           Earlier Address Query for the EID timed out. In retry timeout interval.
-     * @retval OT_ERROR_NO_BUFS        Insufficient buffer space available to send Address Query.
-     * @retval OT_ERROR_NOT_FOUND      The mapping was not found and Address Query was not allowed.
+     * @retval kErrorNone           Successfully provided the RLOC16.
+     * @retval kErrorAddressQuery   Initiated an Address Query if allowed.
+     * @retval kErrorDrop           Earlier Address Query for the EID timed out. In retry timeout interval.
+     * @retval kErrorNoBufs         Insufficient buffer space available to send Address Query.
+     * @retval kErrorNotFound       The mapping was not found and Address Query was not allowed.
      *
      */
-    otError Resolve(const Ip6::Address &aEid, Mac::ShortAddress &aRloc16, bool aAllowAddressQuery = true);
+    Error Resolve(const Ip6::Address &aEid, Mac::ShortAddress &aRloc16, bool aAllowAddressQuery = true);
 
     /**
      * This method restarts any ongoing address queries.
@@ -312,7 +312,7 @@
     CacheEntry *NewCacheEntry(bool aSnoopedEntry);
     void        RemoveCacheEntry(CacheEntry &aEntry, CacheEntryList &aList, CacheEntry *aPrevEntry, Reason aReason);
 
-    otError SendAddressQuery(const Ip6::Address &aEid);
+    Error SendAddressQuery(const Ip6::Address &aEid);
 
     static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
 
diff --git a/src/core/thread/announce_begin_server.cpp b/src/core/thread/announce_begin_server.cpp
index 9183df4..1023980 100644
--- a/src/core/thread/announce_begin_server.cpp
+++ b/src/core/thread/announce_begin_server.cpp
@@ -92,7 +92,7 @@
 
 void AnnounceBeginServer::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<AnnounceBeginServer>().AnnounceSenderBase::HandleTimer();
+    aTimer.Get<AnnounceBeginServer>().AnnounceSenderBase::HandleTimer();
 }
 
 } // namespace ot
diff --git a/src/core/thread/announce_begin_server.hpp b/src/core/thread/announce_begin_server.hpp
index 054e54a..66be807 100644
--- a/src/core/thread/announce_begin_server.hpp
+++ b/src/core/thread/announce_begin_server.hpp
@@ -64,7 +64,7 @@
      * @param[in]  aCount         The number of transmissions per channel.
      * @param[in]  aPeriod        The time between transmissions (milliseconds).
      *
-     * @retval OT_ERROR_NONE  Successfully started the transmission process.
+     * @retval kErrorNone  Successfully started the transmission process.
      *
      */
     void SendAnnounce(uint32_t aChannelMask, uint8_t aCount = kDefaultCount, uint16_t aPeriod = kDefaultPeriod);
diff --git a/src/core/thread/announce_sender.cpp b/src/core/thread/announce_sender.cpp
index f8b5495..b9787dd 100644
--- a/src/core/thread/announce_sender.cpp
+++ b/src/core/thread/announce_sender.cpp
@@ -52,7 +52,7 @@
     , mJitter(0)
     , mCount(0)
     , mChannel(0)
-    , mTimer(aInstance, aHandler, this)
+    , mTimer(aInstance, aHandler)
 {
 }
 
@@ -81,11 +81,11 @@
 
 void AnnounceSenderBase::HandleTimer(void)
 {
-    otError error;
+    Error error;
 
     error = mChannelMask.GetNextChannel(mChannel);
 
-    if (error == OT_ERROR_NOT_FOUND)
+    if (error == kErrorNotFound)
     {
         if (mCount != 0)
         {
@@ -97,7 +97,7 @@
         error    = mChannelMask.GetNextChannel(mChannel);
     }
 
-    OT_ASSERT(error == OT_ERROR_NONE);
+    OT_ASSERT(error == kErrorNone);
 
     Get<Mle::MleRouter>().SendAnnounce(mChannel, false);
 
@@ -116,7 +116,7 @@
 
 void AnnounceSender::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<AnnounceSender>().AnnounceSenderBase::HandleTimer();
+    aTimer.Get<AnnounceSender>().AnnounceSenderBase::HandleTimer();
 }
 
 void AnnounceSender::CheckState(void)
@@ -142,7 +142,7 @@
         }
 #endif
 
-        // fall through
+        OT_FALL_THROUGH;
 
     case Mle::kRoleDisabled:
     case Mle::kRoleDetached:
@@ -150,7 +150,7 @@
         ExitNow();
     }
 
-    VerifyOrExit(Get<MeshCoP::ActiveDataset>().GetChannelMask(channelMask) == OT_ERROR_NONE, Stop());
+    VerifyOrExit(Get<MeshCoP::ActiveDataset>().GetChannelMask(channelMask) == kErrorNone, Stop());
 
     period = interval / channelMask.GetNumberOfChannels();
 
diff --git a/src/core/thread/child_table.cpp b/src/core/thread/child_table.cpp
index 43f5cb5..9df285a 100644
--- a/src/core/thread/child_table.cpp
+++ b/src/core/thread/child_table.cpp
@@ -43,17 +43,17 @@
 
 ChildTable::Iterator::Iterator(Instance &aInstance, Child::StateFilter aFilter)
     : InstanceLocator(aInstance)
+    , ItemPtrIterator(nullptr)
     , mFilter(aFilter)
-    , mChild(nullptr)
 {
     Reset();
 }
 
 void ChildTable::Iterator::Reset(void)
 {
-    mChild = &Get<ChildTable>().mChildren[0];
+    mItem = &Get<ChildTable>().mChildren[0];
 
-    if (!mChild->MatchesFilter(mFilter))
+    if (!mItem->MatchesFilter(mFilter))
     {
         Advance();
     }
@@ -61,13 +61,13 @@
 
 void ChildTable::Iterator::Advance(void)
 {
-    VerifyOrExit(mChild != nullptr);
+    VerifyOrExit(mItem != nullptr);
 
     do
     {
-        mChild++;
-        VerifyOrExit(mChild < &Get<ChildTable>().mChildren[Get<ChildTable>().mMaxChildrenAllowed], mChild = nullptr);
-    } while (!mChild->MatchesFilter(mFilter));
+        mItem++;
+        VerifyOrExit(mItem < &Get<ChildTable>().mChildren[Get<ChildTable>().mMaxChildrenAllowed], mItem = nullptr);
+    } while (!mItem->MatchesFilter(mFilter));
 
 exit:
     return;
@@ -168,12 +168,12 @@
     return numChildren;
 }
 
-otError ChildTable::SetMaxChildrenAllowed(uint16_t aMaxChildren)
+Error ChildTable::SetMaxChildrenAllowed(uint16_t aMaxChildren)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(aMaxChildren > 0 && aMaxChildren <= kMaxChildren, error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit(!HasChildren(Child::kInStateAnyExceptInvalid), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(aMaxChildren > 0 && aMaxChildren <= kMaxChildren, error = kErrorInvalidArgs);
+    VerifyOrExit(!HasChildren(Child::kInStateAnyExceptInvalid), error = kErrorInvalidState);
 
     mMaxChildrenAllowed = aMaxChildren;
 
@@ -181,9 +181,9 @@
     return error;
 }
 
-otError ChildTable::GetChildInfoById(uint16_t aChildId, Child::Info &aChildInfo)
+Error ChildTable::GetChildInfoById(uint16_t aChildId, Child::Info &aChildInfo)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     Child *  child;
     uint16_t rloc16;
 
@@ -194,7 +194,7 @@
 
     rloc16 = Get<Mac::Mac>().GetShortAddress() | aChildId;
     child  = FindChild(rloc16, Child::kInStateValidOrRestoring);
-    VerifyOrExit(child != nullptr, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(child != nullptr, error = kErrorNotFound);
 
     aChildInfo.SetFrom(*child);
 
@@ -202,13 +202,13 @@
     return error;
 }
 
-otError ChildTable::GetChildInfoByIndex(uint16_t aChildIndex, Child::Info &aChildInfo)
+Error ChildTable::GetChildInfoByIndex(uint16_t aChildIndex, Child::Info &aChildInfo)
 {
-    otError error = OT_ERROR_NONE;
-    Child * child = nullptr;
+    Error  error = kErrorNone;
+    Child *child = nullptr;
 
     child = GetChildAtIndex(aChildIndex);
-    VerifyOrExit((child != nullptr) && child->IsStateValidOrRestoring(), error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit((child != nullptr) && child->IsStateValidOrRestoring(), error = kErrorNotFound);
 
     aChildInfo.SetFrom(*child);
 
@@ -218,7 +218,7 @@
 
 void ChildTable::Restore(void)
 {
-    otError  error          = OT_ERROR_NONE;
+    Error    error          = kErrorNone;
     bool     foundDuplicate = false;
     uint16_t numChildren    = 0;
 
@@ -230,7 +230,7 @@
 
         if (child == nullptr)
         {
-            VerifyOrExit((child = GetNewChild()) != nullptr, error = OT_ERROR_NO_BUFS);
+            VerifyOrExit((child = GetNewChild()) != nullptr, error = kErrorNoBufs);
         }
         else
         {
@@ -253,7 +253,7 @@
 
 exit:
 
-    if (foundDuplicate || (numChildren > GetMaxChildren()) || (error != OT_ERROR_NONE))
+    if (foundDuplicate || (numChildren > GetMaxChildren()) || (error != kErrorNone))
     {
         // If there is any error, e.g., there are more saved children
         // in non-volatile settings than could be restored or there are
@@ -277,7 +277,7 @@
     }
 }
 
-otError ChildTable::StoreChild(const Child &aChild)
+Error ChildTable::StoreChild(const Child &aChild)
 {
     Settings::ChildInfo childInfo;
 
diff --git a/src/core/thread/child_table.hpp b/src/core/thread/child_table.hpp
index dfc9055..fe7ff73 100644
--- a/src/core/thread/child_table.hpp
+++ b/src/core/thread/child_table.hpp
@@ -38,6 +38,7 @@
 
 #if OPENTHREAD_FTD
 
+#include "common/iterator_utils.hpp"
 #include "common/locator.hpp"
 #include "common/non_copyable.hpp"
 #include "thread/topology.hpp"
@@ -58,8 +59,9 @@
      * This class represents an iterator for iterating through the child entries in the child table.
      *
      */
-    class Iterator : public InstanceLocator
+    class Iterator : public InstanceLocator, public ItemPtrIterator<Child, Iterator>
     {
+        friend class ItemPtrIterator<Child, Iterator>;
         friend class IteratorBuilder;
 
     public:
@@ -79,99 +81,23 @@
         void Reset(void);
 
         /**
-         * This method indicates whether there are no more `Child` entries in the list (iterator has reached end of
-         * the list).
-         *
-         * @retval TRUE   There are no more entries in the list (reached end of the list).
-         * @retval FALSE  The current entry is valid.
-         *
-         */
-        bool IsDone(void) const { return (mChild == nullptr); }
-
-        /**
-         * This method overloads `++` operator (pre-increment) to advance the iterator.
-         *
-         * The iterator is moved to point to the next `Child` entry matching the given state filter in the constructor.
-         * If there are no more `Child` entries matching the given filter, the iterator becomes empty (i.e.,
-         * `GetChild()` returns `nullptr` and `IsDone()` returns `true`).
-         *
-         */
-        void operator++(void) { Advance(); }
-
-        /**
-         * This method overloads `++` operator (post-increment) to advance the iterator.
-         *
-         * The iterator is moved to point to the next `Child` entry matching the given state filter in the constructor.
-         * If there are no more `Child` entries matching the given filter, the iterator becomes empty (i.e.,
-         * `GetChild()` returns `nullptr` and `IsDone()` returns `true`).
-         *
-         */
-        void operator++(int) { Advance(); }
-
-        /**
          * This method gets the `Child` entry to which the iterator is currently pointing.
          *
          * @returns A pointer to the `Child` entry, or `nullptr` if the iterator is done and/or empty.
          *
          */
-        Child *GetChild(void) { return mChild; }
-
-        /**
-         * This method overloads the `*` dereference operator and gets a reference to `Child` entry to which the
-         * iterator is currently pointing.
-         *
-         * This method MUST be used when the iterator is not empty/finished (i.e., `IsDone()` returns `false`).
-         *
-         * @returns A reference to the `Child` entry currently pointed by the iterator.
-         *
-         */
-        Child &operator*(void) { return *mChild; }
-
-        /**
-         * This method overloads the `->` dereference operator and gets a pointer to `Child` entry to which the iterator
-         * is currently pointing.
-         *
-         * @returns A pointer to the `Child` entry associated with the iterator, or `nullptr` if iterator is empty/done.
-         *
-         */
-        Child *operator->(void) { return mChild; }
-
-        /**
-         * This method overloads operator `==` to evaluate whether or not two `Iterator` instances point to the same
-         * child entry.
-         *
-         * @param[in]  aOther  The other `Iterator` to compare with.
-         *
-         * @retval TRUE   If the two `Iterator` objects point to the same child entry or both are done.
-         * @retval FALSE  If the two `Iterator` objects do not point to the same child entry.
-         *
-         */
-        bool operator==(const Iterator &aOther) const { return mChild == aOther.mChild; }
-
-        /**
-         * This method overloads operator `!=` to evaluate whether or not two `Iterator` instances point to the same
-         * child entry.
-         *
-         * @param[in]  aOther  The other `Iterator` to compare with.
-         *
-         * @retval TRUE   If the two `Iterator` objects do not point to the same child entry.
-         * @retval FALSE  If the two `Iterator` objects point to the same child entry or both are done.
-         *
-         */
-        bool operator!=(const Iterator &aOther) const { return mChild != aOther.mChild; }
+        Child *GetChild(void) { return mItem; }
 
     private:
         explicit Iterator(Instance &aInstance)
             : InstanceLocator(aInstance)
             , mFilter(Child::StateFilter::kInStateValid)
-            , mChild(nullptr)
         {
         }
 
         void Advance(void);
 
         Child::StateFilter mFilter;
-        Child *            mChild;
     };
 
     /**
@@ -300,12 +226,12 @@
      *
      * @param[in]  aMaxChildren  Maximum number of children allowed.
      *
-     * @retval OT_ERROR_NONE          The number of allowed children changed successfully.
-     * @retval OT_ERROR_INVALID_ARGS  If @p aMaxChildren is not in the range [1, Max supported children].
-     * @retval OT_ERROR_INVALID_STATE The child table is not empty.
+     * @retval kErrorNone         The number of allowed children changed successfully.
+     * @retval kErrorInvalidArgs  If @p aMaxChildren is not in the range [1, Max supported children].
+     * @retval kErrorInvalidState The child table is not empty.
      *
      */
-    otError SetMaxChildrenAllowed(uint16_t aMaxChildren);
+    Error SetMaxChildrenAllowed(uint16_t aMaxChildren);
 
     /**
      * This method enables range-based `for` loop iteration over all child entries in the child table matching a given
@@ -329,7 +255,7 @@
      * @param[out]  aChildInfo  A reference to a `Child::Info` to populate with the child information.
      *
      */
-    otError GetChildInfoById(uint16_t aChildId, Child::Info &aChildInfo);
+    Error GetChildInfoById(uint16_t aChildId, Child::Info &aChildInfo);
 
     /**
      * This method retains diagnostic information for an attached child by the internal table index.
@@ -338,7 +264,7 @@
      * @param[out]  aChildInfo   A reference to a `Child::Info` to populate with the child information.
      *
      */
-    otError GetChildInfoByIndex(uint16_t aChildIndex, Child::Info &aChildInfo);
+    Error GetChildInfoByIndex(uint16_t aChildIndex, Child::Info &aChildInfo);
 
     /**
      * This method restores child table from non-volatile memory.
@@ -359,11 +285,11 @@
      *
      * @param[in]  aChild          A reference to the child to store.
      *
-     * @retval  OT_ERROR_NONE      Successfully store child.
-     * @retval  OT_ERROR_NO_BUFS   Insufficient available buffers to store child.
+     * @retval  kErrorNone     Successfully store child.
+     * @retval  kErrorNoBufs   Insufficient available buffers to store child.
      *
      */
-    otError StoreChild(const Child &aChild);
+    Error StoreChild(const Child &aChild);
 
     /**
      * This method indicates whether the child table contains any sleepy child (in states valid or restoring) with a
diff --git a/src/core/thread/csl_tx_scheduler.cpp b/src/core/thread/csl_tx_scheduler.cpp
index 613003e..9b72a53 100644
--- a/src/core/thread/csl_tx_scheduler.cpp
+++ b/src/core/thread/csl_tx_scheduler.cpp
@@ -28,7 +28,7 @@
 
 #include "csl_tx_scheduler.hpp"
 
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
 
 #include "common/locator-getters.hpp"
 #include "common/logging.hpp"
@@ -42,16 +42,16 @@
 {
 }
 
-inline otError CslTxScheduler::Callbacks::PrepareFrameForChild(Mac::TxFrame &aFrame,
-                                                               FrameContext &aContext,
-                                                               Child &       aChild)
+inline Error CslTxScheduler::Callbacks::PrepareFrameForChild(Mac::TxFrame &aFrame,
+                                                             FrameContext &aContext,
+                                                             Child &       aChild)
 {
     return Get<IndirectSender>().PrepareFrameForChild(aFrame, aContext, aChild);
 }
 
 inline void CslTxScheduler::Callbacks::HandleSentFrameToChild(const Mac::TxFrame &aFrame,
                                                               const FrameContext &aContext,
-                                                              otError             aError,
+                                                              Error               aError,
                                                               Child &             aChild)
 {
     Get<IndirectSender>().HandleSentFrameToChild(aFrame, aContext, aError, aChild);
@@ -163,23 +163,22 @@
     return static_cast<uint32_t>(nextTxWindow - radioNow - mCslFrameRequestAheadUs);
 }
 
+#if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
+
 Mac::TxFrame *CslTxScheduler::HandleFrameRequest(Mac::TxFrames &aTxFrames)
 {
     Mac::TxFrame *frame = nullptr;
-
-#if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
-    uint32_t txDelay;
+    uint32_t      txDelay;
 
     VerifyOrExit(mCslTxChild != nullptr);
 
 #if OPENTHREAD_CONFIG_MULTI_RADIO
-    frame = &aTxFrames.GetTxFrame(kRadioTypeIeee802154);
+    frame = &aTxFrames.GetTxFrame(Mac::kRadioTypeIeee802154);
 #else
     frame = &aTxFrames.GetTxFrame();
 #endif
 
-    VerifyOrExit(mCallbacks.PrepareFrameForChild(*frame, mFrameContext, *mCslTxChild) == OT_ERROR_NONE,
-                 frame = nullptr);
+    VerifyOrExit(mCallbacks.PrepareFrameForChild(*frame, mFrameContext, *mCslTxChild) == kErrorNone, frame = nullptr);
     mCslTxMessage = mCslTxChild->GetIndirectMessage();
     VerifyOrExit(mCslTxMessage != nullptr, frame = nullptr);
 
@@ -213,11 +212,19 @@
     frame->SetCsmaCaEnabled(false);
 
 exit:
-#endif // OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
     return frame;
 }
 
-void CslTxScheduler::HandleSentFrame(const Mac::TxFrame &aFrame, otError aError)
+#else // OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
+
+Mac::TxFrame *CslTxScheduler::HandleFrameRequest(Mac::TxFrames &)
+{
+    return nullptr;
+}
+
+#endif // OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
+
+void CslTxScheduler::HandleSentFrame(const Mac::TxFrame &aFrame, Error aError)
 {
     Child *child = mCslTxChild;
 
@@ -232,16 +239,16 @@
     return;
 }
 
-void CslTxScheduler::HandleSentFrame(const Mac::TxFrame &aFrame, otError aError, Child &aChild)
+void CslTxScheduler::HandleSentFrame(const Mac::TxFrame &aFrame, Error aError, Child &aChild)
 {
     switch (aError)
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         aChild.ResetCslTxAttempts();
         aChild.ResetIndirectTxAttempts();
         break;
 
-    case OT_ERROR_NO_ACK:
+    case kErrorNoAck:
         aChild.IncrementCslTxAttempts();
 
         otLogInfoMac("CSL tx to child %04x failed, attempt %d/%d", aChild.GetRloc16(), aChild.GetCslTxAttempts(),
@@ -254,9 +261,10 @@
             aChild.ResetCslTxAttempts();
         }
 
-        // Fall through
-    case OT_ERROR_CHANNEL_ACCESS_FAILURE:
-    case OT_ERROR_ABORT:
+        OT_FALL_THROUGH;
+
+    case kErrorChannelAccessFailure:
+    case kErrorAbort:
 
         // Even if CSL tx attempts count reaches max, the message won't be
         // dropped until indirect tx attempts count reaches max. So here it
@@ -295,4 +303,4 @@
 
 } // namespace ot
 
-#endif // !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
diff --git a/src/core/thread/csl_tx_scheduler.hpp b/src/core/thread/csl_tx_scheduler.hpp
index 645315c..718aed3 100644
--- a/src/core/thread/csl_tx_scheduler.hpp
+++ b/src/core/thread/csl_tx_scheduler.hpp
@@ -50,7 +50,7 @@
  * @{
  */
 
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
 
 class Child;
 
@@ -142,27 +142,27 @@
          * @param[out] aContext  A reference to a `FrameContext` where the context for the new frame would be placed.
          * @param[in]  aChild    The child for which to prepare the frame.
          *
-         * @retval OT_ERROR_NONE   Frame was prepared successfully.
-         * @retval OT_ERROR_ABORT  CSL transmission should be aborted (no frame for the child).
+         * @retval kErrorNone   Frame was prepared successfully.
+         * @retval kErrorAbort  CSL transmission should be aborted (no frame for the child).
          *
          */
-        otError PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &aContext, Child &aChild);
+        Error PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &aContext, Child &aChild);
 
         /**
          * This callback method notifies the end of CSL frame transmission to a child.
          *
          * @param[in]  aFrame     The transmitted frame.
          * @param[in]  aContext   The context associated with the frame when it was prepared.
-         * @param[in]  aError     OT_ERROR_NONE when the frame was transmitted successfully,
-         *                        OT_ERROR_NO_ACK when the frame was transmitted but no ACK was received,
-         *                        OT_ERROR_CHANNEL_ACCESS_FAILURE tx failed due to activity on the channel,
-         *                        OT_ERROR_ABORT when transmission was aborted for other reasons.
+         * @param[in]  aError     kErrorNone when the frame was transmitted successfully,
+         *                        kErrorNoAck when the frame was transmitted but no ACK was received,
+         *                        kErrorChannelAccessFailure tx failed due to activity on the channel,
+         *                        kErrorAbort when transmission was aborted for other reasons.
          * @param[in]  aChild     The child to which the frame was transmitted.
          *
          */
         void HandleSentFrameToChild(const Mac::TxFrame &aFrame,
                                     const FrameContext &aContext,
-                                    otError             aError,
+                                    Error               aError,
                                     Child &             aChild);
     };
     /**
@@ -197,9 +197,9 @@
 
     // Callbacks from `Mac`
     Mac::TxFrame *HandleFrameRequest(Mac::TxFrames &aTxFrames);
-    void          HandleSentFrame(const Mac::TxFrame &aFrame, otError aError);
+    void          HandleSentFrame(const Mac::TxFrame &aFrame, Error aError);
 
-    void HandleSentFrame(const Mac::TxFrame &aFrame, otError aError, Child &aChild);
+    void HandleSentFrame(const Mac::TxFrame &aFrame, Error aError, Child &aChild);
 
     uint32_t                mCslFrameRequestAheadUs;
     Child *                 mCslTxChild;
@@ -208,7 +208,7 @@
     Callbacks               mCallbacks;
 };
 
-#endif // !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
 
 /**
  * @}
diff --git a/src/core/thread/discover_scanner.cpp b/src/core/thread/discover_scanner.cpp
index 3e5720a..072e9d1 100644
--- a/src/core/thread/discover_scanner.cpp
+++ b/src/core/thread/discover_scanner.cpp
@@ -48,7 +48,7 @@
     : InstanceLocator(aInstance)
     , mHandler(nullptr)
     , mHandlerContext(nullptr)
-    , mTimer(aInstance, DiscoverScanner::HandleTimer, this)
+    , mTimer(aInstance, DiscoverScanner::HandleTimer)
     , mFilterIndexes()
     , mState(kStateIdle)
     , mScanChannel(0)
@@ -58,22 +58,22 @@
 {
 }
 
-otError DiscoverScanner::Discover(const Mac::ChannelMask &aScanChannels,
-                                  uint16_t                aPanId,
-                                  bool                    aJoiner,
-                                  bool                    aEnableFiltering,
-                                  const FilterIndexes *   aFilterIndexes,
-                                  Handler                 aCallback,
-                                  void *                  aContext)
+Error DiscoverScanner::Discover(const Mac::ChannelMask &aScanChannels,
+                                uint16_t                aPanId,
+                                bool                    aJoiner,
+                                bool                    aEnableFiltering,
+                                const FilterIndexes *   aFilterIndexes,
+                                Handler                 aCallback,
+                                void *                  aContext)
 {
-    otError                         error   = OT_ERROR_NONE;
+    Error                           error   = kErrorNone;
     Message *                       message = nullptr;
     Tlv                             tlv;
     Ip6::Address                    destination;
     MeshCoP::DiscoveryRequestTlv    discoveryRequest;
     MeshCoP::JoinerAdvertisementTlv joinerAdvertisement;
 
-    VerifyOrExit(mState == kStateIdle, error = OT_ERROR_BUSY);
+    VerifyOrExit(mState == kStateIdle, error = kErrorBusy);
 
     mEnableFiltering = aEnableFiltering;
 
@@ -103,7 +103,7 @@
         mScanChannels.Intersect(aScanChannels);
     }
 
-    VerifyOrExit((message = Get<Mle>().NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Mle>().NewMleMessage()) != nullptr, error = kErrorNoBufs);
     message->SetSubType(Message::kSubTypeMleDiscoverRequest);
     message->SetPanId(aPanId);
     SuccessOrExit(error = Get<Mle>().AppendHeader(*message, Mle::kCommandDiscoveryRequest));
@@ -151,7 +151,7 @@
     }
 
     mScanChannel = Mac::ChannelMask::kChannelIteratorFirst;
-    mState       = (mScanChannels.GetNextChannel(mScanChannel) == OT_ERROR_NONE) ? kStateScanning : kStateScanDone;
+    mState       = (mScanChannels.GetNextChannel(mScanChannel) == kErrorNone) ? kStateScanning : kStateScanDone;
 
     Mle::Log(Mle::kMessageSend, Mle::kTypeDiscoveryRequest, destination);
 
@@ -160,13 +160,13 @@
     return error;
 }
 
-otError DiscoverScanner::SetJoinerAdvertisement(uint32_t aOui, const uint8_t *aAdvData, uint8_t aAdvDataLength)
+Error DiscoverScanner::SetJoinerAdvertisement(uint32_t aOui, const uint8_t *aAdvData, uint8_t aAdvDataLength)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     VerifyOrExit((aAdvData != nullptr) && (aAdvDataLength != 0) &&
                      (aAdvDataLength <= MeshCoP::JoinerAdvertisementTlv::kAdvDataMaxLength) && (aOui <= kMaxOui),
-                 error = OT_ERROR_INVALID_ARGS);
+                 error = kErrorInvalidArgs);
 
     mOui           = aOui;
     mAdvDataLength = aAdvDataLength;
@@ -234,7 +234,7 @@
         mTimer.Stop();
         Get<MeshForwarder>().ResumeMessageTransmissions();
 
-        // Fall through
+        OT_FALL_THROUGH;
 
     case kStateScanDone:
         Get<Mac::Mac>().ClearTemporaryChannel();
@@ -258,7 +258,7 @@
 
 void DiscoverScanner::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<DiscoverScanner>().HandleTimer();
+    aTimer.Get<DiscoverScanner>().HandleTimer();
 }
 
 void DiscoverScanner::HandleTimer(void)
@@ -272,7 +272,7 @@
     // frame tx is aborted  from `PrepareDiscoveryRequestFrame()` and
     // then wraps up the scan (invoking handler callback).
 
-    if (mScanChannels.GetNextChannel(mScanChannel) != OT_ERROR_NONE)
+    if (mScanChannels.GetNextChannel(mScanChannel) != kErrorNone)
     {
         mState = kStateScanDone;
     }
@@ -285,7 +285,7 @@
 
 void DiscoverScanner::HandleDiscoveryResponse(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo) const
 {
-    otError                       error    = OT_ERROR_NONE;
+    Error                         error    = kErrorNone;
     const ThreadLinkInfo *        linkInfo = aMessageInfo.GetThreadLinkInfo();
     Tlv                           tlv;
     MeshCoP::Tlv                  meshcopTlv;
@@ -298,10 +298,10 @@
 
     Mle::Log(Mle::kMessageReceive, Mle::kTypeDiscoveryResponse, aMessageInfo.GetPeerAddr());
 
-    VerifyOrExit(mState == kStateScanning, error = OT_ERROR_DROP);
+    VerifyOrExit(mState == kStateScanning, error = kErrorDrop);
 
     // Find MLE Discovery TLV
-    VerifyOrExit(Tlv::FindTlvOffset(aMessage, Tlv::kDiscovery, offset) == OT_ERROR_NONE, error = OT_ERROR_PARSE);
+    VerifyOrExit(Tlv::FindTlvOffset(aMessage, Tlv::kDiscovery, offset) == kErrorNone, error = kErrorParse);
     IgnoreError(aMessage.Read(offset, tlv));
 
     offset += sizeof(tlv);
@@ -324,7 +324,7 @@
         {
         case MeshCoP::Tlv::kDiscoveryResponse:
             IgnoreError(aMessage.Read(offset, discoveryResponse));
-            VerifyOrExit(discoveryResponse.IsValid(), error = OT_ERROR_PARSE);
+            VerifyOrExit(discoveryResponse.IsValid(), error = kErrorParse);
             result.mVersion  = discoveryResponse.GetVersion();
             result.mIsNative = discoveryResponse.IsNativeCommissioner();
             break;
diff --git a/src/core/thread/discover_scanner.hpp b/src/core/thread/discover_scanner.hpp
index e72d8d2..e05dae7 100644
--- a/src/core/thread/discover_scanner.hpp
+++ b/src/core/thread/discover_scanner.hpp
@@ -114,18 +114,18 @@
      * @param[in]  aHandler           A pointer to a function that is called on receiving an MLE Discovery Response.
      * @param[in]  aContext           A pointer to arbitrary context information.
      *
-     * @retval OT_ERROR_NONE       Successfully started a Thread Discovery Scan.
-     * @retval OT_ERROR_NO_BUFS    Could not allocate message for Discovery Request.
-     * @retval OT_ERROR_BUSY       Thread Discovery Scan is already in progress.
+     * @retval kErrorNone       Successfully started a Thread Discovery Scan.
+     * @retval kErrorNoBufs     Could not allocate message for Discovery Request.
+     * @retval kErrorBusy       Thread Discovery Scan is already in progress.
      *
      */
-    otError Discover(const Mac::ChannelMask &aScanChannels,
-                     Mac::PanId              aPanId,
-                     bool                    aJoiner,
-                     bool                    aEnableFiltering,
-                     const FilterIndexes *   aFilterIndexes,
-                     Handler                 aCallback,
-                     void *                  aContext);
+    Error Discover(const Mac::ChannelMask &aScanChannels,
+                   Mac::PanId              aPanId,
+                   bool                    aJoiner,
+                   bool                    aEnableFiltering,
+                   const FilterIndexes *   aFilterIndexes,
+                   Handler                 aCallback,
+                   void *                  aContext);
 
     /**
      * This method indicates whether or not an MLE Thread Discovery Scan is currently in progress.
@@ -142,11 +142,11 @@
      * @param[in]  aAdvData         A pointer to AdvData for Joiner Advertisement.
      * @param[in]  aAdvDataLength   The length of AdvData.
      *
-     * @retval OT_ERROR_NONE            Successfully set Joiner Advertisement.
-     * @retval OT_ERROR_INVALID_ARGS    Invalid AdvData.
+     * @retval kErrorNone           Successfully set Joiner Advertisement.
+     * @retval kErrorInvalidArgs    Invalid AdvData.
      *
      */
-    otError SetJoinerAdvertisement(uint32_t aOui, const uint8_t *aAdvData, uint8_t aAdvDataLength);
+    Error SetJoinerAdvertisement(uint32_t aOui, const uint8_t *aAdvData, uint8_t aAdvDataLength);
 
 private:
     enum State
diff --git a/src/core/thread/dua_manager.cpp b/src/core/thread/dua_manager.cpp
index a9503a1..7bc809f 100644
--- a/src/core/thread/dua_manager.cpp
+++ b/src/core/thread/dua_manager.cpp
@@ -51,7 +51,7 @@
 
 DuaManager::DuaManager(Instance &aInstance)
     : InstanceLocator(aInstance)
-    , mRegistrationTask(aInstance, DuaManager::HandleRegistrationTask, this)
+    , mRegistrationTask(aInstance, DuaManager::HandleRegistrationTask)
     , mDuaNotification(UriPath::kDuaRegistrationNotify, &DuaManager::HandleDuaNotification, this)
     , mIsDuaPending(false)
 #if OPENTHREAD_CONFIG_DUA_ENABLE
@@ -110,7 +110,8 @@
         // In case removed for some reason e.g. the kDuaInvalid response from PBBR forcely
         VerifyOrExit(!Get<ThreadNetif>().HasUnicastAddress(GetDomainUnicastAddress()));
 
-        // fall through
+        OT_FALL_THROUGH;
+
     case BackboneRouter::Leader::kDomainPrefixRefreshed:
     case BackboneRouter::Leader::kDomainPrefixAdded:
     {
@@ -143,12 +144,12 @@
 }
 
 #if OPENTHREAD_CONFIG_DUA_ENABLE
-otError DuaManager::GenerateDomainUnicastAddressIid(void)
+Error DuaManager::GenerateDomainUnicastAddressIid(void)
 {
-    otError error;
+    Error   error;
     uint8_t dadCounter = mDadCounter;
 
-    if ((error = Get<Utils::Slaac>().GenerateIid(mDomainUnicastAddress, nullptr, 0, &dadCounter)) == OT_ERROR_NONE)
+    if ((error = Get<Utils::Slaac>().GenerateIid(mDomainUnicastAddress, nullptr, 0, &dadCounter)) == kErrorNone)
     {
         if (dadCounter != mDadCounter)
         {
@@ -160,17 +161,17 @@
     }
     else
     {
-        otLogWarnDua("Generate DUA: %s", otThreadErrorToString(error));
+        otLogWarnDua("Generate DUA: %s", ErrorToString(error));
     }
 
     return error;
 }
 
-otError DuaManager::SetFixedDuaInterfaceIdentifier(const Ip6::InterfaceIdentifier &aIid)
+Error DuaManager::SetFixedDuaInterfaceIdentifier(const Ip6::InterfaceIdentifier &aIid)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(!aIid.IsReserved(), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(!aIid.IsReserved(), error = kErrorInvalidArgs);
     VerifyOrExit(mFixedDuaInterfaceIdentifier.IsUnspecified() || mFixedDuaInterfaceIdentifier != aIid);
 
     mFixedDuaInterfaceIdentifier = aIid;
@@ -197,7 +198,7 @@
     {
         RemoveDomainUnicastAddress();
 
-        if (GenerateDomainUnicastAddressIid() == OT_ERROR_NONE)
+        if (GenerateDomainUnicastAddressIid() == kErrorNone)
         {
             AddDomainUnicastAddress();
         }
@@ -221,7 +222,7 @@
     return;
 }
 
-otError DuaManager::Store(void)
+Error DuaManager::Store(void)
 {
     Settings::DadInfo dadInfo;
 
@@ -264,7 +265,7 @@
     RemoveDomainUnicastAddress();
     mDadCounter++;
 
-    if (GenerateDomainUnicastAddressIid() == OT_ERROR_NONE)
+    if (GenerateDomainUnicastAddressIid() == kErrorNone)
     {
         AddDomainUnicastAddress();
     }
@@ -276,7 +277,7 @@
     uint16_t               delay = 0;
     otBackboneRouterConfig config;
 
-    VerifyOrExit(Get<BackboneRouter::Leader>().GetConfig(config) == OT_ERROR_NONE);
+    VerifyOrExit(Get<BackboneRouter::Leader>().GetConfig(config) == kErrorNone);
 
     delay = config.mReregistrationDelay > 1 ? Random::NonCrypto::GetUint16InRange(1, config.mReregistrationDelay) : 1;
 
@@ -395,6 +396,11 @@
     UpdateTimeTickerRegistration();
 }
 
+void DuaManager::HandleRegistrationTask(Tasklet &aTasklet)
+{
+    aTasklet.Get<DuaManager>().PerformNextRegistration();
+}
+
 void DuaManager::UpdateTimeTickerRegistration(void)
 {
     if (mDelay.mValue == 0)
@@ -409,34 +415,41 @@
 
 void DuaManager::PerformNextRegistration(void)
 {
-    otError          error   = OT_ERROR_NONE;
+    Error            error   = kErrorNone;
     Mle::MleRouter & mle     = Get<Mle::MleRouter>();
     Coap::Message *  message = nullptr;
     Ip6::MessageInfo messageInfo;
     Ip6::Address     dua;
 
-    VerifyOrExit(mle.IsAttached(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(Get<BackboneRouter::Leader>().HasPrimary(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(mle.IsAttached(), error = kErrorInvalidState);
+    VerifyOrExit(Get<BackboneRouter::Leader>().HasPrimary(), error = kErrorInvalidState);
 
     // Only allow one outgoing DUA.req
-    VerifyOrExit(!mIsDuaPending, error = OT_ERROR_BUSY);
+    VerifyOrExit(!mIsDuaPending, error = kErrorBusy);
 
     // Only send DUA.req when necessary
 #if OPENTHREAD_CONFIG_DUA_ENABLE
-#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE
-    VerifyOrExit(mle.IsRouterOrLeader() || !mle.IsExpectedToBecomeRouter(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit((mDuaState == kToRegister && mDelay.mFields.mRegistrationDelay == 0) ||
-                     (mChildDuaMask.HasAny() && mChildDuaMask != mChildDuaRegisteredMask),
-                 error = OT_ERROR_NOT_FOUND);
-#else
-    VerifyOrExit(mDuaState == kToRegister && mDelay.mFields.mRegistrationDelay == 0, error = OT_ERROR_NOT_FOUND);
-#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE
-
-    VerifyOrExit(mle.IsFullThreadDevice() || mle.GetParent().IsThreadVersion1p1(), error = OT_ERROR_INVALID_STATE);
+#if OPENTHREAD_FTD
+    VerifyOrExit(mle.IsRouterOrLeader() || !mle.IsExpectedToBecomeRouter(), error = kErrorInvalidState);
+#endif
+    VerifyOrExit(mle.IsFullThreadDevice() || mle.GetParent().IsThreadVersion1p1(), error = kErrorInvalidState);
 #endif // OPENTHREAD_CONFIG_DUA_ENABLE
 
+    {
+        bool needReg = false;
+
+#if OPENTHREAD_CONFIG_DUA_ENABLE
+        needReg = (mDuaState == kToRegister && mDelay.mFields.mRegistrationDelay == 0);
+#endif
+
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE
+        needReg = needReg || (mChildDuaMask.HasAny() && mChildDuaMask != mChildDuaRegisteredMask);
+#endif
+        VerifyOrExit(needReg, error = kErrorNotFound);
+    }
+
     // Prepare DUA.req
-    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewPriorityMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewPriorityMessage()) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsConfirmablePost(UriPath::kDuaRegistrationRequest));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -508,60 +521,61 @@
     mIsDuaPending   = true;
     mRegisteringDua = dua;
 
-    // TODO: (DUA) need update when CSL is enabled.
+    // Generally Thread 1.2 Router would send DUA.req on behalf for DUA registered by its MTD child.
+    // When Thread 1.2 MTD attaches to Thread 1.1 parent, 1.2 MTD should send DUA.req to PBBR itself.
+    // In this case, Thread 1.2 sleepy end device relies on fast data poll to fetch the response timely.
     if (!Get<Mle::Mle>().IsRxOnWhenIdle())
     {
         Get<DataPollSender>().SendFastPolls();
     }
 
 exit:
-    if (error == OT_ERROR_NO_BUFS)
+    if (error == kErrorNoBufs)
     {
         UpdateCheckDelay(Mle::kNoBufDelay);
     }
 
     FreeMessageOnError(message, error);
-    otLogInfoDua("Sent DUA.req for DUA %s: %s", dua.ToString().AsCString(), otThreadErrorToString(error));
+    otLogInfoDua("Sent DUA.req for DUA %s: %s", dua.ToString().AsCString(), ErrorToString(error));
 }
 
-void DuaManager::HandleDuaResponse(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo, otError aResult)
+void DuaManager::HandleDuaResponse(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo, Error aResult)
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
-    otError error;
+    Error error;
 
     mIsDuaPending = false;
 
-    if (aResult == OT_ERROR_RESPONSE_TIMEOUT)
+    if (aResult == kErrorResponseTimeout)
     {
         UpdateCheckDelay(Mle::KResponseTimeoutDelay);
         ExitNow(error = aResult);
     }
 
-    VerifyOrExit(aResult == OT_ERROR_NONE, error = OT_ERROR_PARSE);
+    VerifyOrExit(aResult == kErrorNone, error = kErrorParse);
     VerifyOrExit(aMessage.GetCode() == Coap::kCodeChanged || aMessage.GetCode() >= Coap::kCodeBadRequest,
-                 error = OT_ERROR_PARSE);
+                 error = kErrorParse);
 
     error = ProcessDuaResponse(aMessage);
 
 exit:
-    if (error != OT_ERROR_RESPONSE_TIMEOUT)
+    if (error != kErrorResponseTimeout)
     {
         mRegistrationTask.Post();
     }
 
-    otLogInfoDua("Received DUA.req: %s", otThreadErrorToString(error));
+    otLogInfoDua("Received DUA.req: %s", ErrorToString(error));
 }
 
 void DuaManager::HandleDuaNotification(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
-    otError error;
 
-    OT_UNUSED_VARIABLE(error);
+    Error error;
 
-    VerifyOrExit(aMessage.IsPostRequest(), error = OT_ERROR_PARSE);
+    VerifyOrExit(aMessage.IsPostRequest(), error = kErrorParse);
 
-    if (aMessage.IsConfirmable() && Get<Tmf::TmfAgent>().SendEmptyAck(aMessage, aMessageInfo) == OT_ERROR_NONE)
+    if (aMessage.IsConfirmable() && Get<Tmf::TmfAgent>().SendEmptyAck(aMessage, aMessageInfo) == kErrorNone)
     {
         otLogInfoDua("Sent DUA.ntf acknowledgment");
     }
@@ -569,12 +583,13 @@
     error = ProcessDuaResponse(aMessage);
 
 exit:
-    otLogInfoDua("Received DUA.ntf: %d", otThreadErrorToString(error));
+    OT_UNUSED_VARIABLE(error);
+    otLogInfoDua("Received DUA.ntf: %d", ErrorToString(error));
 }
 
-otError DuaManager::ProcessDuaResponse(Coap::Message &aMessage)
+Error DuaManager::ProcessDuaResponse(Coap::Message &aMessage)
 {
-    otError      error = OT_ERROR_NONE;
+    Error        error = kErrorNone;
     Ip6::Address target;
     uint8_t      status;
 
@@ -622,8 +637,8 @@
     {
         Child *child = Get<ChildTable>().GetChildAtIndex(mChildIndexDuaRegistering);
 
-        VerifyOrExit(child != nullptr, error = OT_ERROR_NOT_FOUND);
-        VerifyOrExit(child->HasIp6Address(target), error = OT_ERROR_NOT_FOUND);
+        VerifyOrExit(child != nullptr, error = kErrorNotFound);
+        VerifyOrExit(child->HasIp6Address(target), error = kErrorNotFound);
 
         mRegisterCurrentChildIndex = false;
 
@@ -665,9 +680,9 @@
 {
     Coap::Message *  message = nullptr;
     Ip6::MessageInfo messageInfo;
-    otError          error;
+    Error            error;
 
-    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewPriorityMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewPriorityMessage()) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsConfirmablePost(UriPath::kDuaRegistrationNotify));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -685,13 +700,13 @@
 
 exit:
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         FreeMessage(message);
 
         // TODO: (DUA) (P4) may enhance to  guarantee the delivery of DUA.ntf
         otLogWarnDua("Sent ADDR_NTF for child %04x DUA %s Error %s", aChild.GetRloc16(),
-                     aAddress.ToString().AsCString(), otThreadErrorToString(error));
+                     aAddress.ToString().AsCString(), ErrorToString(error));
     }
 }
 
diff --git a/src/core/thread/dua_manager.hpp b/src/core/thread/dua_manager.hpp
index ac46c6b..db83d3b 100644
--- a/src/core/thread/dua_manager.hpp
+++ b/src/core/thread/dua_manager.hpp
@@ -38,6 +38,10 @@
 
 #if OPENTHREAD_CONFIG_DUA_ENABLE || (OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE)
 
+#if OPENTHREAD_CONFIG_DUA_ENABLE && (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2)
+#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_DUA_ENABLE"
+#endif
+
 #include "backbone_router/bbr_leader.hpp"
 #include "coap/coap.hpp"
 #include "coap/coap_message.hpp"
@@ -119,11 +123,11 @@
      *
      * @param[in]  aIid        A reference to the Interface Identifier to set.
      *
-     * @retval OT_ERROR_NONE           Successfully set the Interface Identifier.
-     * @retval OT_ERROR_INVALID_ARGS   The specified Interface Identifier is reserved.
+     * @retval kErrorNone          Successfully set the Interface Identifier.
+     * @retval kErrorInvalidArgs   The specified Interface Identifier is reserved.
      *
      */
-    otError SetFixedDuaInterfaceIdentifier(const Ip6::InterfaceIdentifier &aIid);
+    Error SetFixedDuaInterfaceIdentifier(const Ip6::InterfaceIdentifier &aIid);
 
     /**
      * This method clears the Interface Identifier manually specified for the Thread Domain Unicast Address.
@@ -174,8 +178,8 @@
     };
 
 #if OPENTHREAD_CONFIG_DUA_ENABLE
-    otError GenerateDomainUnicastAddressIid(void);
-    otError Store(void);
+    Error GenerateDomainUnicastAddressIid(void);
+    Error Store(void);
 
     void AddDomainUnicastAddress(void);
     void RemoveDomainUnicastAddress(void);
@@ -190,20 +194,17 @@
 
     void HandleTimeTick(void);
 
-    static void HandleRegistrationTask(Tasklet &aTasklet) { aTasklet.GetOwner<DuaManager>().PerformNextRegistration(); }
+    static void HandleRegistrationTask(Tasklet &aTasklet);
 
     void UpdateTimeTickerRegistration(void);
 
-    static void HandleDuaResponse(void *               aContext,
-                                  otMessage *          aMessage,
-                                  const otMessageInfo *aMessageInfo,
-                                  otError              aResult)
+    static void HandleDuaResponse(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo, Error aResult)
     {
         static_cast<DuaManager *>(aContext)->HandleDuaResponse(
             *static_cast<Coap::Message *>(aMessage), *static_cast<const Ip6::MessageInfo *>(aMessageInfo), aResult);
     }
 
-    void HandleDuaResponse(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo, otError aResult);
+    void HandleDuaResponse(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo, Error aResult);
 
     static void HandleDuaNotification(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo)
     {
@@ -211,8 +212,8 @@
             *static_cast<Coap::Message *>(aMessage), *static_cast<const Ip6::MessageInfo *>(aMessageInfo));
     }
 
-    void    HandleDuaNotification(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
-    otError ProcessDuaResponse(Coap::Message &aMessage);
+    void  HandleDuaNotification(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    Error ProcessDuaResponse(Coap::Message &aMessage);
 
     void PerformNextRegistration(void);
     void UpdateReregistrationDelay(void);
diff --git a/src/core/thread/energy_scan_server.cpp b/src/core/thread/energy_scan_server.cpp
index e6d1e7d..dbf8598 100644
--- a/src/core/thread/energy_scan_server.cpp
+++ b/src/core/thread/energy_scan_server.cpp
@@ -55,7 +55,7 @@
     , mCount(0)
     , mActive(false)
     , mScanResultsLength(0)
-    , mTimer(aInstance, EnergyScanServer::HandleTimer, this)
+    , mTimer(aInstance, EnergyScanServer::HandleTimer)
     , mEnergyScan(UriPath::kEnergyScan, &EnergyScanServer::HandleRequest, this)
 {
     Get<Tmf::TmfAgent>().AddResource(mEnergyScan);
@@ -106,7 +106,7 @@
 
 void EnergyScanServer::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<EnergyScanServer>().HandleTimer();
+    aTimer.Get<EnergyScanServer>().HandleTimer();
 }
 
 void EnergyScanServer::HandleTimer(void)
@@ -169,13 +169,13 @@
 
 void EnergyScanServer::SendReport(void)
 {
-    otError                 error = OT_ERROR_NONE;
+    Error                   error = kErrorNone;
     MeshCoP::ChannelMaskTlv channelMask;
     MeshCoP::EnergyListTlv  energyList;
     Ip6::MessageInfo        messageInfo;
     Coap::Message *         message;
 
-    VerifyOrExit((message = MeshCoP::NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = MeshCoP::NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsConfirmablePost(UriPath::kEnergyReport));
     SuccessOrExit(error = message->SetPayloadMarker());
diff --git a/src/core/thread/indirect_sender.cpp b/src/core/thread/indirect_sender.cpp
index 1ad113c..bea9817 100644
--- a/src/core/thread/indirect_sender.cpp
+++ b/src/core/thread/indirect_sender.cpp
@@ -65,7 +65,7 @@
     , mEnabled(false)
     , mSourceMatchController(aInstance)
     , mDataPollHandler(aInstance)
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     , mCslTxScheduler(aInstance)
 #endif
 {
@@ -82,7 +82,7 @@
     }
 
     mDataPollHandler.Clear();
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     mCslTxScheduler.Clear();
 #endif
 
@@ -109,6 +109,7 @@
         if (supervisionMessage != nullptr)
         {
             IgnoreError(RemoveMessageFromSleepyChild(*supervisionMessage, aChild));
+            Get<MeshForwarder>().RemoveMessageIfNoPendingTx(*supervisionMessage);
         }
     }
 
@@ -118,12 +119,12 @@
     return;
 }
 
-otError IndirectSender::RemoveMessageFromSleepyChild(Message &aMessage, Child &aChild)
+Error IndirectSender::RemoveMessageFromSleepyChild(Message &aMessage, Child &aChild)
 {
-    otError  error      = OT_ERROR_NONE;
+    Error    error      = kErrorNone;
     uint16_t childIndex = Get<ChildTable>().GetChildIndex(aChild);
 
-    VerifyOrExit(aMessage.GetChildMask(childIndex), error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(aMessage.GetChildMask(childIndex), error = kErrorNotFound);
 
     aMessage.ClearChildMask(childIndex);
     mSourceMatchController.DecrementMessageCount(aChild);
@@ -147,23 +148,14 @@
 
         message->ClearChildMask(Get<ChildTable>().GetChildIndex(aChild));
 
-        if (!message->IsChildPending() && !message->GetDirectTransmission())
-        {
-            if (Get<MeshForwarder>().mSendMessage == message)
-            {
-                Get<MeshForwarder>().mSendMessage = nullptr;
-            }
-
-            Get<MeshForwarder>().mSendQueue.Dequeue(*message);
-            message->Free();
-        }
+        Get<MeshForwarder>().RemoveMessageIfNoPendingTx(*message);
     }
 
     aChild.SetIndirectMessage(nullptr);
     mSourceMatchController.ResetMessageCount(aChild);
 
     mDataPollHandler.RequestFrameChange(DataPollHandler::kPurgeFrame, aChild);
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     mCslTxScheduler.Update();
 #endif
 
@@ -208,7 +200,7 @@
         mSourceMatchController.ResetMessageCount(aChild);
 
         mDataPollHandler.RequestFrameChange(DataPollHandler::kPurgeFrame, aChild);
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
         mCslTxScheduler.Update();
 #endif
     }
@@ -263,7 +255,7 @@
 
         aChild.SetWaitingForMessageUpdate(true);
         mDataPollHandler.RequestFrameChange(DataPollHandler::kPurgeFrame, aChild);
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
         mCslTxScheduler.Update();
 #endif
 
@@ -295,7 +287,7 @@
 
     aChild.SetWaitingForMessageUpdate(true);
     mDataPollHandler.RequestFrameChange(DataPollHandler::kReplaceFrame, aChild);
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     mCslTxScheduler.Update();
 #endif
 
@@ -321,7 +313,7 @@
     aChild.SetIndirectFragmentOffset(0);
     aChild.SetIndirectTxSuccess(true);
 
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     mCslTxScheduler.Update();
 #endif
 
@@ -332,20 +324,21 @@
         mDataPollHandler.HandleNewFrame(aChild);
 
         aChild.GetMacAddress(childAddress);
-        Get<MeshForwarder>().LogMessage(MeshForwarder::kMessagePrepareIndirect, *message, &childAddress, OT_ERROR_NONE);
+        Get<MeshForwarder>().LogMessage(MeshForwarder::kMessagePrepareIndirect, *message, &childAddress, kErrorNone);
     }
 }
 
-otError IndirectSender::PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &aContext, Child &aChild)
+Error IndirectSender::PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &aContext, Child &aChild)
 {
-    otError  error   = OT_ERROR_NONE;
+    Error    error   = kErrorNone;
     Message *message = aChild.GetIndirectMessage();
 
-    VerifyOrExit(mEnabled, error = OT_ERROR_ABORT);
+    VerifyOrExit(mEnabled, error = kErrorAbort);
 
     if (message == nullptr)
     {
         PrepareEmptyFrame(aFrame, aChild, /* aAckRequest */ true);
+        aContext.mMessageNextOffset = 0;
         ExitNow();
     }
 
@@ -422,7 +415,7 @@
 
 void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame,
                                             const FrameContext &aContext,
-                                            otError             aError,
+                                            Error               aError,
                                             Child &             aChild)
 {
     Message *message    = aChild.GetIndirectMessage();
@@ -430,15 +423,33 @@
 
     VerifyOrExit(mEnabled);
 
+    if (aError == kErrorNone)
+    {
+        Get<Utils::ChildSupervisor>().UpdateOnSend(aChild);
+    }
+
+    // A zero `nextOffset` indicates that the sent frame is an empty
+    // frame generated by `PrepareFrameForChild()` when there was no
+    // indirect message in the send queue for the child. This can happen
+    // in the (not common) case where the radio platform does not
+    // support the "source address match" feature and always includes
+    // "frame pending" flag in acks to data poll frames. In such a case,
+    // `IndirectSender` prepares and sends an empty frame to the child
+    // after it sends a data poll. Here in `HandleSentFrameToChild()` we
+    // exit quickly if we detect the "send done" is for the empty frame
+    // to ensure we do not update any newly added indirect message after
+    // preparing the empty frame.
+
+    VerifyOrExit(nextOffset != 0);
+
     switch (aError)
     {
-    case OT_ERROR_NONE:
-        Get<Utils::ChildSupervisor>().UpdateOnSend(aChild);
+    case kErrorNone:
         break;
 
-    case OT_ERROR_NO_ACK:
-    case OT_ERROR_CHANNEL_ACCESS_FAILURE:
-    case OT_ERROR_ABORT:
+    case kErrorNoAck:
+    case kErrorChannelAccessFailure:
+    case kErrorAbort:
 
         aChild.SetIndirectTxSuccess(false);
 
@@ -463,7 +474,7 @@
     {
         aChild.SetIndirectFragmentOffset(nextOffset);
         mDataPollHandler.HandleNewFrame(aChild);
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
         mCslTxScheduler.Update();
 #endif
         ExitNow();
@@ -473,7 +484,7 @@
     {
         // The indirect tx of this message to the child is done.
 
-        otError      txError    = aError;
+        Error        txError    = aError;
         uint16_t     childIndex = Get<ChildTable>().GetChildIndex(aChild);
         Mac::Address macDest;
 
@@ -501,9 +512,9 @@
         // represents the error status of the last fragment frame
         // transmission.
 
-        if (!aChild.GetIndirectTxSuccess() && (txError == OT_ERROR_NONE))
+        if (!aChild.GetIndirectTxSuccess() && (txError == kErrorNone))
         {
-            txError = OT_ERROR_FAILED;
+            txError = kErrorFailed;
         }
 #endif
 
@@ -531,11 +542,7 @@
             mSourceMatchController.DecrementMessageCount(aChild);
         }
 
-        if (!message->GetDirectTransmission() && !message->IsChildPending())
-        {
-            Get<MeshForwarder>().mSendQueue.Dequeue(*message);
-            message->Free();
-        }
+        Get<MeshForwarder>().RemoveMessageIfNoPendingTx(*message);
     }
 
     UpdateIndirectMessage(aChild);
diff --git a/src/core/thread/indirect_sender.hpp b/src/core/thread/indirect_sender.hpp
index f0bca76..53cb7f1 100644
--- a/src/core/thread/indirect_sender.hpp
+++ b/src/core/thread/indirect_sender.hpp
@@ -67,7 +67,7 @@
 {
     friend class Instance;
     friend class DataPollHandler::Callbacks;
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     friend class CslTxScheduler::Callbacks;
 #endif
 
@@ -168,11 +168,11 @@
      * @param[in] aMessage  The message to update.
      * @param[in] aChild    The (sleepy) child for indirect transmission.
      *
-     * @retval OT_ERROR_NONE           Successfully removed the message for indirect transmission.
-     * @retval OT_ERROR_NOT_FOUND      The message was not scheduled for indirect transmission to the child.
+     * @retval kErrorNone          Successfully removed the message for indirect transmission.
+     * @retval kErrorNotFound      The message was not scheduled for indirect transmission to the child.
      *
      */
-    otError RemoveMessageFromSleepyChild(Message &aMessage, Child &aChild);
+    Error RemoveMessageFromSleepyChild(Message &aMessage, Child &aChild);
 
     /**
      * This method removes all added messages for a specific child and frees message (with no indirect/direct tx).
@@ -211,12 +211,9 @@
     };
 
     // Callbacks from DataPollHandler
-    otError PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &aContext, Child &aChild);
-    void    HandleSentFrameToChild(const Mac::TxFrame &aFrame,
-                                   const FrameContext &aContext,
-                                   otError             aError,
-                                   Child &             aChild);
-    void    HandleFrameChangeDone(Child &aChild);
+    Error PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &aContext, Child &aChild);
+    void  HandleSentFrameToChild(const Mac::TxFrame &aFrame, const FrameContext &aContext, Error aError, Child &aChild);
+    void  HandleFrameChangeDone(Child &aChild);
 
     void     UpdateIndirectMessage(Child &aChild);
     Message *FindIndirectMessage(Child &aChild, bool aSupervisionTypeOnly = false);
@@ -228,7 +225,7 @@
     bool                  mEnabled;
     SourceMatchController mSourceMatchController;
     DataPollHandler       mDataPollHandler;
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     CslTxScheduler mCslTxScheduler;
 #endif
 };
diff --git a/src/core/thread/key_manager.cpp b/src/core/thread/key_manager.cpp
index aa46902..ce8a19a 100644
--- a/src/core/thread/key_manager.cpp
+++ b/src/core/thread/key_manager.cpp
@@ -48,25 +48,6 @@
     'T', 'h', 'r', 'e', 'a', 'd',
 };
 
-const otMasterKey KeyManager::kDefaultMasterKey = {{
-    0x00,
-    0x11,
-    0x22,
-    0x33,
-    0x44,
-    0x55,
-    0x66,
-    0x77,
-    0x88,
-    0x99,
-    0xaa,
-    0xbb,
-    0xcc,
-    0xdd,
-    0xee,
-    0xff,
-}};
-
 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
 const uint8_t KeyManager::kHkdfExtractSaltString[] = {'T', 'h', 'r', 'e', 'a', 'd', 'S', 'e', 'q', 'u', 'e', 'n',
                                                       'c', 'e', 'M', 'a', 's', 't', 'e', 'r', 'K', 'e', 'y'};
@@ -85,13 +66,17 @@
     , mKeyRotationTime(kDefaultKeyRotationTime)
     , mKeySwitchGuardTime(kDefaultKeySwitchGuardTime)
     , mKeySwitchGuardEnabled(false)
-    , mKeyRotationTimer(aInstance, KeyManager::HandleKeyRotationTimer, this)
+    , mKeyRotationTimer(aInstance, KeyManager::HandleKeyRotationTimer)
     , mKekFrameCounter(0)
     , mSecurityPolicyFlags(kDefaultSecurityPolicyFlags)
     , mIsPskcSet(false)
 {
+    Error error = mMasterKey.GenerateRandom();
+
+    OT_ASSERT(error == kErrorNone);
+    OT_UNUSED_VARIABLE(error);
+
     mMacFrameCounters.Reset();
-    mMasterKey = static_cast<const MasterKey &>(kDefaultMasterKey);
     mPskc.Clear();
 }
 
@@ -114,9 +99,9 @@
 }
 #endif // OPENTHREAD_MTD || OPENTHREAD_FTD
 
-otError KeyManager::SetMasterKey(const MasterKey &aKey)
+Error KeyManager::SetMasterKey(const MasterKey &aKey)
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     Router *parent;
 
     SuccessOrExit(Get<Notifier>().Update(mMasterKey, aKey, kEventMasterKeyChanged));
@@ -163,8 +148,8 @@
     hmac.Start(mMasterKey.m8, sizeof(mMasterKey.m8));
 
     Encoding::BigEndian::WriteUint32(aKeySequence, keySequenceBytes);
-    hmac.Update(keySequenceBytes, sizeof(keySequenceBytes));
-    hmac.Update(kThreadString, sizeof(kThreadString));
+    hmac.Update(keySequenceBytes);
+    hmac.Update(kThreadString);
 
     hmac.Finish(aHashKeys.mHash);
 }
@@ -313,11 +298,11 @@
     mKekFrameCounter = 0;
 }
 
-otError KeyManager::SetKeyRotation(uint32_t aKeyRotation)
+Error KeyManager::SetKeyRotation(uint32_t aKeyRotation)
 {
-    otError result = OT_ERROR_NONE;
+    Error result = kErrorNone;
 
-    VerifyOrExit(aKeyRotation >= static_cast<uint32_t>(kMinKeyRotationTime), result = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aKeyRotation >= static_cast<uint32_t>(kMinKeyRotationTime), result = kErrorInvalidArgs);
 
     mKeyRotationTime = aKeyRotation;
 
@@ -338,7 +323,7 @@
 
 void KeyManager::HandleKeyRotationTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<KeyManager>().HandleKeyRotationTimer();
+    aTimer.Get<KeyManager>().HandleKeyRotationTimer();
 }
 
 void KeyManager::HandleKeyRotationTimer(void)
diff --git a/src/core/thread/key_manager.hpp b/src/core/thread/key_manager.hpp
index d73d9c0..9e661ff 100644
--- a/src/core/thread/key_manager.hpp
+++ b/src/core/thread/key_manager.hpp
@@ -73,11 +73,11 @@
     /**
      * This method generates a cryptographically secure random sequence to populate the Thread Master Key.
      *
-     * @retval OT_ERROR_NONE     Successfully generated a random Thread Master Key.
-     * @retval OT_ERROR_FAILED   Failed to generate random sequence.
+     * @retval kErrorNone     Successfully generated a random Thread Master Key.
+     * @retval kErrorFailed   Failed to generate random sequence.
      *
      */
-    otError GenerateRandom(void) { return Random::Crypto::FillBuffer(m8, sizeof(m8)); }
+    Error GenerateRandom(void) { return Random::Crypto::FillBuffer(m8, sizeof(m8)); }
 #endif
 } OT_TOOL_PACKED_END;
 
@@ -93,10 +93,10 @@
     /**
      * This method generates a cryptographically secure random sequence to populate the Thread PSKc.
      *
-     * @retval OT_ERROR_NONE  Successfully generated a random Thread PSKc.
+     * @retval kErrorNone  Successfully generated a random Thread PSKc.
      *
      */
-    otError GenerateRandom(void) { return Random::Crypto::FillBuffer(m8, sizeof(Pskc)); }
+    Error GenerateRandom(void) { return Random::Crypto::FillBuffer(m8, sizeof(Pskc)); }
 #endif
 
 } OT_TOOL_PACKED_END;
@@ -158,11 +158,11 @@
      *
      * @param[in]  aKey        A Thread Master Key.
      *
-     * @retval OT_ERROR_NONE          Successfully set the Thread Master Key.
-     * @retval OT_ERROR_INVALID_ARGS  The @p aKeyLength value was invalid.
+     * @retval kErrorNone         Successfully set the Thread Master Key.
+     * @retval kErrorInvalidArgs  The @p aKeyLength value was invalid.
      *
      */
-    otError SetMasterKey(const MasterKey &aKey);
+    Error SetMasterKey(const MasterKey &aKey);
 
 #if OPENTHREAD_FTD || OPENTHREAD_MTD
     /**
@@ -382,11 +382,11 @@
      *
      * @param[in]  aKeyRotation  The KeyRotation value in hours.
      *
-     * @retval  OT_ERROR_NONE          KeyRotation time updated.
-     * @retval  OT_ERROR_INVALID_ARGS  @p aKeyRotation is out of range.
+     * @retval  kErrorNone          KeyRotation time updated.
+     * @retval  kErrorInvalidArgs   @p aKeyRotation is out of range.
      *
      */
-    otError SetKeyRotation(uint32_t aKeyRotation);
+    Error SetKeyRotation(uint32_t aKeyRotation);
 
     /**
      * This method returns the KeySwitchGuardTime.
@@ -531,8 +531,7 @@
     static void HandleKeyRotationTimer(Timer &aTimer);
     void        HandleKeyRotationTimer(void);
 
-    static const uint8_t     kThreadString[];
-    static const otMasterKey kDefaultMasterKey;
+    static const uint8_t kThreadString[];
 
 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
     static const uint8_t kHkdfExtractSaltString[];
diff --git a/src/core/thread/link_metrics.cpp b/src/core/thread/link_metrics.cpp
index 95f6bea..d6d98d3 100644
--- a/src/core/thread/link_metrics.cpp
+++ b/src/core/thread/link_metrics.cpp
@@ -112,17 +112,17 @@
 {
 }
 
-otError LinkMetrics::LinkMetricsQuery(const Ip6::Address & aDestination,
-                                      uint8_t              aSeriesId,
-                                      const otLinkMetrics *aLinkMetricsFlags)
+Error LinkMetrics::LinkMetricsQuery(const Ip6::Address & aDestination,
+                                    uint8_t              aSeriesId,
+                                    const otLinkMetrics *aLinkMetricsFlags)
 {
-    otError                error;
+    Error                  error;
     LinkMetricsTypeIdFlags typeIdFlags[kMaxTypeIdFlags];
     uint8_t                typeIdFlagsCount = 0;
     Neighbor *             neighbor         = GetNeighborFromLinkLocalAddr(aDestination);
 
-    VerifyOrExit(neighbor != nullptr, error = OT_ERROR_UNKNOWN_NEIGHBOR);
-    VerifyOrExit(neighbor->IsThreadVersion1p2(), error = OT_ERROR_NOT_CAPABLE);
+    VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor);
+    VerifyOrExit(neighbor->IsThreadVersion1p2(), error = kErrorNotCapable);
 
     if (aLinkMetricsFlags != nullptr)
     {
@@ -131,7 +131,7 @@
 
     if (aSeriesId != 0)
     {
-        VerifyOrExit(typeIdFlagsCount == 0, error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit(typeIdFlagsCount == 0, error = kErrorInvalidArgs);
     }
 
     error = SendLinkMetricsQuery(aDestination, aSeriesId, typeIdFlags, typeIdFlagsCount);
@@ -140,12 +140,12 @@
     return error;
 }
 
-otError LinkMetrics::SendMgmtRequestForwardTrackingSeries(const Ip6::Address &            aDestination,
-                                                          uint8_t                         aSeriesId,
-                                                          const otLinkMetricsSeriesFlags &aSeriesFlags,
-                                                          const otLinkMetrics *           aLinkMetricsFlags)
+Error LinkMetrics::SendMgmtRequestForwardTrackingSeries(const Ip6::Address &            aDestination,
+                                                        uint8_t                         aSeriesId,
+                                                        const otLinkMetricsSeriesFlags &aSeriesFlags,
+                                                        const otLinkMetrics *           aLinkMetricsFlags)
 {
-    otError      error = OT_ERROR_NONE;
+    Error        error = kErrorNone;
     uint8_t      subTlvs[sizeof(Tlv) + sizeof(uint8_t) * 2 + sizeof(LinkMetricsTypeIdFlags) * kMaxTypeIdFlags];
     Tlv *        forwardProbingRegistrationSubTlv = reinterpret_cast<Tlv *>(subTlvs);
     SeriesFlags *seriesFlags       = reinterpret_cast<SeriesFlags *>(subTlvs + sizeof(Tlv) + sizeof(aSeriesId));
@@ -153,8 +153,8 @@
     uint8_t      typeIdFlagsCount  = 0;
     Neighbor *   neighbor          = GetNeighborFromLinkLocalAddr(aDestination);
 
-    VerifyOrExit(neighbor != nullptr, error = OT_ERROR_UNKNOWN_NEIGHBOR);
-    VerifyOrExit(neighbor->IsThreadVersion1p2(), error = OT_ERROR_NOT_CAPABLE);
+    VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor);
+    VerifyOrExit(neighbor->IsThreadVersion1p2(), error = kErrorNotCapable);
 
     // Directly transform `aLinkMetricsFlags` into LinkMetricsTypeIdFlags and put them into `subTlvs`
     if (aLinkMetricsFlags != nullptr)
@@ -163,7 +163,7 @@
             reinterpret_cast<LinkMetricsTypeIdFlags *>(subTlvs + typeIdFlagsOffset), *aLinkMetricsFlags);
     }
 
-    VerifyOrExit(aSeriesId > kQueryIdSingleProbe, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aSeriesId > kQueryIdSingleProbe, error = kErrorInvalidArgs);
 
     forwardProbingRegistrationSubTlv->SetType(kForwardProbingRegistration);
     forwardProbingRegistrationSubTlv->SetLength(
@@ -179,26 +179,25 @@
                                                                    forwardProbingRegistrationSubTlv->GetSize());
 
 exit:
-    otLogDebgMle("SendMgmtRequestForwardTrackingSeries, error:%s, Series ID:%u", otThreadErrorToString(error),
-                 aSeriesId);
+    otLogDebgMle("SendMgmtRequestForwardTrackingSeries, error:%s, Series ID:%u", ErrorToString(error), aSeriesId);
     return error;
 }
 
-otError LinkMetrics::SendMgmtRequestEnhAckProbing(const Ip6::Address &           aDestination,
-                                                  const otLinkMetricsEnhAckFlags aEnhAckFlags,
-                                                  const otLinkMetrics *          aLinkMetricsFlags)
+Error LinkMetrics::SendMgmtRequestEnhAckProbing(const Ip6::Address &           aDestination,
+                                                const otLinkMetricsEnhAckFlags aEnhAckFlags,
+                                                const otLinkMetrics *          aLinkMetricsFlags)
 {
-    otError                              error = OT_ERROR_NONE;
+    Error                                error = kErrorNone;
     EnhAckLinkMetricsConfigurationSubTlv enhAckLinkMetricsConfigurationSubTlv;
     Mac::Address                         macAddress;
     Neighbor *                           neighbor = GetNeighborFromLinkLocalAddr(aDestination);
 
-    VerifyOrExit(neighbor != nullptr, error = OT_ERROR_UNKNOWN_NEIGHBOR);
-    VerifyOrExit(neighbor->IsThreadVersion1p2(), error = OT_ERROR_NOT_CAPABLE);
+    VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor);
+    VerifyOrExit(neighbor->IsThreadVersion1p2(), error = kErrorNotCapable);
 
     if (aEnhAckFlags == OT_LINK_METRICS_ENH_ACK_CLEAR)
     {
-        VerifyOrExit(aLinkMetricsFlags == nullptr, error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit(aLinkMetricsFlags == nullptr, error = kErrorInvalidArgs);
     }
 
     enhAckLinkMetricsConfigurationSubTlv.SetEnhAckFlags(aEnhAckFlags);
@@ -227,28 +226,28 @@
     return error;
 }
 
-otError LinkMetrics::SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t aLength)
+Error LinkMetrics::SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t aLength)
 {
-    otError   error = OT_ERROR_NONE;
+    Error     error = kErrorNone;
     uint8_t   buf[kLinkProbeMaxLen];
     Neighbor *neighbor = GetNeighborFromLinkLocalAddr(aDestination);
 
-    VerifyOrExit(neighbor != nullptr, error = OT_ERROR_UNKNOWN_NEIGHBOR);
-    VerifyOrExit(neighbor->IsThreadVersion1p2(), error = OT_ERROR_NOT_CAPABLE);
+    VerifyOrExit(neighbor != nullptr, error = kErrorUnknownNeighbor);
+    VerifyOrExit(neighbor->IsThreadVersion1p2(), error = kErrorNotCapable);
 
     VerifyOrExit(aLength <= LinkMetrics::kLinkProbeMaxLen && aSeriesId != kQueryIdSingleProbe &&
                      aSeriesId != kSeriesIdAllSeries,
-                 error = OT_ERROR_INVALID_ARGS);
+                 error = kErrorInvalidArgs);
 
     error = Get<Mle::MleRouter>().SendLinkProbe(aDestination, aSeriesId, buf, aLength);
 exit:
-    otLogDebgMle("SendLinkProbe, error:%s, Series ID:%u", otThreadErrorToString(error), aSeriesId);
+    otLogDebgMle("SendLinkProbe, error:%s, Series ID:%u", ErrorToString(error), aSeriesId);
     return error;
 }
 
-otError LinkMetrics::AppendLinkMetricsReport(Message &aMessage, const Message &aRequestMessage, Neighbor &aNeighbor)
+Error LinkMetrics::AppendLinkMetricsReport(Message &aMessage, const Message &aRequestMessage, Neighbor &aNeighbor)
 {
-    otError             error = OT_ERROR_NONE;
+    Error               error = kErrorNone;
     Tlv                 tlv;
     uint8_t             queryId;
     bool                hasQueryId  = false;
@@ -289,7 +288,7 @@
         offset += tlv.GetSize();
     }
 
-    VerifyOrExit(hasQueryId, error = OT_ERROR_PARSE);
+    VerifyOrExit(hasQueryId, error = kErrorParse);
 
     // Link Metrics Report TLV
     tlv.SetType(Mle::Tlv::kLinkMetricsReport);
@@ -339,15 +338,15 @@
     aMessage.Write(startOffset, tlv);
 
 exit:
-    otLogDebgMle("AppendLinkMetricsReport, error:%s", otThreadErrorToString(error));
+    otLogDebgMle("AppendLinkMetricsReport, error:%s", ErrorToString(error));
     return error;
 }
 
-otError LinkMetrics::HandleLinkMetricsManagementRequest(const Message &    aMessage,
-                                                        Neighbor &         aNeighbor,
-                                                        LinkMetricsStatus &aStatus)
+Error LinkMetrics::HandleLinkMetricsManagementRequest(const Message &    aMessage,
+                                                      Neighbor &         aNeighbor,
+                                                      LinkMetricsStatus &aStatus)
 {
-    otError                error = OT_ERROR_NONE;
+    Error                  error = kErrorNone;
     Tlv                    tlv;
     uint8_t                seriesId;
     SeriesFlags            seriesFlags;
@@ -371,8 +370,8 @@
         switch (tlv.GetType())
         {
         case kForwardProbingRegistration:
-            VerifyOrExit(!hasForwardProbingRegistrationTlv && !hasEnhAckProbingTlv, error = OT_ERROR_PARSE);
-            VerifyOrExit(tlv.GetLength() >= sizeof(seriesId) + sizeof(seriesFlags), error = OT_ERROR_PARSE);
+            VerifyOrExit(!hasForwardProbingRegistrationTlv && !hasEnhAckProbingTlv, error = kErrorParse);
+            VerifyOrExit(tlv.GetLength() >= sizeof(seriesId) + sizeof(seriesFlags), error = kErrorParse);
             SuccessOrExit(aMessage.Read(pos, seriesId));
             pos += sizeof(seriesId);
             SuccessOrExit(aMessage.Read(pos, seriesFlags));
@@ -383,8 +382,8 @@
             break;
 
         case kEnhancedACKConfiguration:
-            VerifyOrExit(!hasForwardProbingRegistrationTlv && !hasEnhAckProbingTlv, error = OT_ERROR_PARSE);
-            VerifyOrExit(tlv.GetLength() >= sizeof(LinkMetricsEnhAckFlags), error = OT_ERROR_PARSE);
+            VerifyOrExit(!hasForwardProbingRegistrationTlv && !hasEnhAckProbingTlv, error = kErrorParse);
+            VerifyOrExit(tlv.GetLength() >= sizeof(LinkMetricsEnhAckFlags), error = kErrorParse);
             SuccessOrExit(aMessage.Read(pos, enhAckFlags));
             pos += sizeof(enhAckFlags);
             SuccessOrExit(error = ReadTypeIdFlagsFromMessage(
@@ -412,9 +411,9 @@
     return error;
 }
 
-otError LinkMetrics::HandleLinkMetricsManagementResponse(const Message &aMessage, const Ip6::Address &aAddress)
+Error LinkMetrics::HandleLinkMetricsManagementResponse(const Message &aMessage, const Ip6::Address &aAddress)
 {
-    otError           error = OT_ERROR_NONE;
+    Error             error = kErrorNone;
     Tlv               tlv;
     uint16_t          offset;
     uint16_t          length;
@@ -433,8 +432,8 @@
         switch (tlv.GetType())
         {
         case kLinkMetricsStatus:
-            VerifyOrExit(!hasStatus, error = OT_ERROR_PARSE);
-            VerifyOrExit(tlv.GetLength() == sizeof(status), error = OT_ERROR_PARSE);
+            VerifyOrExit(!hasStatus, error = kErrorParse);
+            VerifyOrExit(tlv.GetLength() == sizeof(status), error = kErrorParse);
             SuccessOrExit(aMessage.Read(offset + index + sizeof(tlv), status));
             hasStatus = true;
             break;
@@ -446,7 +445,7 @@
         index += tlv.GetSize();
     }
 
-    VerifyOrExit(hasStatus, error = OT_ERROR_PARSE);
+    VerifyOrExit(hasStatus, error = kErrorParse);
 
     mLinkMetricsMgmtResponseCallback(&aAddress, status, mLinkMetricsMgmtResponseCallbackContext);
 
@@ -459,7 +458,7 @@
                                           uint16_t            aLength,
                                           const Ip6::Address &aAddress)
 {
-    otError                error = OT_ERROR_NONE;
+    Error                  error = kErrorNone;
     otLinkMetricsValues    metricsValues;
     uint8_t                metricsRawValue;
     uint16_t               pos    = aOffset;
@@ -482,14 +481,14 @@
         VerifyOrExit(tlv.GetType() == kLinkMetricsReportSub);
         pos += sizeof(Tlv);
 
-        VerifyOrExit(pos + tlv.GetLength() <= endPos, error = OT_ERROR_PARSE);
+        VerifyOrExit(pos + tlv.GetLength() <= endPos, error = kErrorParse);
 
         switch (tlv.GetType())
         {
         case kLinkMetricsStatus:
             VerifyOrExit(!hasStatus && !hasReport,
-                         error = OT_ERROR_DROP); // There should be either: one Status TLV or some Report-Sub TLVs
-            VerifyOrExit(tlv.GetLength() == sizeof(status), error = OT_ERROR_PARSE);
+                         error = kErrorDrop); // There should be either: one Status TLV or some Report-Sub TLVs
+            VerifyOrExit(tlv.GetLength() == sizeof(status), error = kErrorParse);
             SuccessOrExit(aMessage.Read(pos, status));
             hasStatus = true;
             pos += sizeof(status);
@@ -497,8 +496,8 @@
 
         case kLinkMetricsReportSub:
             VerifyOrExit(!hasStatus,
-                         error = OT_ERROR_DROP); // There shouldn't be any Report-Sub TLV when there's a Status TLV
-            VerifyOrExit(tlv.GetLength() == sizeof(typeIdFlags), error = OT_ERROR_PARSE);
+                         error = kErrorDrop); // There shouldn't be any Report-Sub TLV when there's a Status TLV
+            VerifyOrExit(tlv.GetLength() > sizeof(typeIdFlags), error = kErrorParse);
             SuccessOrExit(aMessage.Read(pos, typeIdFlags));
             if (typeIdFlags.IsExtendedFlagSet())
             {
@@ -560,20 +559,20 @@
     }
 
 exit:
-    otLogDebgMle("HandleLinkMetricsReport, error:%s", otThreadErrorToString(error));
+    otLogDebgMle("HandleLinkMetricsReport, error:%s", ErrorToString(error));
     return;
 }
 
-otError LinkMetrics::HandleLinkProbe(const Message &aMessage, uint8_t &aSeriesId)
+Error LinkMetrics::HandleLinkProbe(const Message &aMessage, uint8_t &aSeriesId)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     uint16_t offset;
     uint16_t length;
 
     SuccessOrExit(error = Tlv::FindTlvValueOffset(aMessage, Mle::Tlv::Type::kLinkProbe, offset, length));
-    VerifyOrExit(length >= sizeof(aSeriesId), error = OT_ERROR_PARSE);
+    VerifyOrExit(length >= sizeof(aSeriesId), error = kErrorParse);
     SuccessOrExit(error = aMessage.Read(offset, aSeriesId));
-    VerifyOrExit(aSeriesId >= kQueryIdSingleProbe && aSeriesId <= kSeriesIdAllSeries, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aSeriesId >= kQueryIdSingleProbe && aSeriesId <= kSeriesIdAllSeries, error = kErrorInvalidArgs);
 
 exit:
     return error;
@@ -628,12 +627,12 @@
     return;
 }
 
-otError LinkMetrics::SendLinkMetricsQuery(const Ip6::Address &          aDestination,
-                                          uint8_t                       aSeriesId,
-                                          const LinkMetricsTypeIdFlags *aTypeIdFlags,
-                                          uint8_t                       aTypeIdFlagsCount)
+Error LinkMetrics::SendLinkMetricsQuery(const Ip6::Address &          aDestination,
+                                        uint8_t                       aSeriesId,
+                                        const LinkMetricsTypeIdFlags *aTypeIdFlags,
+                                        uint8_t                       aTypeIdFlagsCount)
 {
-    otError                    error = OT_ERROR_NONE;
+    Error                      error = kErrorNone;
     LinkMetricsQueryOptionsTlv linkMetricsQueryOptionsTlv;
     uint8_t                    length = 0;
     static const uint8_t       tlvs[] = {Mle::Tlv::kLinkMetricsReport};
@@ -719,7 +718,7 @@
                                                                    Neighbor &             aNeighbor)
 {
     LinkMetricsStatus status = kLinkMetricsStatusSuccess;
-    otError           error  = OT_ERROR_NONE;
+    Error             error  = kErrorNone;
 
     VerifyOrExit(!aLinkMetrics.mReserved, status = kLinkMetricsStatusCannotSupportNewSeries);
 
@@ -744,7 +743,7 @@
         status = kLinkMetricsStatusOtherError;
     }
 
-    VerifyOrExit(error == OT_ERROR_NONE, status = kLinkMetricsStatusOtherError);
+    VerifyOrExit(error == kErrorNone, status = kLinkMetricsStatusOtherError);
 
 exit:
     return status;
@@ -763,12 +762,12 @@
     return neighbor;
 }
 
-otError LinkMetrics::ReadTypeIdFlagsFromMessage(const Message &aMessage,
-                                                uint8_t        aStartPos,
-                                                uint8_t        aEndPos,
-                                                otLinkMetrics &aLinkMetrics)
+Error LinkMetrics::ReadTypeIdFlagsFromMessage(const Message &aMessage,
+                                              uint8_t        aStartPos,
+                                              uint8_t        aEndPos,
+                                              otLinkMetrics &aLinkMetrics)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     memset(&aLinkMetrics, 0, sizeof(aLinkMetrics));
 
@@ -781,22 +780,22 @@
         switch (typeIdFlags.GetRawValue())
         {
         case kTypeIdFlagPdu:
-            VerifyOrExit(!aLinkMetrics.mPduCount, error = OT_ERROR_PARSE);
+            VerifyOrExit(!aLinkMetrics.mPduCount, error = kErrorParse);
             aLinkMetrics.mPduCount = true;
             break;
 
         case kTypeIdFlagLqi:
-            VerifyOrExit(!aLinkMetrics.mLqi, error = OT_ERROR_PARSE);
+            VerifyOrExit(!aLinkMetrics.mLqi, error = kErrorParse);
             aLinkMetrics.mLqi = true;
             break;
 
         case kTypeIdFlagLinkMargin:
-            VerifyOrExit(!aLinkMetrics.mLinkMargin, error = OT_ERROR_PARSE);
+            VerifyOrExit(!aLinkMetrics.mLinkMargin, error = kErrorParse);
             aLinkMetrics.mLinkMargin = true;
             break;
 
         case kTypeIdFlagRssi:
-            VerifyOrExit(!aLinkMetrics.mRssi, error = OT_ERROR_PARSE);
+            VerifyOrExit(!aLinkMetrics.mRssi, error = kErrorParse);
             aLinkMetrics.mRssi = true;
             break;
 
@@ -817,11 +816,9 @@
     return error;
 }
 
-otError LinkMetrics::AppendReportSubTlvToMessage(Message &                  aMessage,
-                                                 uint8_t &                  aLength,
-                                                 const otLinkMetricsValues &aValues)
+Error LinkMetrics::AppendReportSubTlvToMessage(Message &aMessage, uint8_t &aLength, const otLinkMetricsValues &aValues)
 {
-    otError                 error = OT_ERROR_NONE;
+    Error                   error = kErrorNone;
     LinkMetricsReportSubTlv metric;
 
     aLength = 0;
@@ -867,10 +864,10 @@
     return error;
 }
 
-otError LinkMetrics::AppendStatusSubTlvToMessage(Message &aMessage, uint8_t &aLength, LinkMetricsStatus aStatus)
+Error LinkMetrics::AppendStatusSubTlvToMessage(Message &aMessage, uint8_t &aLength, LinkMetricsStatus aStatus)
 {
-    otError error = OT_ERROR_NONE;
-    Tlv     statusTlv;
+    Error error = kErrorNone;
+    Tlv   statusTlv;
 
     statusTlv.SetType(kLinkMetricsStatus);
     statusTlv.SetLength(sizeof(uint8_t));
diff --git a/src/core/thread/link_metrics.hpp b/src/core/thread/link_metrics.hpp
index e2d1c37..2ccd2e1 100644
--- a/src/core/thread/link_metrics.hpp
+++ b/src/core/thread/link_metrics.hpp
@@ -38,6 +38,10 @@
 
 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
 
+#if (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2)
+#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE."
+#endif
+
 #include <openthread/ip6.h>
 #include <openthread/link.h>
 
@@ -194,15 +198,13 @@
      * @param[in]  aSeriesId          The Series ID to query, 0 for single probe.
      * @param[in]  aLinkMetricsFlags  A pointer to flags specifying what metrics to query.
      *
-     * @retval OT_ERROR_NONE              Successfully sent a Link Metrics query message.
-     * @retval OT_ERROR_NO_BUFS           Insufficient buffers to generate the MLE Data Request message.
-     * @retval OT_ERROR_INVALID_ARGS      TypeIdFlags are not valid or exceed the count limit.
-     * @retval OT_ERROR_UNKNOWN_NEIGHBOR  @p aDestination is not link-local or the neighbor is not found.
+     * @retval kErrorNone             Successfully sent a Link Metrics query message.
+     * @retval kErrorNoBufs           Insufficient buffers to generate the MLE Data Request message.
+     * @retval kErrorInvalidArgs      TypeIdFlags are not valid or exceed the count limit.
+     * @retval kErrorUnknownNeighbor  @p aDestination is not link-local or the neighbor is not found.
      *
      */
-    otError LinkMetricsQuery(const Ip6::Address & aDestination,
-                             uint8_t              aSeriesId,
-                             const otLinkMetrics *aLinkMetricsFlags);
+    Error LinkMetricsQuery(const Ip6::Address &aDestination, uint8_t aSeriesId, const otLinkMetrics *aLinkMetricsFlags);
 
     /**
      * This method sends an MLE Link Metrics Management Request to configure/clear a Forward Tracking Series.
@@ -213,17 +215,16 @@
      *                               accounted.
      * @param[in] aLinkMetricsFlags  A pointer to flags specifying what metrics to query.
      *
-     * @retval OT_ERROR_NONE              Successfully sent a Link Metrics Management Request message.
-     * @retval OT_ERROR_NO_BUFS           Insufficient buffers to generate the MLE Link Metrics Management Request
-     *                                    message.
-     * @retval OT_ERROR_INVALID_ARGS      @p aSeriesId is not within the valid range.
-     * @retval OT_ERROR_UNKNOWN_NEIGHBOR  @p aDestination is not link-local or the neighbor is not found.
+     * @retval kErrorNone             Successfully sent a Link Metrics Management Request message.
+     * @retval kErrorNoBufs           Insufficient buffers to generate the MLE Link Metrics Management Request message.
+     * @retval kErrorInvalidArgs      @p aSeriesId is not within the valid range.
+     * @retval kErrorUnknownNeighbor  @p aDestination is not link-local or the neighbor is not found.
      *
      */
-    otError SendMgmtRequestForwardTrackingSeries(const Ip6::Address &            aDestination,
-                                                 uint8_t                         aSeriesId,
-                                                 const otLinkMetricsSeriesFlags &aSeriesFlags,
-                                                 const otLinkMetrics *           aLinkMetricsFlags);
+    Error SendMgmtRequestForwardTrackingSeries(const Ip6::Address &            aDestination,
+                                               uint8_t                         aSeriesId,
+                                               const otLinkMetricsSeriesFlags &aSeriesFlags,
+                                               const otLinkMetrics *           aLinkMetricsFlags);
 
     /**
      * This method sends an MLE Link Metrics Management Request to configure/clear a Enhanced-ACK Based Probing.
@@ -234,16 +235,15 @@
      * @param[in] aLinkMetricsFlags  A pointer to flags specifying what metrics to query. Should be `NULL` when
      *                               `aEnhAckFlags` is `0`.
      *
-     * @retval OT_ERROR_NONE              Successfully sent a Link Metrics Management Request message.
-     * @retval OT_ERROR_NO_BUFS           Insufficient buffers to generate the MLE Link Metrics Management Request
-     *                                    message.
-     * @retval OT_ERROR_INVALID_ARGS      @p aEnhAckFlags is not a valid value or @p aLinkMetricsFlags isn't correct.
-     * @retval OT_ERROR_UNKNOWN_NEIGHBOR  @p aDestination is not link-local or the neighbor is not found.
+     * @retval kErrorNone             Successfully sent a Link Metrics Management Request message.
+     * @retval kErrorNoBufs           Insufficient buffers to generate the MLE Link Metrics Management Request message.
+     * @retval kErrorInvalidArgs      @p aEnhAckFlags is not a valid value or @p aLinkMetricsFlags isn't correct.
+     * @retval kErrorUnknownNeighbor  @p aDestination is not link-local or the neighbor is not found.
      *
      */
-    otError SendMgmtRequestEnhAckProbing(const Ip6::Address &     aDestination,
-                                         otLinkMetricsEnhAckFlags aEnhAckFlags,
-                                         const otLinkMetrics *    aLinkMetricsFlags);
+    Error SendMgmtRequestEnhAckProbing(const Ip6::Address &     aDestination,
+                                       otLinkMetricsEnhAckFlags aEnhAckFlags,
+                                       const otLinkMetrics *    aLinkMetricsFlags);
 
     /**
      * This method sends an MLE Link Probe message.
@@ -252,13 +252,13 @@
      * @param[in] aSeriesId       The Series ID which the Probe message targets at.
      * @param[in] aLength         The length of the data payload in Link Probe TLV, [0, 64].
      *
-     * @retval OT_ERROR_NONE              Successfully sent a Link Probe message.
-     * @retval OT_ERROR_NO_BUFS           Insufficient buffers to generate the MLE Link Probe message.
-     * @retval OT_ERROR_INVALID_ARGS      @p aSeriesId or @p aLength is not within the valid range.
-     * @retval OT_ERROR_UNKNOWN_NEIGHBOR  @p aDestination is not link-local or the neighbor is not found.
+     * @retval kErrorNone             Successfully sent a Link Probe message.
+     * @retval kErrorNoBufs           Insufficient buffers to generate the MLE Link Probe message.
+     * @retval kErrorInvalidArgs      @p aSeriesId or @p aLength is not within the valid range.
+     * @retval kErrorUnknownNeighbor  @p aDestination is not link-local or the neighbor is not found.
      *
      */
-    otError SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t aLength);
+    Error SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t aLength);
 
     /**
      * This method appends a Link Metrics Report to a message according to the Link Metrics query.
@@ -267,12 +267,12 @@
      * @param[in]   aRequestMessage    A reference to the message of the Data Request.
      * @param[in]   aNeighbor          A reference to the neighbor who queries the report.
      *
-     * @retval OT_ERROR_NONE          Successfully appended the Thread Discovery TLV.
-     * @retval OT_ERROR_PARSE         Cannot parse query sub TLV successfully.
-     * @retval OT_ERROR_INVALID_ARGS  QueryId is invalid or any Type ID is invalid.
+     * @retval kErrorNone         Successfully appended the Thread Discovery TLV.
+     * @retval kErrorParse        Cannot parse query sub TLV successfully.
+     * @retval kErrorInvalidArgs  QueryId is invalid or any Type ID is invalid.
      *
      */
-    otError AppendLinkMetricsReport(Message &aMessage, const Message &aRequestMessage, Neighbor &aNeighbor);
+    Error AppendLinkMetricsReport(Message &aMessage, const Message &aRequestMessage, Neighbor &aNeighbor);
 
     /**
      * This method handles the received Link Metrics Management Request contained in @p aMessage and return a status.
@@ -281,13 +281,11 @@
      * @param[in]   aNeighbor    A reference to the neighbor who sends the request.
      * @param[out]  aStatus      A reference to the status which indicates the handling result.
      *
-     * @retval OT_ERROR_NONE     Successfully handled the Link Metrics Management Request.
-     * @retval OT_ERROR_PARSE    Cannot parse sub-TLVs from @p aMessage successfully.
+     * @retval kErrorNone     Successfully handled the Link Metrics Management Request.
+     * @retval kErrorParse    Cannot parse sub-TLVs from @p aMessage successfully.
      *
      */
-    otError HandleLinkMetricsManagementRequest(const Message &    aMessage,
-                                               Neighbor &         aNeighbor,
-                                               LinkMetricsStatus &aStatus);
+    Error HandleLinkMetricsManagementRequest(const Message &aMessage, Neighbor &aNeighbor, LinkMetricsStatus &aStatus);
 
     /**
      * This method handles the received Link Metrics Management Response contained in @p aMessage.
@@ -295,11 +293,11 @@
      * @param[in]  aMessage    A reference to the message that contains the Link Metrics Management Response.
      * @param[in]  aAddress    A reference to the source address of the message.
      *
-     * @retval OT_ERROR_NONE     Successfully handled the Link Metrics Management Response.
-     * @retval OT_ERROR_PARSE    Cannot parse sub-TLVs from @p aMessage successfully.
+     * @retval kErrorNone     Successfully handled the Link Metrics Management Response.
+     * @retval kErrorParse    Cannot parse sub-TLVs from @p aMessage successfully.
      *
      */
-    otError HandleLinkMetricsManagementResponse(const Message &aMessage, const Ip6::Address &aAddress);
+    Error HandleLinkMetricsManagementResponse(const Message &aMessage, const Ip6::Address &aAddress);
 
     /**
      * This method handles the received Link Metrics report contained in @p aMessage.
@@ -321,11 +319,11 @@
      * @param[in]   aMessage     A reference to the message that contains the Link Probe Message.
      * @param[out]  aSeriesId    A reference to Series ID that parsed from the message.
      *
-     * @retval OT_ERROR_NONE     Successfully handled the Link Metrics Management Response.
-     * @retval OT_ERROR_PARSE    Cannot parse sub-TLVs from @p aMessage successfully.
+     * @retval kErrorNone     Successfully handled the Link Metrics Management Response.
+     * @retval kErrorParse    Cannot parse sub-TLVs from @p aMessage successfully.
      *
      */
-    otError HandleLinkProbe(const Message &aMessage, uint8_t &aSeriesId);
+    Error HandleLinkProbe(const Message &aMessage, uint8_t &aSeriesId);
 
     /**
      * This method registers a callback to handle Link Metrics report received.
@@ -383,10 +381,10 @@
 
     Pool<LinkMetricsSeriesInfo, kMaxSeriesSupported> mLinkMetricsSeriesInfoPool;
 
-    otError SendLinkMetricsQuery(const Ip6::Address &          aDestination,
-                                 uint8_t                       aSeriesId,
-                                 const LinkMetricsTypeIdFlags *aTypeIdFlags,
-                                 uint8_t                       aTypeIdFlagsCount);
+    Error SendLinkMetricsQuery(const Ip6::Address &          aDestination,
+                               uint8_t                       aSeriesId,
+                               const LinkMetricsTypeIdFlags *aTypeIdFlags,
+                               uint8_t                       aTypeIdFlagsCount);
 
     LinkMetricsStatus ConfigureForwardTrackingSeries(uint8_t              aSeriesId,
                                                      const SeriesFlags &  aSeriesFlags,
@@ -399,14 +397,14 @@
 
     Neighbor *GetNeighborFromLinkLocalAddr(const Ip6::Address &aDestination);
 
-    static otError ReadTypeIdFlagsFromMessage(const Message &aMessage,
-                                              uint8_t        aStartPos,
-                                              uint8_t        aEndPos,
-                                              otLinkMetrics &aLinkMetrics);
+    static Error ReadTypeIdFlagsFromMessage(const Message &aMessage,
+                                            uint8_t        aStartPos,
+                                            uint8_t        aEndPos,
+                                            otLinkMetrics &aLinkMetrics);
 
-    static otError AppendReportSubTlvToMessage(Message &aMessage, uint8_t &aLength, const otLinkMetricsValues &aValues);
+    static Error AppendReportSubTlvToMessage(Message &aMessage, uint8_t &aLength, const otLinkMetricsValues &aValues);
 
-    static otError AppendStatusSubTlvToMessage(Message &aMessage, uint8_t &aLength, LinkMetricsStatus aStatus);
+    static Error AppendStatusSubTlvToMessage(Message &aMessage, uint8_t &aLength, LinkMetricsStatus aStatus);
 };
 
 /**
diff --git a/src/core/thread/link_quality.cpp b/src/core/thread/link_quality.cpp
index e8a2ac1..0dc65a6 100644
--- a/src/core/thread/link_quality.cpp
+++ b/src/core/thread/link_quality.cpp
@@ -58,12 +58,12 @@
     mFailureRate = static_cast<uint16_t>(((oldAverage * (n - 1)) + newValue + (n / 2)) / n);
 }
 
-otError RssAverager::Add(int8_t aRss)
+Error RssAverager::Add(int8_t aRss)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     uint16_t newValue;
 
-    VerifyOrExit(aRss != OT_RADIO_RSSI_INVALID, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aRss != OT_RADIO_RSSI_INVALID, error = kErrorInvalidArgs);
 
     // Restrict the RSS value to the closed range [0, -128] so the RSS times precision multiple can fit in 11 bits.
     if (aRss > 0)
@@ -234,17 +234,17 @@
     case 0:
         threshold1 += kHysteresisThreshold;
 
-        // fall-through
+        OT_FALL_THROUGH;
 
     case 1:
         threshold2 += kHysteresisThreshold;
 
-        // fall-through
+        OT_FALL_THROUGH;
 
     case 2:
         threshold3 += kHysteresisThreshold;
 
-        // fall-through
+        OT_FALL_THROUGH;
 
     default:
         break;
diff --git a/src/core/thread/link_quality.hpp b/src/core/thread/link_quality.hpp
index 10d06a4..91e41d2 100644
--- a/src/core/thread/link_quality.hpp
+++ b/src/core/thread/link_quality.hpp
@@ -134,16 +134,16 @@
     /**
      * This method adds a received signal strength (RSS) value to the average.
      *
-     * If @p aRss is OT_RADIO_RSSI_INVALID, it is ignored and error status OT_ERROR_INVALID_ARGS is returned.
+     * If @p aRss is OT_RADIO_RSSI_INVALID, it is ignored and error status kErrorInvalidArgs is returned.
      * The value of RSS is capped at 0dBm (i.e., for any given RSS value higher than 0dBm, 0dBm is used instead).
      *
      * @param[in] aRss                Received signal strength value (in dBm) to be added to the average.
      *
-     * @retval OT_ERROR_NONE          New RSS value added to average successfully.
-     * @retval OT_ERROR_INVALID_ARGS  Value of @p aRss is OT_RADIO_RSSI_INVALID.
+     * @retval kErrorNone         New RSS value added to average successfully.
+     * @retval kErrorInvalidArgs  Value of @p aRss is OT_RADIO_RSSI_INVALID.
      *
      */
-    otError Add(int8_t aRss);
+    Error Add(int8_t aRss);
 
     /**
      * This method returns the current average signal strength value maintained by the averager.
diff --git a/src/core/thread/lowpan.cpp b/src/core/thread/lowpan.cpp
index 27e181d..a617d2b 100644
--- a/src/core/thread/lowpan.cpp
+++ b/src/core/thread/lowpan.cpp
@@ -60,9 +60,9 @@
     aAddress.SetPrefix(aContext.mPrefix);
 }
 
-otError Lowpan::ComputeIid(const Mac::Address &aMacAddr, const Context &aContext, Ip6::Address &aIpAddress)
+Error Lowpan::ComputeIid(const Mac::Address &aMacAddr, const Context &aContext, Ip6::Address &aIpAddress)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     switch (aMacAddr.GetType())
     {
@@ -75,7 +75,7 @@
         break;
 
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
     }
 
     if (aContext.mPrefix.GetLength() > 64)
@@ -91,13 +91,13 @@
     return error;
 }
 
-otError Lowpan::CompressSourceIid(const Mac::Address &aMacAddr,
-                                  const Ip6::Address &aIpAddr,
-                                  const Context &     aContext,
-                                  uint16_t &          aHcCtl,
-                                  BufferWriter &      aBuf)
+Error Lowpan::CompressSourceIid(const Mac::Address &aMacAddr,
+                                const Ip6::Address &aIpAddr,
+                                const Context &     aContext,
+                                uint16_t &          aHcCtl,
+                                BufferWriter &      aBuf)
 {
-    otError      error = OT_ERROR_NONE;
+    Error        error = kErrorNone;
     BufferWriter buf   = aBuf;
     Ip6::Address ipaddr;
     Mac::Address tmp;
@@ -126,7 +126,7 @@
     }
 
 exit:
-    if (error == OT_ERROR_NONE)
+    if (error == kErrorNone)
     {
         aBuf = buf;
     }
@@ -134,13 +134,13 @@
     return error;
 }
 
-otError Lowpan::CompressDestinationIid(const Mac::Address &aMacAddr,
-                                       const Ip6::Address &aIpAddr,
-                                       const Context &     aContext,
-                                       uint16_t &          aHcCtl,
-                                       BufferWriter &      aBuf)
+Error Lowpan::CompressDestinationIid(const Mac::Address &aMacAddr,
+                                     const Ip6::Address &aIpAddr,
+                                     const Context &     aContext,
+                                     uint16_t &          aHcCtl,
+                                     BufferWriter &      aBuf)
 {
-    otError      error = OT_ERROR_NONE;
+    Error        error = kErrorNone;
     BufferWriter buf   = aBuf;
     Ip6::Address ipaddr;
     Mac::Address tmp;
@@ -169,7 +169,7 @@
     }
 
 exit:
-    if (error == OT_ERROR_NONE)
+    if (error == kErrorNone)
     {
         aBuf = buf;
     }
@@ -177,9 +177,9 @@
     return error;
 }
 
-otError Lowpan::CompressMulticast(const Ip6::Address &aIpAddr, uint16_t &aHcCtl, BufferWriter &aBuf)
+Error Lowpan::CompressMulticast(const Ip6::Address &aIpAddr, uint16_t &aHcCtl, BufferWriter &aBuf)
 {
-    otError      error = OT_ERROR_NONE;
+    Error        error = kErrorNone;
     BufferWriter buf   = aBuf;
     Context      multicastContext;
 
@@ -212,7 +212,7 @@
             else
             {
                 // Check if multicast address can be compressed using Context ID 0.
-                if (Get<NetworkData::Leader>().GetContext(0, multicastContext) == OT_ERROR_NONE &&
+                if (Get<NetworkData::Leader>().GetContext(0, multicastContext) == kErrorNone &&
                     multicastContext.mPrefix.GetLength() == aIpAddr.mFields.m8[3] &&
                     memcmp(multicastContext.mPrefix.GetBytes(), aIpAddr.mFields.m8 + 4, 8) == 0)
                 {
@@ -231,7 +231,7 @@
     }
 
 exit:
-    if (error == OT_ERROR_NONE)
+    if (error == kErrorNone)
     {
         aBuf = buf;
     }
@@ -239,29 +239,29 @@
     return error;
 }
 
-otError Lowpan::Compress(Message &           aMessage,
-                         const Mac::Address &aMacSource,
-                         const Mac::Address &aMacDest,
-                         BufferWriter &      aBuf)
+Error Lowpan::Compress(Message &           aMessage,
+                       const Mac::Address &aMacSource,
+                       const Mac::Address &aMacDest,
+                       BufferWriter &      aBuf)
 {
-    otError error;
+    Error   error;
     uint8_t headerDepth = 0xff;
 
     do
     {
         error = Compress(aMessage, aMacSource, aMacDest, aBuf, headerDepth);
-    } while ((error != OT_ERROR_NONE) && (headerDepth > 0));
+    } while ((error != kErrorNone) && (headerDepth > 0));
 
     return error;
 }
 
-otError Lowpan::Compress(Message &           aMessage,
-                         const Mac::Address &aMacSource,
-                         const Mac::Address &aMacDest,
-                         BufferWriter &      aBuf,
-                         uint8_t &           aHeaderDepth)
+Error Lowpan::Compress(Message &           aMessage,
+                       const Mac::Address &aMacSource,
+                       const Mac::Address &aMacDest,
+                       BufferWriter &      aBuf,
+                       uint8_t &           aHeaderDepth)
 {
-    otError              error       = OT_ERROR_NONE;
+    Error                error       = kErrorNone;
     NetworkData::Leader &networkData = Get<NetworkData::Leader>();
     uint16_t             startOffset = aMessage.GetOffset();
     BufferWriter         buf         = aBuf;
@@ -279,7 +279,7 @@
     SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), ip6Header));
 
     srcContextValid =
-        (networkData.GetContext(ip6Header.GetSource(), srcContext) == OT_ERROR_NONE && srcContext.mCompressFlag);
+        (networkData.GetContext(ip6Header.GetSource(), srcContext) == kErrorNone && srcContext.mCompressFlag);
 
     if (!srcContextValid)
     {
@@ -287,7 +287,7 @@
     }
 
     dstContextValid =
-        (networkData.GetContext(ip6Header.GetDestination(), dstContext) == OT_ERROR_NONE && dstContext.mCompressFlag);
+        (networkData.GetContext(ip6Header.GetDestination(), dstContext) == kErrorNone && dstContext.mCompressFlag);
 
     if (!dstContextValid)
     {
@@ -350,7 +350,7 @@
             hcCtl |= kHcNextHeader;
             break;
         }
-        // fall through
+        OT_FALL_THROUGH;
 
     default:
         SuccessOrExit(error = buf.Write(static_cast<uint8_t>(ip6Header.GetNextHeader())));
@@ -439,7 +439,7 @@
 
             error = Compress(aMessage, aMacSource, aMacDest, buf);
 
-            // fall through
+            OT_FALL_THROUGH;
 
         default:
             ExitNow();
@@ -451,7 +451,7 @@
 exit:
     aHeaderDepth = headerDepth;
 
-    if (error == OT_ERROR_NONE)
+    if (error == kErrorNone)
     {
         IgnoreError(aBuf.Write(hcCtl >> 8));
         IgnoreError(aBuf.Write(hcCtl & 0xff));
@@ -465,9 +465,9 @@
     return error;
 }
 
-otError Lowpan::CompressExtensionHeader(Message &aMessage, BufferWriter &aBuf, uint8_t &aNextHeader)
+Error Lowpan::CompressExtensionHeader(Message &aMessage, BufferWriter &aBuf, uint8_t &aNextHeader)
 {
-    otError              error       = OT_ERROR_NONE;
+    Error                error       = kErrorNone;
     BufferWriter         buf         = aBuf;
     uint16_t             startOffset = aMessage.GetOffset();
     Ip6::ExtensionHeader extHeader;
@@ -498,7 +498,7 @@
     len = (extHeader.GetLength() + 1) * 8 - sizeof(extHeader);
 
     // RFC 6282 does not support compressing large extension headers
-    VerifyOrExit(len <= kExtHdrMaxLength, error = OT_ERROR_FAILED);
+    VerifyOrExit(len <= kExtHdrMaxLength, error = kErrorFailed);
 
     // RFC 6282 says: "IPv6 Hop-by-Hop and Destination Options Headers may use a trailing
     // Pad1 or PadN to achieve 8-octet alignment. When there is a single trailing Pad1 or PadN
@@ -536,7 +536,7 @@
         len -= padLength;
     }
 
-    VerifyOrExit(aMessage.GetOffset() + len + padLength <= aMessage.GetLength(), error = OT_ERROR_PARSE);
+    VerifyOrExit(aMessage.GetOffset() + len + padLength <= aMessage.GetLength(), error = kErrorParse);
 
     aNextHeader = static_cast<uint8_t>(extHeader.GetNextHeader());
 
@@ -545,7 +545,7 @@
     aMessage.MoveOffset(len + padLength);
 
 exit:
-    if (error == OT_ERROR_NONE)
+    if (error == kErrorNone)
     {
         aBuf = buf;
     }
@@ -557,9 +557,9 @@
     return error;
 }
 
-otError Lowpan::CompressUdp(Message &aMessage, BufferWriter &aBuf)
+Error Lowpan::CompressUdp(Message &aMessage, BufferWriter &aBuf)
 {
-    otError          error       = OT_ERROR_NONE;
+    Error            error       = kErrorNone;
     BufferWriter     buf         = aBuf;
     uint16_t         startOffset = aMessage.GetOffset();
     Ip6::Udp::Header udpHeader;
@@ -602,7 +602,7 @@
     aMessage.MoveOffset(sizeof(udpHeader));
 
 exit:
-    if (error == OT_ERROR_NONE)
+    if (error == kErrorNone)
     {
         aBuf = buf;
     }
@@ -614,9 +614,9 @@
     return error;
 }
 
-otError Lowpan::DispatchToNextHeader(uint8_t aDispatch, uint8_t &aNextHeader)
+Error Lowpan::DispatchToNextHeader(uint8_t aDispatch, uint8_t &aNextHeader)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     if ((aDispatch & kExtHdrDispatchMask) == kExtHdrDispatch)
     {
@@ -649,7 +649,7 @@
         ExitNow();
     }
 
-    error = OT_ERROR_PARSE;
+    error = kErrorParse;
 
 exit:
     return error;
@@ -663,7 +663,7 @@
                                  uint16_t            aBufLength)
 {
     NetworkData::Leader &networkData = Get<NetworkData::Leader>();
-    otError              error       = OT_ERROR_PARSE;
+    Error                error       = kErrorParse;
     const uint8_t *      cur         = aBuf;
     const uint8_t *      end         = aBuf + aBufLength;
     uint16_t             hcCtl;
@@ -687,12 +687,12 @@
     {
         VerifyOrExit(cur < end);
 
-        if (networkData.GetContext(cur[0] >> 4, srcContext) != OT_ERROR_NONE)
+        if (networkData.GetContext(cur[0] >> 4, srcContext) != kErrorNone)
         {
             srcContextValid = false;
         }
 
-        if (networkData.GetContext(cur[0] & 0xf, dstContext) != OT_ERROR_NONE)
+        if (networkData.GetContext(cur[0] & 0xf, dstContext) != kErrorNone)
         {
             dstContextValid = false;
         }
@@ -924,15 +924,15 @@
         aIp6Header.SetNextHeader(nextHeader);
     }
 
-    error = OT_ERROR_NONE;
+    error = kErrorNone;
 
 exit:
-    return (error == OT_ERROR_NONE) ? static_cast<int>(cur - aBuf) : -1;
+    return (error == kErrorNone) ? static_cast<int>(cur - aBuf) : -1;
 }
 
 int Lowpan::DecompressExtensionHeader(Message &aMessage, const uint8_t *aBuf, uint16_t aBufLength)
 {
-    otError         error = OT_ERROR_PARSE;
+    Error           error = kErrorParse;
     const uint8_t * cur   = aBuf;
     const uint8_t * end   = aBuf + aBufLength;
     uint8_t         hdr[2];
@@ -1001,15 +1001,15 @@
         aMessage.MoveOffset(padLength);
     }
 
-    error = OT_ERROR_NONE;
+    error = kErrorNone;
 
 exit:
-    return (error == OT_ERROR_NONE) ? static_cast<int>(cur - aBuf) : -1;
+    return (error == kErrorNone) ? static_cast<int>(cur - aBuf) : -1;
 }
 
 int Lowpan::DecompressUdpHeader(Ip6::Udp::Header &aUdpHeader, const uint8_t *aBuf, uint16_t aBufLength)
 {
-    otError        error = OT_ERROR_PARSE;
+    Error          error = kErrorParse;
     const uint8_t *cur   = aBuf;
     const uint8_t *end   = aBuf + aBufLength;
     uint8_t        udpCtl;
@@ -1066,10 +1066,10 @@
         cur += 2;
     }
 
-    error = OT_ERROR_NONE;
+    error = kErrorNone;
 
 exit:
-    return (error == OT_ERROR_NONE) ? static_cast<int>(cur - aBuf) : -1;
+    return (error == kErrorNone) ? static_cast<int>(cur - aBuf) : -1;
 }
 
 int Lowpan::DecompressUdpHeader(Message &aMessage, const uint8_t *aBuf, uint16_t aBufLength, uint16_t aDatagramLength)
@@ -1090,7 +1090,7 @@
         udpHeader.SetLength(aDatagramLength - aMessage.GetOffset());
     }
 
-    VerifyOrExit(aMessage.Append(udpHeader) == OT_ERROR_NONE, headerLen = -1);
+    VerifyOrExit(aMessage.Append(udpHeader) == kErrorNone, headerLen = -1);
     aMessage.MoveOffset(sizeof(udpHeader));
 
 exit:
@@ -1104,7 +1104,7 @@
                        uint16_t            aBufLength,
                        uint16_t            aDatagramLength)
 {
-    otError        error = OT_ERROR_PARSE;
+    Error          error = kErrorParse;
     Ip6::Header    ip6Header;
     const uint8_t *cur       = aBuf;
     uint16_t       remaining = aBufLength;
@@ -1173,10 +1173,10 @@
 
     aMessage.Write(currentOffset + Ip6::Header::kPayloadLengthFieldOffset, ip6PayloadLength);
 
-    error = OT_ERROR_NONE;
+    error = kErrorNone;
 
 exit:
-    return (error == OT_ERROR_NONE) ? static_cast<int>(compressedLength) : -1;
+    return (error == kErrorNone) ? static_cast<int>(compressedLength) : -1;
 }
 
 //---------------------------------------------------------------------------------------------------------------------
@@ -1194,9 +1194,9 @@
     return (aFrameLength >= kMinHeaderLength) && ((*aFrame & kDispatchMask) == kDispatch);
 }
 
-otError MeshHeader::ParseFrom(const uint8_t *aFrame, uint16_t aFrameLength, uint16_t &aHeaderLength)
+Error MeshHeader::ParseFrom(const uint8_t *aFrame, uint16_t aFrameLength, uint16_t &aHeaderLength)
 {
-    otError error = OT_ERROR_PARSE;
+    Error   error = kErrorParse;
     uint8_t dispatch;
 
     VerifyOrExit(aFrameLength >= kMinHeaderLength);
@@ -1220,20 +1220,20 @@
     mSource      = ReadUint16(aFrame);
     mDestination = ReadUint16(aFrame + 2);
 
-    error = OT_ERROR_NONE;
+    error = kErrorNone;
 
 exit:
     return error;
 }
 
-otError MeshHeader::ParseFrom(const Message &aMessage)
+Error MeshHeader::ParseFrom(const Message &aMessage)
 {
     uint16_t headerLength;
 
     return ParseFrom(aMessage, headerLength);
 }
 
-otError MeshHeader::ParseFrom(const Message &aMessage, uint16_t &aHeaderLength)
+Error MeshHeader::ParseFrom(const Message &aMessage, uint16_t &aHeaderLength)
 {
     uint8_t  frame[kDeepHopsHeaderLength];
     uint16_t frameLength;
@@ -1306,9 +1306,9 @@
     return (aFrameLength >= kFirstFragmentHeaderSize) && ((*aFrame & kDispatchMask) == kDispatch);
 }
 
-otError FragmentHeader::ParseFrom(const uint8_t *aFrame, uint16_t aFrameLength, uint16_t &aHeaderLength)
+Error FragmentHeader::ParseFrom(const uint8_t *aFrame, uint16_t aFrameLength, uint16_t &aHeaderLength)
 {
-    otError error = OT_ERROR_PARSE;
+    Error error = kErrorParse;
 
     VerifyOrExit(IsFragmentHeader(aFrame, aFrameLength));
 
@@ -1327,13 +1327,13 @@
         aHeaderLength = kFirstFragmentHeaderSize;
     }
 
-    error = OT_ERROR_NONE;
+    error = kErrorNone;
 
 exit:
     return error;
 }
 
-otError FragmentHeader::ParseFrom(const Message &aMessage, uint16_t aOffset, uint16_t &aHeaderLength)
+Error FragmentHeader::ParseFrom(const Message &aMessage, uint16_t aOffset, uint16_t &aHeaderLength)
 {
     uint8_t  frame[kSubsequentFragmentHeaderSize];
     uint16_t frameLength;
diff --git a/src/core/thread/lowpan.hpp b/src/core/thread/lowpan.hpp
index dbb1901..e80542d 100644
--- a/src/core/thread/lowpan.hpp
+++ b/src/core/thread/lowpan.hpp
@@ -121,15 +121,15 @@
      *
      * @param[in]  aLength  Number of bytes to advance.
      *
-     * @retval OT_ERROR_NONE     Enough buffer space is available to advance the requested number of bytes.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffer space to advance the requested number of bytes.
+     * @retval kErrorNone    Enough buffer space is available to advance the requested number of bytes.
+     * @retval kErrorNoBufs  Insufficient buffer space to advance the requested number of bytes.
      *
      */
-    otError Advance(uint8_t aLength)
+    Error Advance(uint8_t aLength)
     {
-        otError error = OT_ERROR_NONE;
+        Error error = kErrorNone;
 
-        VerifyOrExit(CanWrite(aLength), error = OT_ERROR_NO_BUFS);
+        VerifyOrExit(CanWrite(aLength), error = kErrorNoBufs);
         mWritePointer += aLength;
 
     exit:
@@ -141,15 +141,15 @@
      *
      * @param[in]  aByte  Byte to write.
      *
-     * @retval  OT_ERROR_NONE     Successfully wrote the byte and updated the pointer.
-     * @retval  OT_ERROR_NO_BUFS  Insufficient buffer space to write the byte.
+     * @retval  kErrorNone     Successfully wrote the byte and updated the pointer.
+     * @retval  kErrorNoBufs  Insufficient buffer space to write the byte.
      *
      */
-    otError Write(uint8_t aByte)
+    Error Write(uint8_t aByte)
     {
-        otError error = OT_ERROR_NONE;
+        Error error = kErrorNone;
 
-        VerifyOrExit(CanWrite(sizeof(aByte)), error = OT_ERROR_NO_BUFS);
+        VerifyOrExit(CanWrite(sizeof(aByte)), error = kErrorNoBufs);
 
         *mWritePointer++ = aByte;
 
@@ -163,15 +163,15 @@
      * @param[in]  aBuf     A pointer to the byte sequence.
      * @param[in]  aLength  Number of bytes to write.
      *
-     * @retval OT_ERROR_NONE     Successfully wrote the byte sequence and updated the pointer.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffer space to write the byte sequence.
+     * @retval kErrorNone    Successfully wrote the byte sequence and updated the pointer.
+     * @retval kErrorNoBufs  Insufficient buffer space to write the byte sequence.
      *
      */
-    otError Write(const void *aBuf, uint8_t aLength)
+    Error Write(const void *aBuf, uint8_t aLength)
     {
-        otError error = OT_ERROR_NONE;
+        Error error = kErrorNone;
 
-        VerifyOrExit(CanWrite(aLength), error = OT_ERROR_NO_BUFS);
+        VerifyOrExit(CanWrite(aLength), error = kErrorNoBufs);
 
         memcpy(mWritePointer, aBuf, aLength);
         mWritePointer += aLength;
@@ -188,18 +188,18 @@
      * @param[in]  aMessage  A message buffer.
      * @param[in]  aLength   Number of bytes to write.
      *
-     * @retval OT_ERROR_NONE     Successfully wrote the byte sequence and updated the pointer.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffer space to write the byte sequence.
+     * @retval kErrorNone    Successfully wrote the byte sequence and updated the pointer.
+     * @retval kErrorNoBufs  Insufficient buffer space to write the byte sequence.
      *
      */
-    otError Write(const Message &aMessage, uint8_t aLength)
+    Error Write(const Message &aMessage, uint8_t aLength)
     {
-        otError error = OT_ERROR_NONE;
-        int     rval;
+        Error error = kErrorNone;
+        int   rval;
 
         OT_UNUSED_VARIABLE(rval);
 
-        VerifyOrExit(CanWrite(aLength), error = OT_ERROR_NO_BUFS);
+        VerifyOrExit(CanWrite(aLength), error = kErrorNoBufs);
 
         rval = aMessage.ReadBytes(aMessage.GetOffset(), mWritePointer, aLength);
         OT_ASSERT(rval == aLength);
@@ -254,10 +254,7 @@
      * @returns The size of the compressed header in bytes.
      *
      */
-    otError Compress(Message &           aMessage,
-                     const Mac::Address &aMacSource,
-                     const Mac::Address &aMacDest,
-                     BufferWriter &      aBuf);
+    Error Compress(Message &aMessage, const Mac::Address &aMacSource, const Mac::Address &aMacDest, BufferWriter &aBuf);
 
     /**
      * This method decompresses a LOWPAN_IPHC header.
@@ -361,32 +358,32 @@
         kUdpPortMask     = 3 << 0,
     };
 
-    otError Compress(Message &           aMessage,
-                     const Mac::Address &aMacSource,
-                     const Mac::Address &aMacDest,
-                     BufferWriter &      aBuf,
-                     uint8_t &           aHeaderDepth);
+    Error Compress(Message &           aMessage,
+                   const Mac::Address &aMacSource,
+                   const Mac::Address &aMacDest,
+                   BufferWriter &      aBuf,
+                   uint8_t &           aHeaderDepth);
 
-    otError CompressExtensionHeader(Message &aMessage, BufferWriter &aBuf, uint8_t &aNextHeader);
-    otError CompressSourceIid(const Mac::Address &aMacAddr,
-                              const Ip6::Address &aIpAddr,
-                              const Context &     aContext,
-                              uint16_t &          aHcCtl,
-                              BufferWriter &      aBuf);
-    otError CompressDestinationIid(const Mac::Address &aMacAddr,
-                                   const Ip6::Address &aIpAddr,
-                                   const Context &     aContext,
-                                   uint16_t &          aHcCtl,
-                                   BufferWriter &      aBuf);
-    otError CompressMulticast(const Ip6::Address &aIpAddr, uint16_t &aHcCtl, BufferWriter &aBuf);
-    otError CompressUdp(Message &aMessage, BufferWriter &aBuf);
+    Error CompressExtensionHeader(Message &aMessage, BufferWriter &aBuf, uint8_t &aNextHeader);
+    Error CompressSourceIid(const Mac::Address &aMacAddr,
+                            const Ip6::Address &aIpAddr,
+                            const Context &     aContext,
+                            uint16_t &          aHcCtl,
+                            BufferWriter &      aBuf);
+    Error CompressDestinationIid(const Mac::Address &aMacAddr,
+                                 const Ip6::Address &aIpAddr,
+                                 const Context &     aContext,
+                                 uint16_t &          aHcCtl,
+                                 BufferWriter &      aBuf);
+    Error CompressMulticast(const Ip6::Address &aIpAddr, uint16_t &aHcCtl, BufferWriter &aBuf);
+    Error CompressUdp(Message &aMessage, BufferWriter &aBuf);
 
-    int     DecompressExtensionHeader(Message &aMessage, const uint8_t *aBuf, uint16_t aBufLength);
-    int     DecompressUdpHeader(Message &aMessage, const uint8_t *aBuf, uint16_t aBufLength, uint16_t aDatagramLength);
-    otError DispatchToNextHeader(uint8_t aDispatch, uint8_t &aNextHeader);
+    int   DecompressExtensionHeader(Message &aMessage, const uint8_t *aBuf, uint16_t aBufLength);
+    int   DecompressUdpHeader(Message &aMessage, const uint8_t *aBuf, uint16_t aBufLength, uint16_t aDatagramLength);
+    Error DispatchToNextHeader(uint8_t aDispatch, uint8_t &aNextHeader);
 
-    static void    CopyContext(const Context &aContext, Ip6::Address &aAddress);
-    static otError ComputeIid(const Mac::Address &aMacAddr, const Context &aContext, Ip6::Address &aIpAddress);
+    static void  CopyContext(const Context &aContext, Ip6::Address &aAddress);
+    static Error ComputeIid(const Mac::Address &aMacAddr, const Context &aContext, Ip6::Address &aIpAddress);
 };
 
 /**
@@ -431,11 +428,11 @@
      * @param[in]  aFrameLength  The length of the frame.
      * @param[out] aHeaderLength A reference to a variable to output the parsed header length (on success).
      *
-     * @retval OT_ERROR_NONE     Mesh Header parsed successfully.
-     * @retval OT_ERROR_PARSE    Mesh Header could not be parsed.
+     * @retval kErrorNone     Mesh Header parsed successfully.
+     * @retval kErrorParse    Mesh Header could not be parsed.
      *
      */
-    otError ParseFrom(const uint8_t *aFrame, uint16_t aFrameLength, uint16_t &aHeaderLength);
+    Error ParseFrom(const uint8_t *aFrame, uint16_t aFrameLength, uint16_t &aHeaderLength);
 
     /**
      * This method parses the Mesh Header from a given message.
@@ -444,11 +441,11 @@
      *
      * @param[in]  aMessage    The message to read from.
      *
-     * @retval OT_ERROR_NONE   Mesh Header parsed successfully.
-     * @retval OT_ERROR_PARSE  Mesh Header could not be parsed.
+     * @retval kErrorNone   Mesh Header parsed successfully.
+     * @retval kErrorParse  Mesh Header could not be parsed.
      *
      */
-    otError ParseFrom(const Message &aMessage);
+    Error ParseFrom(const Message &aMessage);
 
     /**
      * This method parses the Mesh Header from a given message.
@@ -458,11 +455,11 @@
      * @param[in]  aMessage       The message to read from.
      * @param[out] aHeaderLength  A reference to a variable to output the parsed header length (on success).
      *
-     * @retval OT_ERROR_NONE   Mesh Header parsed successfully.
-     * @retval OT_ERROR_PARSE  Mesh Header could not be parsed.
+     * @retval kErrorNone   Mesh Header parsed successfully.
+     * @retval kErrorParse  Mesh Header could not be parsed.
      *
      */
-    otError ParseFrom(const Message &aMessage, uint16_t &aHeaderLength);
+    Error ParseFrom(const Message &aMessage, uint16_t &aHeaderLength);
 
     /**
      * This method returns the the Mesh Header length when written to a frame.
@@ -606,11 +603,11 @@
      * @param[in]  aFrameLength    The length of the frame.
      * @param[out] aHeaderLength   A reference to a variable to output the parsed header length (on success).
      *
-     * @retval OT_ERROR_NONE     Fragment Header parsed successfully.
-     * @retval OT_ERROR_PARSE    Fragment header could not be parsed from @p aFrame.
+     * @retval kErrorNone     Fragment Header parsed successfully.
+     * @retval kErrorParse    Fragment header could not be parsed from @p aFrame.
      *
      */
-    otError ParseFrom(const uint8_t *aFrame, uint16_t aFrameLength, uint16_t &aHeaderLength);
+    Error ParseFrom(const uint8_t *aFrame, uint16_t aFrameLength, uint16_t &aHeaderLength);
 
     /**
      * This method parses the Fragment Header from a message.
@@ -619,11 +616,11 @@
      * @param[in]  aOffset       The offset within the message to start reading from.
      * @param[out] aHeaderLength A reference to a variable to output the parsed header length (on success).
      *
-     * @retval OT_ERROR_NONE     Fragment Header parsed successfully.
-     * @retval OT_ERROR_PARSE    Fragment header could not be parsed from @p aFrame.
+     * @retval kErrorNone     Fragment Header parsed successfully.
+     * @retval kErrorParse    Fragment header could not be parsed from @p aFrame.
      *
      */
-    otError ParseFrom(const Message &aMessage, uint16_t aOffset, uint16_t &aHeaderLength);
+    Error ParseFrom(const Message &aMessage, uint16_t aOffset, uint16_t &aHeaderLength);
 
     /**
      * This method returns the Datagram Size value.
diff --git a/src/core/thread/mesh_forwarder.cpp b/src/core/thread/mesh_forwarder.cpp
index a1339fe..09505cc 100644
--- a/src/core/thread/mesh_forwarder.cpp
+++ b/src/core/thread/mesh_forwarder.cpp
@@ -58,7 +58,7 @@
 {
     Clear();
 
-    if (OT_ERROR_NONE != aFrame.GetSrcPanId(mPanId))
+    if (kErrorNone != aFrame.GetSrcPanId(mPanId))
     {
         IgnoreError(aFrame.GetDstPanId(mPanId));
     }
@@ -89,7 +89,7 @@
     , mEnabled(false)
     , mTxPaused(false)
     , mSendBusy(false)
-    , mScheduleTransmissionTask(aInstance, MeshForwarder::ScheduleTransmissionTask, this)
+    , mScheduleTransmissionTask(aInstance, MeshForwarder::ScheduleTransmissionTask)
 #if OPENTHREAD_FTD
     , mIndirectSender(aInstance)
 #endif
@@ -154,7 +154,8 @@
 
 void MeshForwarder::PrepareEmptyFrame(Mac::TxFrame &aFrame, const Mac::Address &aMacDest, bool aAckRequest)
 {
-    uint16_t fcf = 0;
+    uint16_t fcf       = 0;
+    bool     iePresent = CalcIePresent(nullptr);
 
     Mac::Address macSource;
     macSource.SetShort(Get<Mac::Mac>().GetShortAddress());
@@ -166,6 +167,13 @@
 
     fcf = Mac::Frame::kFcfFrameData | Mac::Frame::kFcfPanidCompression | Mac::Frame::kFcfSecurityEnabled;
 
+    if (iePresent)
+    {
+        fcf |= Mac::Frame::kFcfIePresent;
+    }
+
+    fcf |= CalcFrameVersion(Get<NeighborTable>().FindNeighbor(aMacDest), iePresent);
+
     if (aAckRequest)
     {
         fcf |= Mac::Frame::kFcfAckRequest;
@@ -173,7 +181,6 @@
 
     fcf |= (aMacDest.IsShort()) ? Mac::Frame::kFcfDstAddrShort : Mac::Frame::kFcfDstAddrExt;
     fcf |= (macSource.IsShort()) ? Mac::Frame::kFcfSrcAddrShort : Mac::Frame::kFcfSrcAddrExt;
-    Get<Mac::Mac>().UpdateFrameControlField(Get<NeighborTable>().FindNeighbor(aMacDest), false, fcf);
 
     aFrame.InitMacHeader(fcf, Mac::Frame::kKeyIdMode1 | Mac::Frame::kSecEncMic32);
 
@@ -187,7 +194,10 @@
     aFrame.SetSrcAddr(macSource);
     aFrame.SetFramePending(false);
 #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
-    IgnoreError(Get<Mac::Mac>().AppendHeaderIe(false, aFrame));
+    if (iePresent)
+    {
+        AppendHeaderIe(nullptr, aFrame);
+    }
 #endif
     aFrame.SetPayloadLength(0);
 }
@@ -214,7 +224,7 @@
     }
 
     queue->Dequeue(aMessage);
-    LogMessage(kMessageEvict, aMessage, nullptr, OT_ERROR_NO_BUFS);
+    LogMessage(kMessageEvict, aMessage, nullptr, kErrorNoBufs);
     aMessage.Free();
 }
 
@@ -229,7 +239,7 @@
 
 void MeshForwarder::ScheduleTransmissionTask(Tasklet &aTasklet)
 {
-    aTasklet.GetOwner<MeshForwarder>().ScheduleTransmissionTask();
+    aTasklet.Get<MeshForwarder>().ScheduleTransmissionTask();
 }
 
 void MeshForwarder::ScheduleTransmissionTask(void)
@@ -253,7 +263,7 @@
 Message *MeshForwarder::GetDirectTransmission(void)
 {
     Message *curMessage, *nextMessage;
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
 
     for (curMessage = mSendQueue.GetHead(); curMessage; curMessage = nextMessage)
     {
@@ -281,12 +291,12 @@
 
 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
         case Message::kTypeMacEmptyData:
-            error = OT_ERROR_NONE;
+            error = kErrorNone;
             break;
 #endif
 
         default:
-            error = OT_ERROR_DROP;
+            error = kErrorDrop;
             break;
         }
 
@@ -297,12 +307,12 @@
 
         switch (error)
         {
-        case OT_ERROR_NONE:
+        case kErrorNone:
             ExitNow();
 
 #if OPENTHREAD_FTD
 
-        case OT_ERROR_ADDRESS_QUERY:
+        case kErrorAddressQuery:
             mSendQueue.Dequeue(*curMessage);
             mResolvingQueue.Enqueue(*curMessage);
             continue;
@@ -321,17 +331,17 @@
     return curMessage;
 }
 
-otError MeshForwarder::UpdateIp6Route(Message &aMessage)
+Error MeshForwarder::UpdateIp6Route(Message &aMessage)
 {
     Mle::MleRouter &mle   = Get<Mle::MleRouter>();
-    otError         error = OT_ERROR_NONE;
+    Error           error = kErrorNone;
     Ip6::Header     ip6Header;
 
     mAddMeshHeader = false;
 
     IgnoreError(aMessage.Read(0, ip6Header));
 
-    VerifyOrExit(!ip6Header.GetSource().IsMulticast(), error = OT_ERROR_DROP);
+    VerifyOrExit(!ip6Header.GetSource().IsMulticast(), error = kErrorDrop);
 
     GetMacSourceAddress(ip6Header.GetSource(), mMacSource);
 
@@ -343,7 +353,7 @@
         }
         else
         {
-            error = OT_ERROR_DROP;
+            error = kErrorDrop;
         }
 
         ExitNow();
@@ -432,33 +442,33 @@
     }
 }
 
-otError MeshForwarder::DecompressIp6Header(const uint8_t *     aFrame,
-                                           uint16_t            aFrameLength,
-                                           const Mac::Address &aMacSource,
-                                           const Mac::Address &aMacDest,
-                                           Ip6::Header &       aIp6Header,
-                                           uint8_t &           aHeaderLength,
-                                           bool &              aNextHeaderCompressed)
+Error MeshForwarder::DecompressIp6Header(const uint8_t *     aFrame,
+                                         uint16_t            aFrameLength,
+                                         const Mac::Address &aMacSource,
+                                         const Mac::Address &aMacDest,
+                                         Ip6::Header &       aIp6Header,
+                                         uint8_t &           aHeaderLength,
+                                         bool &              aNextHeaderCompressed)
 {
-    otError                error = OT_ERROR_NONE;
+    Error                  error = kErrorNone;
     const uint8_t *        start = aFrame;
     Lowpan::FragmentHeader fragmentHeader;
     uint16_t               fragmentHeaderLength;
     int                    headerLength;
 
-    if (fragmentHeader.ParseFrom(aFrame, aFrameLength, fragmentHeaderLength) == OT_ERROR_NONE)
+    if (fragmentHeader.ParseFrom(aFrame, aFrameLength, fragmentHeaderLength) == kErrorNone)
     {
         // Only the first fragment header is followed by a LOWPAN_IPHC header
-        VerifyOrExit(fragmentHeader.GetDatagramOffset() == 0, error = OT_ERROR_NOT_FOUND);
+        VerifyOrExit(fragmentHeader.GetDatagramOffset() == 0, error = kErrorNotFound);
         aFrame += fragmentHeaderLength;
         aFrameLength -= fragmentHeaderLength;
     }
 
-    VerifyOrExit(aFrameLength >= 1 && Lowpan::Lowpan::IsLowpanHc(aFrame), error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(aFrameLength >= 1 && Lowpan::Lowpan::IsLowpanHc(aFrame), error = kErrorNotFound);
     headerLength = Get<Lowpan::Lowpan>().DecompressBaseHeader(aIp6Header, aNextHeaderCompressed, aMacSource, aMacDest,
                                                               aFrame, aFrameLength);
 
-    VerifyOrExit(headerLength > 0, error = OT_ERROR_PARSE);
+    VerifyOrExit(headerLength > 0, error = kErrorParse);
     aHeaderLength = static_cast<uint8_t>(aFrame - start) + static_cast<uint8_t>(headerLength);
 
 exit:
@@ -542,7 +552,7 @@
         // queue for it. The message would be then converted to a
         // direct tx.
 
-        // Fall through
+        OT_FALL_THROUGH;
 #endif
 
     default:
@@ -585,17 +595,23 @@
     uint16_t dstpan;
     uint8_t  secCtl;
     uint16_t nextOffset;
+    bool     iePresent = CalcIePresent(&aMessage);
 
 start:
 
     // Initialize MAC header
     fcf = Mac::Frame::kFcfFrameData;
 
-    Get<Mac::Mac>().UpdateFrameControlField(Get<NeighborTable>().FindNeighbor(aMacDest), aMessage.IsTimeSync(), fcf);
-
     fcf |= (aMacDest.IsShort()) ? Mac::Frame::kFcfDstAddrShort : Mac::Frame::kFcfDstAddrExt;
     fcf |= (aMacSource.IsShort()) ? Mac::Frame::kFcfSrcAddrShort : Mac::Frame::kFcfSrcAddrExt;
 
+    if (iePresent)
+    {
+        fcf |= Mac::Frame::kFcfIePresent;
+    }
+
+    fcf |= CalcFrameVersion(Get<NeighborTable>().FindNeighbor(aMacDest), iePresent);
+
     // All unicast frames request ACK
     if (aMacDest.IsExtended() || !aMacDest.IsBroadcast())
     {
@@ -663,7 +679,10 @@
     aFrame.SetSrcAddr(aMacSource);
 
 #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
-    IgnoreError(Get<Mac::Mac>().AppendHeaderIe(aMessage.IsTimeSync(), aFrame));
+    if (iePresent)
+    {
+        AppendHeaderIe(&aMessage, aFrame);
+    }
 #endif
 
     payload          = aFrame.GetPayload();
@@ -742,7 +761,7 @@
                                     maxPayloadLength - headerLength - Lowpan::FragmentHeader::kFirstFragmentHeaderSize);
         uint8_t              hcLength;
         Mac::Address         meshSource, meshDest;
-        otError              error;
+        Error                error;
 
         OT_UNUSED_VARIABLE(error);
 
@@ -758,7 +777,7 @@
         }
 
         error = Get<Lowpan::Lowpan>().Compress(aMessage, meshSource, meshDest, buffer);
-        OT_ASSERT(error == OT_ERROR_NONE);
+        OT_ASSERT(error == kErrorNone);
 
         hcLength = static_cast<uint8_t>(buffer.GetWritePointer() - payload);
         headerLength += hcLength;
@@ -854,7 +873,7 @@
     return nextOffset;
 }
 
-Neighbor *MeshForwarder::UpdateNeighborOnSentFrame(Mac::TxFrame &aFrame, otError aError, const Mac::Address &aMacDest)
+Neighbor *MeshForwarder::UpdateNeighborOnSentFrame(Mac::TxFrame &aFrame, Error aError, const Mac::Address &aMacDest)
 {
     Neighbor *neighbor = nullptr;
 
@@ -873,7 +892,7 @@
     if (aFrame.GetRadioType() == Mac::kRadioTypeTrel)
 #endif
     {
-        VerifyOrExit(aError != OT_ERROR_NONE);
+        VerifyOrExit(aError != kErrorNone);
     }
 #endif // OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
 
@@ -883,15 +902,15 @@
     return neighbor;
 }
 
-void MeshForwarder::UpdateNeighborLinkFailures(Neighbor &aNeighbor, otError aError, bool aAllowNeighborRemove)
+void MeshForwarder::UpdateNeighborLinkFailures(Neighbor &aNeighbor, Error aError, bool aAllowNeighborRemove)
 {
     // Update neighbor `LinkFailures` counter on ack error.
 
-    if (aError == OT_ERROR_NONE)
+    if (aError == kErrorNone)
     {
         aNeighbor.ResetLinkFailures();
     }
-    else if (aError == OT_ERROR_NO_ACK)
+    else if (aError == kErrorNoAck)
     {
         aNeighbor.IncrementLinkFailures();
 
@@ -904,13 +923,13 @@
 }
 
 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
-void MeshForwarder::HandleDeferredAck(Neighbor &aNeighbor, otError aError)
+void MeshForwarder::HandleDeferredAck(Neighbor &aNeighbor, Error aError)
 {
     bool allowNeighborRemove = true;
 
     VerifyOrExit(mEnabled);
 
-    if (aError == OT_ERROR_NO_ACK)
+    if (aError == kErrorNoAck)
     {
         otLogInfoMac("Deferred ack timeout on trel for neighbor %s rloc16:0x%04x",
                      aNeighbor.GetExtAddress().ToString().AsCString(), aNeighbor.GetRloc16());
@@ -929,13 +948,13 @@
 }
 #endif // #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
 
-void MeshForwarder::HandleSentFrame(Mac::TxFrame &aFrame, otError aError)
+void MeshForwarder::HandleSentFrame(Mac::TxFrame &aFrame, Error aError)
 {
     Neighbor *   neighbor = nullptr;
     Mac::Address macDest;
 
-    OT_ASSERT((aError == OT_ERROR_NONE) || (aError == OT_ERROR_CHANNEL_ACCESS_FAILURE) || (aError == OT_ERROR_ABORT) ||
-              (aError == OT_ERROR_NO_ACK));
+    OT_ASSERT((aError == kErrorNone) || (aError == kErrorChannelAccessFailure) || (aError == kErrorAbort) ||
+              (aError == kErrorNoAck));
 
     mSendBusy = false;
 
@@ -953,13 +972,15 @@
     return;
 }
 
-void MeshForwarder::UpdateSendMessage(otError aFrameTxError, Mac::Address &aMacDest, Neighbor *aNeighbor)
+void MeshForwarder::UpdateSendMessage(Error aFrameTxError, Mac::Address &aMacDest, Neighbor *aNeighbor)
 {
+    Error txError = aFrameTxError;
+
     VerifyOrExit(mSendMessage != nullptr);
 
     OT_ASSERT(mSendMessage->GetDirectTransmission());
 
-    if (aFrameTxError != OT_ERROR_NONE)
+    if (aFrameTxError != kErrorNone)
     {
         // If the transmission of any fragment frame fails,
         // the overall message transmission is considered
@@ -979,57 +1000,58 @@
     if (mMessageNextOffset < mSendMessage->GetLength())
     {
         mSendMessage->SetOffset(mMessageNextOffset);
+        ExitNow();
     }
-    else
+
+    txError = aFrameTxError;
+
+    mSendMessage->ClearDirectTransmission();
+    mSendMessage->SetOffset(0);
+
+    if (aNeighbor != nullptr)
     {
-        otError txError = aFrameTxError;
-
-        mSendMessage->ClearDirectTransmission();
-        mSendMessage->SetOffset(0);
-
-        if (aNeighbor != nullptr)
-        {
-            aNeighbor->GetLinkInfo().AddMessageTxStatus(mSendMessage->GetTxSuccess());
-        }
+        aNeighbor->GetLinkInfo().AddMessageTxStatus(mSendMessage->GetTxSuccess());
+    }
 
 #if !OPENTHREAD_CONFIG_DROP_MESSAGE_ON_FRAGMENT_TX_FAILURE
 
-        // When `CONFIG_DROP_MESSAGE_ON_FRAGMENT_TX_FAILURE` is
-        // disabled, all fragment frames of a larger message are
-        // sent even if the transmission of an earlier fragment fail.
-        // Note that `GetTxSuccess() tracks the tx success of the
-        // entire message, while `aFrameTxError` represents the error
-        // status of the last fragment frame transmission.
+    // When `CONFIG_DROP_MESSAGE_ON_FRAGMENT_TX_FAILURE` is
+    // disabled, all fragment frames of a larger message are
+    // sent even if the transmission of an earlier fragment fail.
+    // Note that `GetTxSuccess() tracks the tx success of the
+    // entire message, while `aFrameTxError` represents the error
+    // status of the last fragment frame transmission.
 
-        if (!mSendMessage->GetTxSuccess() && (txError == OT_ERROR_NONE))
-        {
-            txError = OT_ERROR_FAILED;
-        }
+    if (!mSendMessage->GetTxSuccess() && (txError == kErrorNone))
+    {
+        txError = kErrorFailed;
+    }
 #endif
 
-        LogMessage(kMessageTransmit, *mSendMessage, &aMacDest, txError);
+    LogMessage(kMessageTransmit, *mSendMessage, &aMacDest, txError);
 
-        if (mSendMessage->GetType() == Message::kTypeIp6)
+    if (mSendMessage->GetType() == Message::kTypeIp6)
+    {
+        if (mSendMessage->GetTxSuccess())
         {
-            if (mSendMessage->GetTxSuccess())
-            {
-                mIpCounters.mTxSuccess++;
-            }
-            else
-            {
-                mIpCounters.mTxFailure++;
-            }
+            mIpCounters.mTxSuccess++;
+        }
+        else
+        {
+            mIpCounters.mTxFailure++;
         }
     }
 
-    if (mSendMessage->GetSubType() == Message::kSubTypeMleDiscoverRequest)
+    switch (mSendMessage->GetSubType())
     {
+    case Message::kSubTypeMleDiscoverRequest:
+        // Note that `HandleDiscoveryRequestFrameTxDone()` may update
+        // `mSendMessage` and mark it again for direct transmission.
         Get<Mle::DiscoverScanner>().HandleDiscoveryRequestFrameTxDone(*mSendMessage);
-    }
+        break;
 
-    if (!mSendMessage->GetDirectTransmission() && !mSendMessage->IsChildPending())
-    {
-        if (mSendMessage->GetSubType() == Message::kSubTypeMleChildIdRequest && mSendMessage->IsLinkSecurityEnabled())
+    case Message::kSubTypeMleChildIdRequest:
+        if (mSendMessage->IsLinkSecurityEnabled())
         {
             // If the Child ID Request requires fragmentation and therefore
             // link layer security, the frame transmission will be aborted.
@@ -1041,14 +1063,33 @@
             Get<Mle::Mle>().RequestShorterChildIdRequest();
         }
 
-        mSendQueue.Dequeue(*mSendMessage);
-        mSendMessage->Free();
+        break;
+
+    default:
+        break;
+    }
+
+    RemoveMessageIfNoPendingTx(*mSendMessage);
+
+exit:
+    mScheduleTransmissionTask.Post();
+}
+
+void MeshForwarder::RemoveMessageIfNoPendingTx(Message &aMessage)
+{
+    VerifyOrExit(!aMessage.GetDirectTransmission() && !aMessage.IsChildPending());
+
+    if (mSendMessage == &aMessage)
+    {
         mSendMessage       = nullptr;
         mMessageNextOffset = 0;
     }
 
+    mSendQueue.Dequeue(aMessage);
+    aMessage.Free();
+
 exit:
-    mScheduleTransmissionTask.Post();
+    return;
 }
 
 void MeshForwarder::HandleReceivedFrame(Mac::RxFrame &aFrame)
@@ -1058,9 +1099,9 @@
     Mac::Address   macSource;
     uint8_t *      payload;
     uint16_t       payloadLength;
-    otError        error = OT_ERROR_NONE;
+    Error          error = kErrorNone;
 
-    VerifyOrExit(mEnabled, error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(mEnabled, error = kErrorInvalidState);
 
     SuccessOrExit(error = aFrame.GetSrcAddr(macSource));
     SuccessOrExit(error = aFrame.GetDstAddr(macDest));
@@ -1091,9 +1132,9 @@
         }
         else
         {
-            VerifyOrExit(payloadLength == 0, error = OT_ERROR_NOT_LOWPAN_DATA_FRAME);
+            VerifyOrExit(payloadLength == 0, error = kErrorNotLowpanDataFrame);
 
-            LogFrame("Received empty payload frame", aFrame, OT_ERROR_NONE);
+            LogFrame("Received empty payload frame", aFrame, kErrorNone);
         }
 
         break;
@@ -1102,13 +1143,13 @@
         break;
 
     default:
-        error = OT_ERROR_DROP;
+        error = kErrorDrop;
         break;
     }
 
 exit:
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         LogFrame("Dropping rx frame", aFrame, error);
     }
@@ -1120,7 +1161,7 @@
                                    const Mac::Address &  aMacDest,
                                    const ThreadLinkInfo &aLinkInfo)
 {
-    otError                error = OT_ERROR_NONE;
+    Error                  error = kErrorNone;
     Lowpan::FragmentHeader fragmentHeader;
     uint16_t               fragmentHeaderLength;
     Message *              message = nullptr;
@@ -1142,11 +1183,11 @@
 
             if (neighbor->IsLastRxFragmentTagSet())
             {
-                VerifyOrExit(!neighbor->IsLastRxFragmentTagAfter(tag), error = OT_ERROR_DUPLICATED);
+                VerifyOrExit(!neighbor->IsLastRxFragmentTagAfter(tag), error = kErrorDuplicated);
 
                 if (neighbor->GetLastRxFragmentTag() == tag)
                 {
-                    VerifyOrExit(fragmentHeader.GetDatagramOffset() != 0, error = OT_ERROR_DUPLICATED);
+                    VerifyOrExit(fragmentHeader.GetDatagramOffset() != 0, error = kErrorDuplicated);
 
                     // Duplication suppression for a "next fragment" is handled
                     // by the code below where the the datagram offset is
@@ -1172,7 +1213,7 @@
         error = FrameToMessage(aFrame, aFrameLength, datagramSize, aMacSource, aMacDest, message);
         SuccessOrExit(error);
 
-        VerifyOrExit(datagramSize >= message->GetLength(), error = OT_ERROR_PARSE);
+        VerifyOrExit(datagramSize >= message->GetLength(), error = kErrorParse);
         error = message->SetLength(datagramSize);
         SuccessOrExit(error);
 
@@ -1180,7 +1221,7 @@
         message->SetTimeout(kReassemblyTimeout);
         message->SetLinkInfo(aLinkInfo);
 
-        VerifyOrExit(Get<Ip6::Filter>().Accept(*message), error = OT_ERROR_DROP);
+        VerifyOrExit(Get<Ip6::Filter>().Accept(*message), error = kErrorDrop);
 
 #if OPENTHREAD_FTD
         SendIcmpErrorIfDstUnreach(*message, aMacSource, aMacDest);
@@ -1225,7 +1266,7 @@
             ClearReassemblyList();
         }
 
-        VerifyOrExit(message != nullptr, error = OT_ERROR_DROP);
+        VerifyOrExit(message != nullptr, error = kErrorDrop);
 
         message->WriteBytes(message->GetOffset(), aFrame, aFrameLength);
         message->MoveOffset(aFrameLength);
@@ -1238,7 +1279,7 @@
 
 exit:
 
-    if (error == OT_ERROR_NONE)
+    if (error == kErrorNone)
     {
         if (message->GetOffset() >= message->GetLength())
         {
@@ -1264,7 +1305,7 @@
         next = message->GetNext();
         mReassemblyList.Dequeue(*message);
 
-        LogMessage(kMessageReassemblyDrop, *message, nullptr, OT_ERROR_NO_FRAME_RECEIVED);
+        LogMessage(kMessageReassemblyDrop, *message, nullptr, kErrorNoFrameReceived);
 
         if (message->GetType() == Message::kTypeIp6)
         {
@@ -1307,7 +1348,7 @@
         {
             mReassemblyList.Dequeue(*message);
 
-            LogMessage(kMessageReassemblyDrop, *message, nullptr, OT_ERROR_REASSEMBLY_TIMEOUT);
+            LogMessage(kMessageReassemblyDrop, *message, nullptr, kErrorReassemblyTimeout);
             if (message->GetType() == Message::kTypeIp6)
             {
                 mIpCounters.mRxFailure++;
@@ -1320,14 +1361,14 @@
     return mReassemblyList.GetHead() != nullptr;
 }
 
-otError MeshForwarder::FrameToMessage(const uint8_t *     aFrame,
-                                      uint16_t            aFrameLength,
-                                      uint16_t            aDatagramSize,
-                                      const Mac::Address &aMacSource,
-                                      const Mac::Address &aMacDest,
-                                      Message *&          aMessage)
+Error MeshForwarder::FrameToMessage(const uint8_t *     aFrame,
+                                    uint16_t            aFrameLength,
+                                    uint16_t            aDatagramSize,
+                                    const Mac::Address &aMacSource,
+                                    const Mac::Address &aMacDest,
+                                    Message *&          aMessage)
 {
-    otError           error = OT_ERROR_NONE;
+    Error             error = kErrorNone;
     int               headerLength;
     Message::Priority priority;
 
@@ -1335,11 +1376,11 @@
     SuccessOrExit(error);
 
     aMessage = Get<MessagePool>().New(Message::kTypeIp6, 0, priority);
-    VerifyOrExit(aMessage, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(aMessage, error = kErrorNoBufs);
 
     headerLength =
         Get<Lowpan::Lowpan>().Decompress(*aMessage, aMacSource, aMacDest, aFrame, aFrameLength, aDatagramSize);
-    VerifyOrExit(headerLength > 0, error = OT_ERROR_PARSE);
+    VerifyOrExit(headerLength > 0, error = kErrorParse);
 
     aFrame += headerLength;
     aFrameLength -= static_cast<uint16_t>(headerLength);
@@ -1358,7 +1399,7 @@
                                    const Mac::Address &  aMacDest,
                                    const ThreadLinkInfo &aLinkInfo)
 {
-    otError  error   = OT_ERROR_NONE;
+    Error    error   = kErrorNone;
     Message *message = nullptr;
 
 #if OPENTHREAD_FTD
@@ -1369,7 +1410,7 @@
 
     message->SetLinkInfo(aLinkInfo);
 
-    VerifyOrExit(Get<Ip6::Filter>().Accept(*message), error = OT_ERROR_DROP);
+    VerifyOrExit(Get<Ip6::Filter>().Accept(*message), error = kErrorDrop);
 
 #if OPENTHREAD_FTD
     SendIcmpErrorIfDstUnreach(*message, aMacSource, aMacDest);
@@ -1377,7 +1418,7 @@
 
 exit:
 
-    if (error == OT_ERROR_NONE)
+    if (error == kErrorNone)
     {
         IgnoreError(HandleDatagram(*message, aLinkInfo, aMacSource));
     }
@@ -1388,13 +1429,11 @@
     }
 }
 
-otError MeshForwarder::HandleDatagram(Message &             aMessage,
-                                      const ThreadLinkInfo &aLinkInfo,
-                                      const Mac::Address &  aMacSource)
+Error MeshForwarder::HandleDatagram(Message &aMessage, const ThreadLinkInfo &aLinkInfo, const Mac::Address &aMacSource)
 {
     ThreadNetif &netif = Get<ThreadNetif>();
 
-    LogMessage(kMessageReceive, aMessage, &aMacSource, OT_ERROR_NONE);
+    LogMessage(kMessageReceive, aMessage, &aMacSource, kErrorNone);
 
     if (aMessage.GetType() == Message::kTypeIp6)
     {
@@ -1404,13 +1443,13 @@
     return Get<Ip6::Ip6>().HandleDatagram(aMessage, &netif, &aLinkInfo, false);
 }
 
-otError MeshForwarder::GetFramePriority(const uint8_t *     aFrame,
-                                        uint16_t            aFrameLength,
-                                        const Mac::Address &aMacSource,
-                                        const Mac::Address &aMacDest,
-                                        Message::Priority & aPriority)
+Error MeshForwarder::GetFramePriority(const uint8_t *     aFrame,
+                                      uint16_t            aFrameLength,
+                                      const Mac::Address &aMacSource,
+                                      const Mac::Address &aMacDest,
+                                      Message::Priority & aPriority)
 {
-    otError     error = OT_ERROR_NONE;
+    Error       error = kErrorNone;
     Ip6::Header ip6Header;
     uint16_t    dstPort;
     uint8_t     headerLength;
@@ -1427,7 +1466,7 @@
     {
     case Ip6::kProtoIcmp6:
 
-        VerifyOrExit(aFrameLength >= sizeof(Ip6::Icmp::Header), error = OT_ERROR_PARSE);
+        VerifyOrExit(aFrameLength >= sizeof(Ip6::Icmp::Header), error = kErrorParse);
 
         // Only ICMPv6 error messages are prioritized.
         if (reinterpret_cast<const Ip6::Icmp::Header *>(aFrame)->IsError())
@@ -1444,13 +1483,13 @@
             Ip6::Udp::Header udpHeader;
 
             VerifyOrExit(Get<Lowpan::Lowpan>().DecompressUdpHeader(udpHeader, aFrame, aFrameLength) >= 0,
-                         error = OT_ERROR_PARSE);
+                         error = kErrorParse);
 
             dstPort = udpHeader.GetDestinationPort();
         }
         else
         {
-            VerifyOrExit(aFrameLength >= sizeof(Ip6::Udp::Header), error = OT_ERROR_PARSE);
+            VerifyOrExit(aFrameLength >= sizeof(Ip6::Udp::Header), error = kErrorParse);
             dstPort = reinterpret_cast<const Ip6::Udp::Header *>(aFrame)->GetDestinationPort();
         }
 
@@ -1470,38 +1509,111 @@
 }
 
 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
-otError MeshForwarder::SendEmptyMessage(void)
+Error MeshForwarder::SendEmptyMessage(void)
 {
-    otError  error   = OT_ERROR_NONE;
+    Error    error   = kErrorNone;
     Message *message = nullptr;
 
     VerifyOrExit(mEnabled && !Get<Mac::Mac>().GetRxOnWhenIdle() &&
                      Get<Mle::MleRouter>().GetParent().IsStateValidOrRestoring(),
-                 error = OT_ERROR_INVALID_STATE);
+                 error = kErrorInvalidState);
 
     message = Get<MessagePool>().New(Message::kTypeMacEmptyData, 0);
-    VerifyOrExit(message != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(message != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = SendMessage(*message));
 
 exit:
     FreeMessageOnError(message, error);
-    otLogDebgMac("Send empty message, error:%s", otThreadErrorToString(error));
+    otLogDebgMac("Send empty message, error:%s", ErrorToString(error));
     return error;
 }
 #endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
 
+bool MeshForwarder::CalcIePresent(const Message *aMessage)
+{
+    bool iePresent = false;
+
+    OT_UNUSED_VARIABLE(aMessage);
+
+#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
+    iePresent |= (aMessage != nullptr && aMessage->IsTimeSync());
+#endif
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+    iePresent |= Get<Mac::Mac>().IsCslEnabled();
+#endif
+
+    return iePresent;
+}
+
+#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
+void MeshForwarder::AppendHeaderIe(const Message *aMessage, Mac::Frame &aFrame)
+{
+    uint8_t index     = 0;
+    bool    iePresent = false;
+    bool    payloadPresent =
+        (aFrame.GetType() == Mac::Frame::kFcfFrameMacCmd) || (aMessage != nullptr && aMessage->GetLength() != 0);
+
+#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
+    if (aMessage != nullptr && aMessage->IsTimeSync())
+    {
+        IgnoreError(aFrame.AppendHeaderIeAt<Mac::TimeIe>(index));
+        iePresent = true;
+    }
+#endif
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+    if (Get<Mac::Mac>().IsCslEnabled())
+    {
+        IgnoreError(aFrame.AppendHeaderIeAt<Mac::CslIe>(index));
+        iePresent = true;
+    }
+#endif
+
+    if (iePresent && payloadPresent)
+    {
+        // Assume no Payload IE in current implementation
+        IgnoreError(aFrame.AppendHeaderIeAt<Mac::Termination2Ie>(index));
+    }
+}
+#endif
+
+uint16_t MeshForwarder::CalcFrameVersion(const Neighbor *aNeighbor, bool aIePresent)
+{
+    uint16_t version = Mac::Frame::kFcfFrameVersion2006;
+    OT_UNUSED_VARIABLE(aNeighbor);
+
+    if (aIePresent)
+    {
+        version = Mac::Frame::kFcfFrameVersion2015;
+    }
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+    else if (aNeighbor != nullptr && !Mle::MleRouter::IsActiveRouter(aNeighbor->GetRloc16()) &&
+             static_cast<const Child *>(aNeighbor)->IsCslSynchronized())
+    {
+        version = Mac::Frame::kFcfFrameVersion2015;
+    }
+#endif
+#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
+    else if (aNeighbor != nullptr && aNeighbor->IsEnhAckProbingActive())
+    {
+        version = Mac::Frame::kFcfFrameVersion2015; ///< Set version to 2015 to fetch Link Metrics data in Enh-ACK.
+    }
+#endif
+
+    return version;
+}
+
 // LCOV_EXCL_START
 
 #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_NOTE) && (OPENTHREAD_CONFIG_LOG_MAC == 1)
 
-otError MeshForwarder::ParseIp6UdpTcpHeader(const Message &aMessage,
-                                            Ip6::Header &  aIp6Header,
-                                            uint16_t &     aChecksum,
-                                            uint16_t &     aSourcePort,
-                                            uint16_t &     aDestPort)
+Error MeshForwarder::ParseIp6UdpTcpHeader(const Message &aMessage,
+                                          Ip6::Header &  aIp6Header,
+                                          uint16_t &     aChecksum,
+                                          uint16_t &     aSourcePort,
+                                          uint16_t &     aDestPort)
 {
-    otError error = OT_ERROR_PARSE;
+    Error error = kErrorParse;
     union
     {
         Ip6::Udp::Header udp;
@@ -1535,70 +1647,36 @@
         break;
     }
 
-    error = OT_ERROR_NONE;
+    error = kErrorNone;
 
 exit:
     return error;
 }
 
-const char *MeshForwarder::MessageActionToString(MessageAction aAction, otError aError)
+const char *MeshForwarder::MessageActionToString(MessageAction aAction, Error aError)
 {
-    const char *actionText = "";
+    static const char *const kMessageActionStrings[] = {
+        "Received",                    // (0) kMessageReceive
+        "Sent",                        // (1) kMessageTransmit
+        "Prepping indir tx",           // (2) kMessagePrepareIndirect
+        "Dropping",                    // (3) kMessageDrop
+        "Dropping (reassembly queue)", // (4) kMessageReassemblyDrop
+        "Evicting",                    // (5) kMessageEvict
+    };
 
-    switch (aAction)
-    {
-    case kMessageReceive:
-        actionText = "Received";
-        break;
+    static_assert(kMessageReceive == 0, "kMessageReceive value is incorrect");
+    static_assert(kMessageTransmit == 1, "kMessageTransmit value is incorrect");
+    static_assert(kMessagePrepareIndirect == 2, "kMessagePrepareIndirect value is incorrect");
+    static_assert(kMessageDrop == 3, "kMessageDrop value is incorrect");
+    static_assert(kMessageReassemblyDrop == 4, "kMessageReassemblyDrop value is incorrect");
+    static_assert(kMessageEvict == 5, "kMessageEvict value is incorrect");
 
-    case kMessageTransmit:
-        actionText = (aError == OT_ERROR_NONE) ? "Sent" : "Failed to send";
-        break;
-
-    case kMessagePrepareIndirect:
-        actionText = "Prepping indir tx";
-        break;
-
-    case kMessageDrop:
-        actionText = "Dropping";
-        break;
-
-    case kMessageReassemblyDrop:
-        actionText = "Dropping (reassembly queue)";
-        break;
-
-    case kMessageEvict:
-        actionText = "Evicting";
-        break;
-    }
-
-    return actionText;
+    return (aError == kErrorNone) ? kMessageActionStrings[aAction] : "Failed to send";
 }
 
 const char *MeshForwarder::MessagePriorityToString(const Message &aMessage)
 {
-    const char *priorityText = "unknown";
-
-    switch (aMessage.GetPriority())
-    {
-    case Message::kPriorityNet:
-        priorityText = "net";
-        break;
-
-    case Message::kPriorityHigh:
-        priorityText = "high";
-        break;
-
-    case Message::kPriorityNormal:
-        priorityText = "normal";
-        break;
-
-    case Message::kPriorityLow:
-        priorityText = "low";
-        break;
-    }
-
-    return priorityText;
+    return Message::PriorityToString(aMessage.GetPriority());
 }
 
 #if OPENTHREAD_CONFIG_LOG_SRC_DST_IP_ADDRESSES
@@ -1634,7 +1712,7 @@
 void MeshForwarder::LogIp6Message(MessageAction       aAction,
                                   const Message &     aMessage,
                                   const Mac::Address *aMacAddress,
-                                  otError             aError,
+                                  Error               aError,
                                   otLogLevel          aLogLevel)
 {
     Ip6::Header ip6Header;
@@ -1659,10 +1737,11 @@
              aMessage.GetLength(), checksum,
              (aMacAddress == nullptr) ? "" : ((aAction == kMessageReceive) ? ", from:" : ", to:"),
              (aMacAddress == nullptr) ? "" : aMacAddress->ToString().AsCString(),
-             aMessage.IsLinkSecurityEnabled() ? "yes" : "no", (aError == OT_ERROR_NONE) ? "" : ", error:",
-             (aError == OT_ERROR_NONE) ? "" : otThreadErrorToString(aError), MessagePriorityToString(aMessage),
-             shouldLogRss ? ", rss:" : "", shouldLogRss ? aMessage.GetRssAverager().ToString().AsCString() : "",
-             shouldLogRadio ? ", radio:" : "", radioString);
+             aMessage.IsLinkSecurityEnabled() ? "yes" : "no",
+             (aError == kErrorNone) ? "" : ", error:", (aError == kErrorNone) ? "" : ErrorToString(aError),
+             MessagePriorityToString(aMessage), shouldLogRss ? ", rss:" : "",
+             shouldLogRss ? aMessage.GetRssAverager().ToString().AsCString() : "", shouldLogRadio ? ", radio:" : "",
+             radioString);
 
     if (aAction != kMessagePrepareIndirect)
     {
@@ -1676,7 +1755,7 @@
 void MeshForwarder::LogMessage(MessageAction       aAction,
                                const Message &     aMessage,
                                const Mac::Address *aMacAddress,
-                               otError             aError)
+                               Error               aError)
 {
     otLogLevel logLevel = OT_LOG_LEVEL_INFO;
 
@@ -1685,7 +1764,7 @@
     case kMessageReceive:
     case kMessageTransmit:
     case kMessagePrepareIndirect:
-        logLevel = (aError == OT_ERROR_NONE) ? OT_LOG_LEVEL_INFO : OT_LOG_LEVEL_NOTE;
+        logLevel = (aError == kErrorNone) ? OT_LOG_LEVEL_INFO : OT_LOG_LEVEL_NOTE;
         break;
 
     case kMessageDrop:
@@ -1717,12 +1796,11 @@
     return;
 }
 
-void MeshForwarder::LogFrame(const char *aActionText, const Mac::Frame &aFrame, otError aError)
+void MeshForwarder::LogFrame(const char *aActionText, const Mac::Frame &aFrame, Error aError)
 {
-    if (aError != OT_ERROR_NONE)
+    if (aError != kErrorNone)
     {
-        otLogNoteMac("%s, aError:%s, %s", aActionText, otThreadErrorToString(aError),
-                     aFrame.ToInfoString().AsCString());
+        otLogNoteMac("%s, aError:%s, %s", aActionText, ErrorToString(aError), aFrame.ToInfoString().AsCString());
     }
     else
     {
@@ -1730,7 +1808,7 @@
     }
 }
 
-void MeshForwarder::LogFragmentFrameDrop(otError                       aError,
+void MeshForwarder::LogFragmentFrameDrop(Error                         aError,
                                          uint16_t                      aFrameLength,
                                          const Mac::Address &          aMacSource,
                                          const Mac::Address &          aMacDest,
@@ -1738,33 +1816,33 @@
                                          bool                          aIsSecure)
 {
     otLogNoteMac("Dropping rx frag frame, error:%s, len:%d, src:%s, dst:%s, tag:%d, offset:%d, dglen:%d, sec:%s",
-                 otThreadErrorToString(aError), aFrameLength, aMacSource.ToString().AsCString(),
+                 ErrorToString(aError), aFrameLength, aMacSource.ToString().AsCString(),
                  aMacDest.ToString().AsCString(), aFragmentHeader.GetDatagramTag(), aFragmentHeader.GetDatagramOffset(),
                  aFragmentHeader.GetDatagramSize(), aIsSecure ? "yes" : "no");
 }
 
-void MeshForwarder::LogLowpanHcFrameDrop(otError             aError,
+void MeshForwarder::LogLowpanHcFrameDrop(Error               aError,
                                          uint16_t            aFrameLength,
                                          const Mac::Address &aMacSource,
                                          const Mac::Address &aMacDest,
                                          bool                aIsSecure)
 {
-    otLogNoteMac("Dropping rx lowpan HC frame, error:%s, len:%d, src:%s, dst:%s, sec:%s", otThreadErrorToString(aError),
+    otLogNoteMac("Dropping rx lowpan HC frame, error:%s, len:%d, src:%s, dst:%s, sec:%s", ErrorToString(aError),
                  aFrameLength, aMacSource.ToString().AsCString(), aMacDest.ToString().AsCString(),
                  aIsSecure ? "yes" : "no");
 }
 
 #else // #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_NOTE) && (OPENTHREAD_CONFIG_LOG_MAC == 1)
 
-void MeshForwarder::LogMessage(MessageAction, const Message &, const Mac::Address *, otError)
+void MeshForwarder::LogMessage(MessageAction, const Message &, const Mac::Address *, Error)
 {
 }
 
-void MeshForwarder::LogFrame(const char *, const Mac::Frame &, otError)
+void MeshForwarder::LogFrame(const char *, const Mac::Frame &, Error)
 {
 }
 
-void MeshForwarder::LogFragmentFrameDrop(otError,
+void MeshForwarder::LogFragmentFrameDrop(Error,
                                          uint16_t,
                                          const Mac::Address &,
                                          const Mac::Address &,
@@ -1773,7 +1851,7 @@
 {
 }
 
-void MeshForwarder::LogLowpanHcFrameDrop(otError, uint16_t, const Mac::Address &, const Mac::Address &, bool)
+void MeshForwarder::LogLowpanHcFrameDrop(Error, uint16_t, const Mac::Address &, const Mac::Address &, bool)
 {
 }
 
diff --git a/src/core/thread/mesh_forwarder.hpp b/src/core/thread/mesh_forwarder.hpp
index 6a54d58..3758de4 100644
--- a/src/core/thread/mesh_forwarder.hpp
+++ b/src/core/thread/mesh_forwarder.hpp
@@ -44,6 +44,7 @@
 #include "mac/channel_mask.hpp"
 #include "mac/data_poll_sender.hpp"
 #include "mac/mac.hpp"
+#include "mac/mac_frame.hpp"
 #include "net/ip6.hpp"
 #include "thread/address_resolver.hpp"
 #include "thread/indirect_sender.hpp"
@@ -180,33 +181,33 @@
      *
      * @param[in]  aMessage  A reference to the message.
      *
-     * @retval OT_ERROR_NONE     Successfully enqueued the message.
-     * @retval OT_ERROR_ALREADY  The message was already enqueued.
-     * @retval OT_ERROR_DROP     The message could not be sent and should be dropped.
+     * @retval kErrorNone     Successfully enqueued the message.
+     * @retval kErrorAlready  The message was already enqueued.
+     * @retval kErrorDrop     The message could not be sent and should be dropped.
      *
      */
-    otError SendMessage(Message &aMessage);
+    Error SendMessage(Message &aMessage);
 
 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
     /**
      * This method sends an empty data frame to the parent.
      *
-     * @retval OT_ERROR_NONE           Successfully enqueued an empty message.
-     * @retval OT_ERROR_INVALID_STATE  Device is not in Rx-Off-When-Idle mode or it has no parent.
-     * @retval OT_ERROR_NO_BUFS        Insufficient message buffers available.
+     * @retval kErrorNone          Successfully enqueued an empty message.
+     * @retval kErrorInvalidState  Device is not in Rx-Off-When-Idle mode or it has no parent.
+     * @retval kErrorNoBufs        Insufficient message buffers available.
      *
      */
-    otError SendEmptyMessage(void);
+    Error SendEmptyMessage(void);
 #endif
 
     /**
      * This method is called by the address resolver when an EID-to-RLOC mapping has been resolved.
      *
      * @param[in]  aEid    A reference to the EID that has been resolved.
-     * @param[in]  aError  OT_ERROR_NONE on success and OT_ERROR_DROP otherwise.
+     * @param[in]  aError  kErrorNone on success and kErrorDrop otherwise.
      *
      */
-    void HandleResolved(const Ip6::Address &aEid, otError aError);
+    void HandleResolved(const Ip6::Address &aEid, Error aError);
 
     /**
      * This method indicates whether or not rx-on-when-idle mode is enabled.
@@ -235,12 +236,6 @@
     void SetDiscoverParameters(const Mac::ChannelMask &aScanChannels);
 
     /**
-     * This method frees any indirect messages queued for children that are no longer attached.
-     *
-     */
-    void UpdateIndirectMessages(void);
-
-    /**
      * This method frees any messages queued for an existing child.
      *
      * @param[in]  aChild    A reference to the child.
@@ -261,11 +256,11 @@
      *
      * @param[in]  aPriority  The highest priority level of the evicted message.
      *
-     * @retval OT_ERROR_NONE       Successfully evicted a low priority message.
-     * @retval OT_ERROR_NOT_FOUND  No low priority messages available to evict.
+     * @retval kErrorNone       Successfully evicted a low priority message.
+     * @retval kErrorNotFound   No low priority messages available to evict.
      *
      */
-    otError EvictMessage(Message::Priority aPriority);
+    Error EvictMessage(Message::Priority aPriority);
 
     /**
      * This method returns a reference to the send queue.
@@ -317,11 +312,11 @@
      * on the radio link.
      *
      * @param[in] aNeighbor  The neighbor for which the deferred ack status is being reported.
-     * @param[in] aTxError   The deferred ack error status: `OT_ERROR_NONE` to indicate a deferred ack was received,
-     *                       `OT_ERROR_NO_ACK` to indicate an ack timeout.
+     * @param[in] aTxError   The deferred ack error status: `kErrorNone` to indicate a deferred ack was received,
+     *                       `kErrorNoAck` to indicate an ack timeout.
      *
      */
-    void HandleDeferredAck(Neighbor &aNeighbor, otError aTxError);
+    void HandleDeferredAck(Neighbor &aNeighbor, Error aTxError);
 #endif
 
 private:
@@ -332,7 +327,7 @@
         kMeshHeaderFrameFcsSize = sizeof(uint16_t),        // Frame FCS size for Mesh Header frame.
     };
 
-    enum MessageAction ///< Defines the action parameter in `LogMessageInfo()` method.
+    enum MessageAction : uint8_t ///< Defines the action parameter in `LogMessageInfo()` method.
     {
         kMessageReceive,         ///< Indicates that the message was received.
         kMessageTransmit,        ///< Indicates that the message was sent.
@@ -382,32 +377,32 @@
     };
 #endif // OPENTHREAD_FTD
 
-    void    SendIcmpErrorIfDstUnreach(const Message &     aMessage,
-                                      const Mac::Address &aMacSource,
-                                      const Mac::Address &aMacDest);
-    otError CheckReachability(const uint8_t *     aFrame,
-                              uint16_t            aFrameLength,
-                              const Mac::Address &aMeshSource,
-                              const Mac::Address &aMeshDest);
-    void    UpdateRoutes(const uint8_t *     aFrame,
-                         uint16_t            aFrameLength,
-                         const Mac::Address &aMeshSource,
-                         const Mac::Address &aMeshDest);
+    void  SendIcmpErrorIfDstUnreach(const Message &     aMessage,
+                                    const Mac::Address &aMacSource,
+                                    const Mac::Address &aMacDest);
+    Error CheckReachability(const uint8_t *     aFrame,
+                            uint16_t            aFrameLength,
+                            const Mac::Address &aMeshSource,
+                            const Mac::Address &aMeshDest);
+    void  UpdateRoutes(const uint8_t *     aFrame,
+                       uint16_t            aFrameLength,
+                       const Mac::Address &aMeshSource,
+                       const Mac::Address &aMeshDest);
 
-    otError  DecompressIp6Header(const uint8_t *     aFrame,
+    Error    DecompressIp6Header(const uint8_t *     aFrame,
                                  uint16_t            aFrameLength,
                                  const Mac::Address &aMacSource,
                                  const Mac::Address &aMacDest,
                                  Ip6::Header &       aIp6Header,
                                  uint8_t &           aHeaderLength,
                                  bool &              aNextHeaderCompressed);
-    otError  FrameToMessage(const uint8_t *     aFrame,
+    Error    FrameToMessage(const uint8_t *     aFrame,
                             uint16_t            aFrameLength,
                             uint16_t            aDatagramSize,
                             const Mac::Address &aMacSource,
                             const Mac::Address &aMacDest,
                             Message *&          aMessage);
-    otError  GetIp6Header(const uint8_t *     aFrame,
+    Error    GetIp6Header(const uint8_t *     aFrame,
                           uint16_t            aFrameLength,
                           const Mac::Address &aMacSource,
                           const Mac::Address &aMacDest,
@@ -439,101 +434,108 @@
                               bool                aAddFragHeader = false);
     void     PrepareEmptyFrame(Mac::TxFrame &aFrame, const Mac::Address &aMacDest, bool aAckRequest);
 
-    void    SendMesh(Message &aMessage, Mac::TxFrame &aFrame);
-    void    SendDestinationUnreachable(uint16_t aMeshSource, const Message &aMessage);
-    otError UpdateIp6Route(Message &aMessage);
-    otError UpdateIp6RouteFtd(Ip6::Header &ip6Header, Message &aMessage);
-    otError UpdateMeshRoute(Message &aMessage);
-    bool    UpdateReassemblyList(void);
-    void    UpdateFragmentPriority(Lowpan::FragmentHeader &aFragmentHeader,
-                                   uint16_t                aFragmentLength,
-                                   uint16_t                aSrcRloc16,
-                                   Message::Priority       aPriority);
-    otError HandleDatagram(Message &aMessage, const ThreadLinkInfo &aLinkInfo, const Mac::Address &aMacSource);
-    void    ClearReassemblyList(void);
-    void    RemoveMessage(Message &aMessage);
-    void    HandleDiscoverComplete(void);
+    void  SendMesh(Message &aMessage, Mac::TxFrame &aFrame);
+    void  SendDestinationUnreachable(uint16_t aMeshSource, const Message &aMessage);
+    Error UpdateIp6Route(Message &aMessage);
+    Error UpdateIp6RouteFtd(Ip6::Header &ip6Header, Message &aMessage);
+    Error UpdateMeshRoute(Message &aMessage);
+    bool  UpdateReassemblyList(void);
+    void  UpdateFragmentPriority(Lowpan::FragmentHeader &aFragmentHeader,
+                                 uint16_t                aFragmentLength,
+                                 uint16_t                aSrcRloc16,
+                                 Message::Priority       aPriority);
+    Error HandleDatagram(Message &aMessage, const ThreadLinkInfo &aLinkInfo, const Mac::Address &aMacSource);
+    void  ClearReassemblyList(void);
+    void  RemoveMessage(Message &aMessage);
+    void  HandleDiscoverComplete(void);
 
     void          HandleReceivedFrame(Mac::RxFrame &aFrame);
     Mac::TxFrame *HandleFrameRequest(Mac::TxFrames &aTxFrames);
-    Neighbor *    UpdateNeighborOnSentFrame(Mac::TxFrame &aFrame, otError aError, const Mac::Address &aMacDest);
-    void          UpdateNeighborLinkFailures(Neighbor &aNeighbor, otError aError, bool aAllowNeighborRemove);
-    void          HandleSentFrame(Mac::TxFrame &aFrame, otError aError);
-    void          UpdateSendMessage(otError aFrameTxError, Mac::Address &aMacDest, Neighbor *aNeighbor);
+    Neighbor *    UpdateNeighborOnSentFrame(Mac::TxFrame &aFrame, Error aError, const Mac::Address &aMacDest);
+    void          UpdateNeighborLinkFailures(Neighbor &aNeighbor, Error aError, bool aAllowNeighborRemove);
+    void          HandleSentFrame(Mac::TxFrame &aFrame, Error aError);
+    void          UpdateSendMessage(Error aFrameTxError, Mac::Address &aMacDest, Neighbor *aNeighbor);
+    void          RemoveMessageIfNoPendingTx(Message &aMessage);
 
     void        HandleTimeTick(void);
     static void ScheduleTransmissionTask(Tasklet &aTasklet);
     void        ScheduleTransmissionTask(void);
 
-    otError GetFramePriority(const uint8_t *     aFrame,
-                             uint16_t            aFrameLength,
-                             const Mac::Address &aMacSource,
-                             const Mac::Address &aMacDest,
-                             Message::Priority & aPriority);
-    otError GetFragmentPriority(Lowpan::FragmentHeader &aFragmentHeader,
-                                uint16_t                aSrcRloc16,
-                                Message::Priority &     aPriority);
-    void    GetForwardFramePriority(const uint8_t *     aFrame,
-                                    uint16_t            aFrameLength,
-                                    const Mac::Address &aMeshSource,
-                                    const Mac::Address &aMeshDest,
-                                    Message::Priority & aPriority);
+    Error GetFramePriority(const uint8_t *     aFrame,
+                           uint16_t            aFrameLength,
+                           const Mac::Address &aMacSource,
+                           const Mac::Address &aMacDest,
+                           Message::Priority & aPriority);
+    Error GetFragmentPriority(Lowpan::FragmentHeader &aFragmentHeader,
+                              uint16_t                aSrcRloc16,
+                              Message::Priority &     aPriority);
+    void  GetForwardFramePriority(const uint8_t *     aFrame,
+                                  uint16_t            aFrameLength,
+                                  const Mac::Address &aMeshSource,
+                                  const Mac::Address &aMeshDest,
+                                  Message::Priority & aPriority);
 
-    otError GetDestinationRlocByServiceAloc(uint16_t aServiceAloc, uint16_t &aMeshDest);
+    Error GetDestinationRlocByServiceAloc(uint16_t aServiceAloc, uint16_t &aMeshDest);
+
+    bool     CalcIePresent(const Message *aMessage);
+    uint16_t CalcFrameVersion(const Neighbor *aNeighbor, bool aIePresent);
+#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
+    void AppendHeaderIe(const Message *aMessage, Mac::Frame &aFrame);
+#endif
 
     void PauseMessageTransmissions(void) { mTxPaused = true; }
     void ResumeMessageTransmissions(void);
 
-    void LogMessage(MessageAction aAction, const Message &aMessage, const Mac::Address *aAddress, otError aError);
-    void LogFrame(const char *aActionText, const Mac::Frame &aFrame, otError aError);
-    void LogFragmentFrameDrop(otError                       aError,
+    void LogMessage(MessageAction aAction, const Message &aMessage, const Mac::Address *aAddress, Error aError);
+    void LogFrame(const char *aActionText, const Mac::Frame &aFrame, Error aError);
+    void LogFragmentFrameDrop(Error                         aError,
                               uint16_t                      aFrameLength,
                               const Mac::Address &          aMacSource,
                               const Mac::Address &          aMacDest,
                               const Lowpan::FragmentHeader &aFragmentHeader,
                               bool                          aIsSecure);
-    void LogLowpanHcFrameDrop(otError             aError,
+    void LogLowpanHcFrameDrop(Error               aError,
                               uint16_t            aFrameLength,
                               const Mac::Address &aMacSource,
                               const Mac::Address &aMacDest,
                               bool                aIsSecure);
 
 #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_NOTE) && (OPENTHREAD_CONFIG_LOG_MAC == 1)
-    const char *MessageActionToString(MessageAction aAction, otError aError);
+    const char *MessageActionToString(MessageAction aAction, Error aError);
     const char *MessagePriorityToString(const Message &aMessage);
 
-    otError ParseIp6UdpTcpHeader(const Message &aMessage,
-                                 Ip6::Header &  aIp6Header,
-                                 uint16_t &     aChecksum,
-                                 uint16_t &     aSourcePort,
-                                 uint16_t &     aDestPort);
+    Error ParseIp6UdpTcpHeader(const Message &aMessage,
+                               Ip6::Header &  aIp6Header,
+                               uint16_t &     aChecksum,
+                               uint16_t &     aSourcePort,
+                               uint16_t &     aDestPort);
 #if OPENTHREAD_FTD
-    otError DecompressIp6UdpTcpHeader(const Message &     aMessage,
-                                      uint16_t            aOffset,
-                                      const Mac::Address &aMeshSource,
-                                      const Mac::Address &aMeshDest,
-                                      Ip6::Header &       aIp6Header,
-                                      uint16_t &          aChecksum,
-                                      uint16_t &          aSourcePort,
-                                      uint16_t &          aDestPort);
-    otError LogMeshFragmentHeader(MessageAction       aAction,
-                                  const Message &     aMessage,
-                                  const Mac::Address *aMacAddress,
-                                  otError             aError,
-                                  uint16_t &          aOffset,
-                                  Mac::Address &      aMeshSource,
-                                  Mac::Address &      aMeshDest,
-                                  otLogLevel          aLogLevel);
-    void    LogMeshIpHeader(const Message &     aMessage,
-                            uint16_t            aOffset,
-                            const Mac::Address &aMeshSource,
-                            const Mac::Address &aMeshDest,
-                            otLogLevel          aLogLevel);
-    void    LogMeshMessage(MessageAction       aAction,
-                           const Message &     aMessage,
-                           const Mac::Address *aAddress,
-                           otError             aError,
-                           otLogLevel          aLogLevel);
+    Error DecompressIp6UdpTcpHeader(const Message &     aMessage,
+                                    uint16_t            aOffset,
+                                    const Mac::Address &aMeshSource,
+                                    const Mac::Address &aMeshDest,
+                                    Ip6::Header &       aIp6Header,
+                                    uint16_t &          aChecksum,
+                                    uint16_t &          aSourcePort,
+                                    uint16_t &          aDestPort);
+    Error LogMeshFragmentHeader(MessageAction       aAction,
+                                const Message &     aMessage,
+                                const Mac::Address *aMacAddress,
+                                Error               aError,
+                                uint16_t &          aOffset,
+                                Mac::Address &      aMeshSource,
+                                Mac::Address &      aMeshDest,
+                                otLogLevel          aLogLevel);
+    void  LogMeshIpHeader(const Message &     aMessage,
+                          uint16_t            aOffset,
+                          const Mac::Address &aMeshSource,
+                          const Mac::Address &aMeshDest,
+                          otLogLevel          aLogLevel);
+    void  LogMeshMessage(MessageAction       aAction,
+                         const Message &     aMessage,
+                         const Mac::Address *aAddress,
+                         Error               aError,
+                         otLogLevel          aLogLevel);
 #endif
     void LogIp6SourceDestAddresses(Ip6::Header &aIp6Header,
                                    uint16_t     aSourcePort,
@@ -542,7 +544,7 @@
     void LogIp6Message(MessageAction       aAction,
                        const Message &     aMessage,
                        const Mac::Address *aAddress,
-                       otError             aError,
+                       Error               aError,
                        otLogLevel          aLogLevel);
 #endif // #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_NOTE) && (OPENTHREAD_CONFIG_LOG_MAC == 1)
 
diff --git a/src/core/thread/mesh_forwarder_ftd.cpp b/src/core/thread/mesh_forwarder_ftd.cpp
index 4d87316..0ce2f4d 100644
--- a/src/core/thread/mesh_forwarder_ftd.cpp
+++ b/src/core/thread/mesh_forwarder_ftd.cpp
@@ -44,10 +44,10 @@
 
 namespace ot {
 
-otError MeshForwarder::SendMessage(Message &aMessage)
+Error MeshForwarder::SendMessage(Message &aMessage)
 {
     Mle::MleRouter &mle   = Get<Mle::MleRouter>();
-    otError         error = OT_ERROR_NONE;
+    Error           error = kErrorNone;
     Neighbor *      neighbor;
 
     aMessage.SetOffset(0);
@@ -135,7 +135,7 @@
     return error;
 }
 
-void MeshForwarder::HandleResolved(const Ip6::Address &aEid, otError aError)
+void MeshForwarder::HandleResolved(const Ip6::Address &aEid, Error aError)
 {
     Message *    cur, *next;
     Ip6::Address ip6Dst;
@@ -156,7 +156,7 @@
         {
             mResolvingQueue.Dequeue(*cur);
 
-            if (aError == OT_ERROR_NONE)
+            if (aError == kErrorNone)
             {
                 mSendQueue.Enqueue(*cur);
                 enqueuedMessage = true;
@@ -175,9 +175,9 @@
     }
 }
 
-otError MeshForwarder::EvictMessage(Message::Priority aPriority)
+Error MeshForwarder::EvictMessage(Message::Priority aPriority)
 {
-    otError        error    = OT_ERROR_NOT_FOUND;
+    Error          error    = kErrorNotFound;
     PriorityQueue *queues[] = {&mResolvingQueue, &mSendQueue};
     Message *      evict    = nullptr;
 
@@ -208,7 +208,7 @@
 
     if (evict != nullptr)
     {
-        ExitNow(error = OT_ERROR_NONE);
+        ExitNow(error = kErrorNone);
     }
 
     for (uint8_t priority = aPriority; priority < Message::kNumPriorities; priority++)
@@ -229,14 +229,14 @@
             if (message->IsChildPending())
             {
                 evict = message;
-                ExitNow(error = OT_ERROR_NONE);
+                ExitNow(error = kErrorNone);
             }
         }
     }
 
 exit:
 
-    if (error == OT_ERROR_NONE)
+    if (error == kErrorNone)
     {
         RemoveMessage(*evict);
     }
@@ -257,7 +257,7 @@
             continue;
         }
 
-        if (mIndirectSender.RemoveMessageFromSleepyChild(*message, aChild) != OT_ERROR_NONE)
+        if (mIndirectSender.RemoveMessageFromSleepyChild(*message, aChild) != kErrorNone)
         {
             switch (message->GetType())
             {
@@ -294,16 +294,7 @@
             }
         }
 
-        if (!message->IsChildPending() && !message->GetDirectTransmission())
-        {
-            if (mSendMessage == message)
-            {
-                mSendMessage = nullptr;
-            }
-
-            mSendQueue.Dequeue(*message);
-            message->Free();
-        }
+        RemoveMessageIfNoPendingTx(*message);
     }
 }
 
@@ -334,7 +325,7 @@
         }
 
         mSendQueue.Dequeue(*message);
-        LogMessage(kMessageDrop, *message, nullptr, OT_ERROR_NONE);
+        LogMessage(kMessageDrop, *message, nullptr, kErrorNone);
         message->Free();
     }
 }
@@ -342,29 +333,28 @@
 void MeshForwarder::SendMesh(Message &aMessage, Mac::TxFrame &aFrame)
 {
     uint16_t fcf;
+    bool     iePresent = CalcIePresent(&aMessage);
 
     // initialize MAC header
     fcf = Mac::Frame::kFcfFrameData | Mac::Frame::kFcfPanidCompression | Mac::Frame::kFcfDstAddrShort |
           Mac::Frame::kFcfSrcAddrShort | Mac::Frame::kFcfAckRequest | Mac::Frame::kFcfSecurityEnabled;
-    Get<Mac::Mac>().UpdateFrameControlField(nullptr, aMessage.IsTimeSync(), fcf);
+
+    if (iePresent)
+    {
+        fcf |= Mac::Frame::kFcfIePresent;
+    }
+
+    fcf |= CalcFrameVersion(Get<NeighborTable>().FindNeighbor(mMacDest), iePresent);
 
     aFrame.InitMacHeader(fcf, Mac::Frame::kKeyIdMode1 | Mac::Frame::kSecEncMic32);
     aFrame.SetDstPanId(Get<Mac::Mac>().GetPanId());
     aFrame.SetDstAddr(mMacDest.GetShort());
     aFrame.SetSrcAddr(mMacSource.GetShort());
 
-#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
-    if (Get<Mac::Mac>().IsCslEnabled())
+#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
+    if (iePresent)
     {
-        Mac::HeaderIe ieList[2]; // CSL + Termination
-
-        ieList[0].Init();
-        ieList[0].SetId(Mac::Frame::kHeaderIeCsl);
-        ieList[0].SetLength(sizeof(Mac::CslIe));
-        ieList[1].Init();
-        ieList[1].SetId(Mac::Frame::kHeaderIeTermination2);
-        ieList[1].SetLength(0);
-        IgnoreError(aFrame.AppendHeaderIe(ieList, 2));
+        AppendHeaderIe(&aMessage, aFrame);
     }
 #endif
 
@@ -376,9 +366,9 @@
     mMessageNextOffset = aMessage.GetLength();
 }
 
-otError MeshForwarder::UpdateMeshRoute(Message &aMessage)
+Error MeshForwarder::UpdateMeshRoute(Message &aMessage)
 {
-    otError            error = OT_ERROR_NONE;
+    Error              error = kErrorNone;
     Lowpan::MeshHeader meshHeader;
     Neighbor *         neighbor;
     uint16_t           nextHop;
@@ -398,7 +388,7 @@
 
     if (neighbor == nullptr)
     {
-        ExitNow(error = OT_ERROR_DROP);
+        ExitNow(error = kErrorDrop);
     }
 
     mMacDest.SetShort(neighbor->GetRloc16());
@@ -412,10 +402,10 @@
     return error;
 }
 
-otError MeshForwarder::UpdateIp6RouteFtd(Ip6::Header &ip6Header, Message &aMessage)
+Error MeshForwarder::UpdateIp6RouteFtd(Ip6::Header &ip6Header, Message &aMessage)
 {
     Mle::MleRouter &mle   = Get<Mle::MleRouter>();
-    otError         error = OT_ERROR_NONE;
+    Error           error = kErrorNone;
     Neighbor *      neighbor;
 
     if (aMessage.GetOffset() > 0)
@@ -425,7 +415,7 @@
     else if (mle.IsRoutingLocator(ip6Header.GetDestination()))
     {
         uint16_t rloc16 = ip6Header.GetDestination().GetIid().GetLocator();
-        VerifyOrExit(mle.IsRouterIdValid(Mle::Mle::RouterIdFromRloc16(rloc16)), error = OT_ERROR_DROP);
+        VerifyOrExit(mle.IsRouterIdValid(Mle::Mle::RouterIdFromRloc16(rloc16)), error = kErrorDrop);
         mMeshDest = rloc16;
     }
     else if (mle.IsAnycastLocator(ip6Header.GetDestination()))
@@ -441,8 +431,8 @@
             uint16_t agentRloc16;
             uint8_t  routerId;
             VerifyOrExit((Get<NetworkData::Leader>().GetRlocByContextId(
-                              static_cast<uint8_t>(aloc16 & Mle::kAloc16DhcpAgentMask), agentRloc16) == OT_ERROR_NONE),
-                         error = OT_ERROR_DROP);
+                              static_cast<uint8_t>(aloc16 & Mle::kAloc16DhcpAgentMask), agentRloc16) == kErrorNone),
+                         error = kErrorDrop);
 
             routerId = Mle::Mle::RouterIdFromRloc16(agentRloc16);
 
@@ -469,14 +459,14 @@
 #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
         else if (aloc16 == Mle::kAloc16BackboneRouterPrimary)
         {
-            VerifyOrExit(Get<BackboneRouter::Leader>().HasPrimary(), error = OT_ERROR_DROP);
+            VerifyOrExit(Get<BackboneRouter::Leader>().HasPrimary(), error = kErrorDrop);
             mMeshDest = Get<BackboneRouter::Leader>().GetServer16();
         }
 #endif
         else
         {
             // TODO: support for Neighbor Discovery Agent ALOC
-            ExitNow(error = OT_ERROR_DROP);
+            ExitNow(error = kErrorDrop);
         }
     }
     else if ((neighbor = Get<NeighborTable>().FindNeighbor(ip6Header.GetDestination())) != nullptr)
@@ -493,7 +483,7 @@
                                                            &mMeshDest));
     }
 
-    VerifyOrExit(mMeshDest != Mac::kShortAddrInvalid, error = OT_ERROR_DROP);
+    VerifyOrExit(mMeshDest != Mac::kShortAddrInvalid, error = kErrorDrop);
 
     mMeshSource = Get<Mac::Mac>().GetShortAddress();
 
@@ -512,11 +502,11 @@
     return error;
 }
 
-otError MeshForwarder::GetIp6Header(const uint8_t *     aFrame,
-                                    uint16_t            aFrameLength,
-                                    const Mac::Address &aMacSource,
-                                    const Mac::Address &aMacDest,
-                                    Ip6::Header &       aIp6Header)
+Error MeshForwarder::GetIp6Header(const uint8_t *     aFrame,
+                                  uint16_t            aFrameLength,
+                                  const Mac::Address &aMacSource,
+                                  const Mac::Address &aMacDest,
+                                  Ip6::Header &       aIp6Header)
 {
     uint8_t headerLength;
     bool    nextHeaderCompressed;
@@ -529,7 +519,7 @@
                                               const Mac::Address &aMacSource,
                                               const Mac::Address &aMacDest)
 {
-    otError     error;
+    Error       error;
     Ip6::Header ip6header;
     Child *     child;
 
@@ -544,7 +534,7 @@
 
     error = Get<Mle::MleRouter>().CheckReachability(aMacDest.GetShort(), ip6header);
 
-    if (error == OT_ERROR_NO_ROUTE)
+    if (error == kErrorNoRoute)
     {
         SendDestinationUnreachable(aMacSource.GetShort(), aMessage);
     }
@@ -553,29 +543,29 @@
     return;
 }
 
-otError MeshForwarder::CheckReachability(const uint8_t *     aFrame,
-                                         uint16_t            aFrameLength,
-                                         const Mac::Address &aMeshSource,
-                                         const Mac::Address &aMeshDest)
+Error MeshForwarder::CheckReachability(const uint8_t *     aFrame,
+                                       uint16_t            aFrameLength,
+                                       const Mac::Address &aMeshSource,
+                                       const Mac::Address &aMeshDest)
 {
-    otError                error = OT_ERROR_NONE;
+    Error                  error = kErrorNone;
     Ip6::Header            ip6Header;
     Message *              message = nullptr;
     Lowpan::FragmentHeader fragmentHeader;
     uint16_t               fragmentHeaderLength;
     uint16_t               datagramSize = 0;
 
-    if (fragmentHeader.ParseFrom(aFrame, aFrameLength, fragmentHeaderLength) == OT_ERROR_NONE)
+    if (fragmentHeader.ParseFrom(aFrame, aFrameLength, fragmentHeaderLength) == kErrorNone)
     {
         // Only the first fragment header is followed by a LOWPAN_IPHC header
-        VerifyOrExit(fragmentHeader.GetDatagramOffset() == 0, error = OT_ERROR_NOT_FOUND);
+        VerifyOrExit(fragmentHeader.GetDatagramOffset() == 0, error = kErrorNotFound);
         aFrame += fragmentHeaderLength;
         aFrameLength -= fragmentHeaderLength;
 
         datagramSize = fragmentHeader.GetDatagramSize();
     }
 
-    VerifyOrExit(aFrameLength >= 1 && Lowpan::Lowpan::IsLowpanHc(aFrame), error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(aFrameLength >= 1 && Lowpan::Lowpan::IsLowpanHc(aFrame), error = kErrorNotFound);
 
     error = FrameToMessage(aFrame, aFrameLength, datagramSize, aMeshSource, aMeshDest, message);
     SuccessOrExit(error);
@@ -584,12 +574,12 @@
     error = Get<Mle::MleRouter>().CheckReachability(aMeshDest.GetShort(), ip6Header);
 
 exit:
-    if (error == OT_ERROR_NOT_FOUND)
+    if (error == kErrorNotFound)
     {
         // the message may not contain an IPv6 header
-        error = OT_ERROR_NONE;
+        error = kErrorNone;
     }
-    else if (error == OT_ERROR_NO_ROUTE)
+    else if (error == kErrorNoRoute)
     {
         SendDestinationUnreachable(aMeshSource.GetShort(), *message);
     }
@@ -615,7 +605,7 @@
                                const Mac::Address &  aMacSource,
                                const ThreadLinkInfo &aLinkInfo)
 {
-    otError            error   = OT_ERROR_NONE;
+    Error              error   = kErrorNone;
     Message *          message = nullptr;
     Mac::Address       meshDest;
     Mac::Address       meshSource;
@@ -623,7 +613,7 @@
     uint16_t           headerLength;
 
     // Security Check: only process Mesh Header frames that had security enabled.
-    VerifyOrExit(aLinkInfo.IsLinkSecurityEnabled(), error = OT_ERROR_SECURITY);
+    VerifyOrExit(aLinkInfo.IsLinkSecurityEnabled(), error = kErrorSecurity);
 
     SuccessOrExit(error = meshHeader.ParseFrom(aFrame, aFrameLength, headerLength));
 
@@ -648,7 +638,7 @@
         }
         else
         {
-            ExitNow(error = OT_ERROR_PARSE);
+            ExitNow(error = kErrorParse);
         }
     }
     else if (meshHeader.GetHopsLeft() > 0)
@@ -664,7 +654,7 @@
 
         GetForwardFramePriority(aFrame, aFrameLength, meshSource, meshDest, priority);
         message = Get<MessagePool>().New(Message::kType6lowpan, priority);
-        VerifyOrExit(message != nullptr, error = OT_ERROR_NO_BUFS);
+        VerifyOrExit(message != nullptr, error = kErrorNoBufs);
 
         SuccessOrExit(error = message->SetLength(meshHeader.GetHeaderLength() + aFrameLength));
         offset += meshHeader.WriteTo(*message, offset);
@@ -677,7 +667,7 @@
         message->SetRadioType(static_cast<Mac::RadioType>(aLinkInfo.mRadioType));
 #endif
 
-        LogMessage(kMessageReceive, *message, &aMacSource, OT_ERROR_NONE);
+        LogMessage(kMessageReceive, *message, &aMacSource, kErrorNone);
 
 #if OPENTHREAD_CONFIG_MULTI_RADIO
         // Since the message will be forwarded, we clear the radio
@@ -692,10 +682,10 @@
 
 exit:
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogInfoMac("Dropping rx mesh frame, error:%s, len:%d, src:%s, sec:%s", otThreadErrorToString(error),
-                     aFrameLength, aMacSource.ToString().AsCString(), aLinkInfo.IsLinkSecurityEnabled() ? "yes" : "no");
+        otLogInfoMac("Dropping rx mesh frame, error:%s, len:%d, src:%s, sec:%s", ErrorToString(error), aFrameLength,
+                     aMacSource.ToString().AsCString(), aLinkInfo.IsLinkSecurityEnabled() ? "yes" : "no");
         FreeMessage(message);
     }
 }
@@ -714,8 +704,7 @@
     if (!ip6Header.GetSource().GetIid().IsLocator() &&
         Get<NetworkData::Leader>().IsOnMesh(ip6Header.GetSource()) /* only for on mesh address which may require AQ */)
     {
-        if (Get<AddressResolver>().UpdateCacheEntry(ip6Header.GetSource(), aMeshSource.GetShort()) ==
-            OT_ERROR_NOT_FOUND)
+        if (Get<AddressResolver>().UpdateCacheEntry(ip6Header.GetSource(), aMeshSource.GetShort()) == kErrorNotFound)
         {
             // Thread 1.1 Specification 5.5.2.2: FTDs MAY add/update
             // EID-to-RLOC Map Cache entries by inspecting packets
@@ -836,15 +825,15 @@
     return newEntry;
 }
 
-otError MeshForwarder::GetFragmentPriority(Lowpan::FragmentHeader &aFragmentHeader,
-                                           uint16_t                aSrcRloc16,
-                                           Message::Priority &     aPriority)
+Error MeshForwarder::GetFragmentPriority(Lowpan::FragmentHeader &aFragmentHeader,
+                                         uint16_t                aSrcRloc16,
+                                         Message::Priority &     aPriority)
 {
-    otError                      error = OT_ERROR_NONE;
+    Error                        error = kErrorNone;
     FragmentPriorityList::Entry *entry;
 
     entry = mFragmentPriorityList.FindEntry(aSrcRloc16, aFragmentHeader.GetDatagramTag());
-    VerifyOrExit(entry != nullptr, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(entry != nullptr, error = kErrorNotFound);
     aPriority = entry->GetPriority();
 
 exit:
@@ -857,12 +846,12 @@
                                             const Mac::Address &aMeshDest,
                                             Message::Priority & aPriority)
 {
-    otError                error      = OT_ERROR_NONE;
+    Error                  error      = kErrorNone;
     bool                   isFragment = false;
     Lowpan::FragmentHeader fragmentHeader;
     uint16_t               fragmentHeaderLength;
 
-    if (fragmentHeader.ParseFrom(aFrame, aFrameLength, fragmentHeaderLength) == OT_ERROR_NONE)
+    if (fragmentHeader.ParseFrom(aFrame, aFrameLength, fragmentHeaderLength) == kErrorNone)
     {
         isFragment = true;
         aFrame += fragmentHeaderLength;
@@ -879,11 +868,10 @@
     error = GetFramePriority(aFrame, aFrameLength, aMeshSource, aMeshDest, aPriority);
 
 exit:
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogNoteMac("Failed to get forwarded frame priority, error:%s, len:%d, src:%d, dst:%s",
-                     otThreadErrorToString(error), aFrameLength, aMeshSource.ToString().AsCString(),
-                     aMeshDest.ToString().AsCString());
+        otLogNoteMac("Failed to get forwarded frame priority, error:%s, len:%d, src:%d, dst:%s", ErrorToString(error),
+                     aFrameLength, aMeshSource.ToString().AsCString(), aMeshDest.ToString().AsCString());
     }
     else if (isFragment)
     {
@@ -893,9 +881,9 @@
     return;
 }
 
-otError MeshForwarder::GetDestinationRlocByServiceAloc(uint16_t aServiceAloc, uint16_t &aMeshDest)
+Error MeshForwarder::GetDestinationRlocByServiceAloc(uint16_t aServiceAloc, uint16_t &aMeshDest)
 {
-    otError                        error      = OT_ERROR_NONE;
+    Error                          error      = kErrorNone;
     uint8_t                        serviceId  = Mle::Mle::ServiceIdFromAloc(aServiceAloc);
     const NetworkData::ServiceTlv *serviceTlv = Get<NetworkData::Leader>().FindServiceById(serviceId);
 
@@ -972,13 +960,13 @@
         else
         {
             // ServiceTLV without ServerTLV? Can't forward packet anywhere.
-            ExitNow(error = OT_ERROR_NO_ROUTE);
+            ExitNow(error = kErrorNoRoute);
         }
     }
     else
     {
         // Unknown service, can't forward
-        ExitNow(error = OT_ERROR_NO_ROUTE);
+        ExitNow(error = kErrorNoRoute);
     }
 
 exit:
@@ -989,16 +977,16 @@
 
 #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_NOTE) && (OPENTHREAD_CONFIG_LOG_MAC == 1)
 
-otError MeshForwarder::LogMeshFragmentHeader(MessageAction       aAction,
-                                             const Message &     aMessage,
-                                             const Mac::Address *aMacAddress,
-                                             otError             aError,
-                                             uint16_t &          aOffset,
-                                             Mac::Address &      aMeshSource,
-                                             Mac::Address &      aMeshDest,
-                                             otLogLevel          aLogLevel)
+Error MeshForwarder::LogMeshFragmentHeader(MessageAction       aAction,
+                                           const Message &     aMessage,
+                                           const Mac::Address *aMacAddress,
+                                           Error               aError,
+                                           uint16_t &          aOffset,
+                                           Mac::Address &      aMeshSource,
+                                           Mac::Address &      aMeshDest,
+                                           otLogLevel          aLogLevel)
 {
-    otError                error             = OT_ERROR_FAILED;
+    Error                  error             = kErrorFailed;
     bool                   hasFragmentHeader = false;
     bool                   shouldLogRss;
     Lowpan::MeshHeader     meshHeader;
@@ -1014,7 +1002,7 @@
 
     aOffset = headerLength;
 
-    if (fragmentHeader.ParseFrom(aMessage, aOffset, headerLength) == OT_ERROR_NONE)
+    if (fragmentHeader.ParseFrom(aMessage, aOffset, headerLength) == kErrorNone)
     {
         hasFragmentHeader = true;
         aOffset += headerLength;
@@ -1027,16 +1015,15 @@
     radioString    = aMessage.IsRadioTypeSet() ? RadioTypeToString(aMessage.GetRadioType()) : "all";
 #endif
 
-    otLogMac(
-        aLogLevel, "%s mesh frame, len:%d%s%s, msrc:%s, mdst:%s, hops:%d, frag:%s, sec:%s%s%s%s%s%s%s",
-        MessageActionToString(aAction, aError), aMessage.GetLength(),
-        (aMacAddress == nullptr) ? "" : ((aAction == kMessageReceive) ? ", from:" : ", to:"),
-        (aMacAddress == nullptr) ? "" : aMacAddress->ToString().AsCString(), aMeshSource.ToString().AsCString(),
-        aMeshDest.ToString().AsCString(), meshHeader.GetHopsLeft() + ((aAction == kMessageReceive) ? 1 : 0),
-        hasFragmentHeader ? "yes" : "no", aMessage.IsLinkSecurityEnabled() ? "yes" : "no",
-        (aError == OT_ERROR_NONE) ? "" : ", error:", (aError == OT_ERROR_NONE) ? "" : otThreadErrorToString(aError),
-        shouldLogRss ? ", rss:" : "", shouldLogRss ? aMessage.GetRssAverager().ToString().AsCString() : "",
-        shouldLogRadio ? ", radio:" : "", radioString);
+    otLogMac(aLogLevel, "%s mesh frame, len:%d%s%s, msrc:%s, mdst:%s, hops:%d, frag:%s, sec:%s%s%s%s%s%s%s",
+             MessageActionToString(aAction, aError), aMessage.GetLength(),
+             (aMacAddress == nullptr) ? "" : ((aAction == kMessageReceive) ? ", from:" : ", to:"),
+             (aMacAddress == nullptr) ? "" : aMacAddress->ToString().AsCString(), aMeshSource.ToString().AsCString(),
+             aMeshDest.ToString().AsCString(), meshHeader.GetHopsLeft() + ((aAction == kMessageReceive) ? 1 : 0),
+             hasFragmentHeader ? "yes" : "no", aMessage.IsLinkSecurityEnabled() ? "yes" : "no",
+             (aError == kErrorNone) ? "" : ", error:", (aError == kErrorNone) ? "" : ErrorToString(aError),
+             shouldLogRss ? ", rss:" : "", shouldLogRss ? aMessage.GetRssAverager().ToString().AsCString() : "",
+             shouldLogRadio ? ", radio:" : "", radioString);
 
     if (hasFragmentHeader)
     {
@@ -1046,22 +1033,22 @@
         VerifyOrExit(fragmentHeader.GetDatagramOffset() == 0);
     }
 
-    error = OT_ERROR_NONE;
+    error = kErrorNone;
 
 exit:
     return error;
 }
 
-otError MeshForwarder::DecompressIp6UdpTcpHeader(const Message &     aMessage,
-                                                 uint16_t            aOffset,
-                                                 const Mac::Address &aMeshSource,
-                                                 const Mac::Address &aMeshDest,
-                                                 Ip6::Header &       aIp6Header,
-                                                 uint16_t &          aChecksum,
-                                                 uint16_t &          aSourcePort,
-                                                 uint16_t &          aDestPort)
+Error MeshForwarder::DecompressIp6UdpTcpHeader(const Message &     aMessage,
+                                               uint16_t            aOffset,
+                                               const Mac::Address &aMeshSource,
+                                               const Mac::Address &aMeshDest,
+                                               Ip6::Header &       aIp6Header,
+                                               uint16_t &          aChecksum,
+                                               uint16_t &          aSourcePort,
+                                               uint16_t &          aDestPort)
 {
-    otError  error = OT_ERROR_PARSE;
+    Error    error = kErrorParse;
     int      headerLength;
     bool     nextHeaderCompressed;
     uint8_t  frameBuffer[sizeof(Ip6::Header)];
@@ -1118,7 +1105,7 @@
         break;
     }
 
-    error = OT_ERROR_NONE;
+    error = kErrorNone;
 
 exit:
     return error;
@@ -1150,7 +1137,7 @@
 void MeshForwarder::LogMeshMessage(MessageAction       aAction,
                                    const Message &     aMessage,
                                    const Mac::Address *aMacAddress,
-                                   otError             aError,
+                                   Error               aError,
                                    otLogLevel          aLogLevel)
 {
     uint16_t     offset;
diff --git a/src/core/thread/mesh_forwarder_mtd.cpp b/src/core/thread/mesh_forwarder_mtd.cpp
index a11fc6b..6771791 100644
--- a/src/core/thread/mesh_forwarder_mtd.cpp
+++ b/src/core/thread/mesh_forwarder_mtd.cpp
@@ -37,7 +37,7 @@
 
 namespace ot {
 
-otError MeshForwarder::SendMessage(Message &aMessage)
+Error MeshForwarder::SendMessage(Message &aMessage)
 {
     aMessage.SetDirectTransmission();
     aMessage.SetOffset(0);
@@ -46,12 +46,12 @@
     mSendQueue.Enqueue(aMessage);
     mScheduleTransmissionTask.Post();
 
-    return OT_ERROR_NONE;
+    return kErrorNone;
 }
 
-otError MeshForwarder::EvictMessage(Message::Priority aPriority)
+Error MeshForwarder::EvictMessage(Message::Priority aPriority)
 {
-    otError  error = OT_ERROR_NOT_FOUND;
+    Error    error = kErrorNotFound;
     Message *message;
 
     VerifyOrExit((message = mSendQueue.GetTail()) != nullptr);
@@ -59,7 +59,7 @@
     if (message->GetPriority() < static_cast<uint8_t>(aPriority))
     {
         RemoveMessage(*message);
-        ExitNow(error = OT_ERROR_NONE);
+        ExitNow(error = kErrorNone);
     }
 
 exit:
diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp
index feaa4d6..d696a07 100644
--- a/src/core/thread/mle.cpp
+++ b/src/core/thread/mle.cpp
@@ -71,9 +71,9 @@
     , mReattachState(kReattachStop)
     , mAttachCounter(0)
     , mAnnounceDelay(kAnnounceTimeout)
-    , mAttachTimer(aInstance, Mle::HandleAttachTimer, this)
-    , mDelayedResponseTimer(aInstance, Mle::HandleDelayedResponseTimer, this)
-    , mMessageTransmissionTimer(aInstance, Mle::HandleMessageTransmissionTimer, this)
+    , mAttachTimer(aInstance, Mle::HandleAttachTimer)
+    , mDelayedResponseTimer(aInstance, Mle::HandleDelayedResponseTimer)
+    , mMessageTransmissionTimer(aInstance, Mle::HandleMessageTransmissionTimer)
     , mParentLeaderCost(0)
     , mParentRequestMode(kAttachAny)
     , mParentPriority(0)
@@ -101,7 +101,7 @@
     , mParentSearchBackoffWasCanceled(false)
     , mParentSearchRecentlyDetached(false)
     , mParentSearchBackoffCancelTime(0)
-    , mParentSearchTimer(aInstance, Mle::HandleParentSearchTimer, this)
+    , mParentSearchTimer(aInstance, Mle::HandleParentSearchTimer)
 #endif
     , mAnnounceChannel(0)
     , mAlternateChannel(0)
@@ -160,9 +160,9 @@
     // to the Link- and Realm-Local All Thread Nodes multicast addresses.
 }
 
-otError Mle::Enable(void)
+Error Mle::Enable(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     UpdateLinkLocalAddress();
     SuccessOrExit(error = mSocket.Open(&Mle::HandleUdpReceive, this));
@@ -181,9 +181,9 @@
     ScheduleMessageTransmissionTimer();
 }
 
-otError Mle::Disable(void)
+Error Mle::Disable(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     Stop(false);
     SuccessOrExit(error = mSocket.Close());
@@ -193,13 +193,13 @@
     return error;
 }
 
-otError Mle::Start(bool aAnnounceAttach)
+Error Mle::Start(bool aAnnounceAttach)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     // cannot bring up the interface if IEEE 802.15.4 promiscuous mode is enabled
-    VerifyOrExit(!Get<Radio>().GetPromiscuous(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(Get<ThreadNetif>().IsUp(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(!Get<Radio>().GetPromiscuous(), error = kErrorInvalidState);
+    VerifyOrExit(Get<ThreadNetif>().IsUp(), error = kErrorInvalidState);
 
     if (Get<Mac::Mac>().GetPanId() == Mac::kPanIdBroadcast)
     {
@@ -227,7 +227,7 @@
 #if OPENTHREAD_FTD
     else if (IsActiveRouter(GetRloc16()))
     {
-        if (Get<MleRouter>().BecomeRouter(ThreadStatusTlv::kTooFewRouters) != OT_ERROR_NONE)
+        if (Get<MleRouter>().BecomeRouter(ThreadStatusTlv::kTooFewRouters) != kErrorNone)
         {
             IgnoreError(BecomeChild(kAttachAny));
         }
@@ -313,9 +313,9 @@
     return;
 }
 
-otError Mle::Restore(void)
+Error Mle::Restore(void)
 {
-    otError               error = OT_ERROR_NONE;
+    Error                 error = kErrorNone;
     Settings::NetworkInfo networkInfo;
     Settings::ParentInfo  parentInfo;
 
@@ -361,7 +361,7 @@
     {
         error = Get<Settings>().ReadParentInfo(parentInfo);
 
-        if (error != OT_ERROR_NONE)
+        if (error != kErrorNone)
         {
             // If the restored RLOC16 corresponds to an end-device, it
             // is expected that the `ParentInfo` settings to be valid
@@ -402,9 +402,9 @@
     return error;
 }
 
-otError Mle::Store(void)
+Error Mle::Store(void)
 {
-    otError               error = OT_ERROR_NONE;
+    Error                 error = kErrorNone;
     Settings::NetworkInfo networkInfo;
 
     networkInfo.Init();
@@ -464,11 +464,11 @@
     return error;
 }
 
-otError Mle::BecomeDetached(void)
+Error Mle::BecomeDetached(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(!IsDisabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(!IsDisabled(), error = kErrorInvalidState);
 
     // In case role is already detached and attach state is `kAttachStateStart`
     // (i.e., waiting to start an attach attempt), there is no need to make any
@@ -495,16 +495,21 @@
     return error;
 }
 
-otError Mle::BecomeChild(AttachMode aMode)
+Error Mle::BecomeChild(AttachMode aMode)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(!IsDisabled(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(!IsAttaching(), error = OT_ERROR_BUSY);
+    VerifyOrExit(!IsDisabled(), error = kErrorInvalidState);
+    VerifyOrExit(!IsAttaching(), error = kErrorBusy);
+
+    if (!IsDetached())
+    {
+        mAttachCounter = 0;
+    }
 
     if (mReattachState == kReattachStart)
     {
-        if (Get<MeshCoP::ActiveDataset>().Restore() == OT_ERROR_NONE)
+        if (Get<MeshCoP::ActiveDataset>().Restore() == kErrorNone)
         {
             mReattachState = kReattachActive;
         }
@@ -523,7 +528,7 @@
 #if OPENTHREAD_FTD
         if (IsFullThreadDevice())
         {
-            Get<MleRouter>().StopAdvertiseTimer();
+            Get<MleRouter>().StopAdvertiseTrickleTimer();
         }
 #endif
     }
@@ -652,8 +657,7 @@
     SetRloc16(aRloc16);
     SetRole(kRoleChild);
     SetAttachState(kAttachStateIdle);
-    mAttachTimer.Stop();
-    mAttachCounter       = 0;
+    mAttachTimer.Start(kAttachBackoffDelayToResetCounter);
     mReattachState       = kReattachStop;
     mChildUpdateAttempts = 0;
     mDataRequestAttempts = 0;
@@ -731,12 +735,12 @@
     return;
 }
 
-otError Mle::SetDeviceMode(DeviceMode aDeviceMode)
+Error Mle::SetDeviceMode(DeviceMode aDeviceMode)
 {
-    otError    error   = OT_ERROR_NONE;
+    Error      error   = kErrorNone;
     DeviceMode oldMode = mDeviceMode;
 
-    VerifyOrExit(aDeviceMode.IsValid(), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aDeviceMode.IsValid(), error = kErrorInvalidArgs);
     VerifyOrExit(mDeviceMode != aDeviceMode);
     mDeviceMode = aDeviceMode;
 
@@ -930,11 +934,11 @@
     mLeaderData.SetLeaderRouterId(aLeaderRouterId);
 }
 
-otError Mle::GetLeaderAddress(Ip6::Address &aAddress) const
+Error Mle::GetLeaderAddress(Ip6::Address &aAddress) const
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(GetRloc16() != Mac::kShortAddrInvalid, error = OT_ERROR_DETACHED);
+    VerifyOrExit(GetRloc16() != Mac::kShortAddrInvalid, error = kErrorDetached);
 
     aAddress.SetToRoutingLocator(GetMeshLocalPrefix(), Rloc16FromRouterId(mLeaderData.GetLeaderRouterId()));
 
@@ -942,11 +946,11 @@
     return error;
 }
 
-otError Mle::GetLocatorAddress(Ip6::Address &aAddress, uint16_t aLocator) const
+Error Mle::GetLocatorAddress(Ip6::Address &aAddress, uint16_t aLocator) const
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(GetRloc16() != Mac::kShortAddrInvalid, error = OT_ERROR_DETACHED);
+    VerifyOrExit(GetRloc16() != Mac::kShortAddrInvalid, error = kErrorDetached);
 
     memcpy(&aAddress, &mMeshLocal16.GetAddress(), 14);
     aAddress.GetIid().SetLocator(aLocator);
@@ -955,11 +959,11 @@
     return error;
 }
 
-otError Mle::GetServiceAloc(uint8_t aServiceId, Ip6::Address &aAddress) const
+Error Mle::GetServiceAloc(uint8_t aServiceId, Ip6::Address &aAddress) const
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(GetRloc16() != Mac::kShortAddrInvalid, error = OT_ERROR_DETACHED);
+    VerifyOrExit(GetRloc16() != Mac::kShortAddrInvalid, error = kErrorDetached);
     aAddress.SetToAnycastLocator(GetMeshLocalPrefix(), ServiceAlocFromId(aServiceId));
 
 exit:
@@ -988,10 +992,10 @@
     return message;
 }
 
-otError Mle::AppendHeader(Message &aMessage, Command aCommand)
+Error Mle::AppendHeader(Message &aMessage, Command aCommand)
 {
-    otError error = OT_ERROR_NONE;
-    Header  header;
+    Error  error = kErrorNone;
+    Header header;
 
     header.Init();
 
@@ -1012,49 +1016,49 @@
     return error;
 }
 
-otError Mle::AppendSourceAddress(Message &aMessage) const
+Error Mle::AppendSourceAddress(Message &aMessage) const
 {
     return Tlv::Append<SourceAddressTlv>(aMessage, GetRloc16());
 }
 
-otError Mle::AppendStatus(Message &aMessage, StatusTlv::Status aStatus)
+Error Mle::AppendStatus(Message &aMessage, StatusTlv::Status aStatus)
 {
     return Tlv::Append<StatusTlv>(aMessage, aStatus);
 }
 
-otError Mle::AppendMode(Message &aMessage, DeviceMode aMode)
+Error Mle::AppendMode(Message &aMessage, DeviceMode aMode)
 {
     return Tlv::Append<ModeTlv>(aMessage, aMode.Get());
 }
 
-otError Mle::AppendTimeout(Message &aMessage, uint32_t aTimeout)
+Error Mle::AppendTimeout(Message &aMessage, uint32_t aTimeout)
 {
     return Tlv::Append<TimeoutTlv>(aMessage, aTimeout);
 }
 
-otError Mle::AppendChallenge(Message &aMessage, const Challenge &aChallenge)
+Error Mle::AppendChallenge(Message &aMessage, const Challenge &aChallenge)
 {
     return Tlv::Append<ChallengeTlv>(aMessage, aChallenge.mBuffer, aChallenge.mLength);
 }
 
-otError Mle::AppendChallenge(Message &aMessage, const uint8_t *aChallenge, uint8_t aChallengeLength)
+Error Mle::AppendChallenge(Message &aMessage, const uint8_t *aChallenge, uint8_t aChallengeLength)
 {
     return Tlv::Append<ChallengeTlv>(aMessage, aChallenge, aChallengeLength);
 }
 
-otError Mle::AppendResponse(Message &aMessage, const Challenge &aResponse)
+Error Mle::AppendResponse(Message &aMessage, const Challenge &aResponse)
 {
     return Tlv::Append<ResponseTlv>(aMessage, aResponse.mBuffer, aResponse.mLength);
 }
 
-otError Mle::ReadChallengeOrResponse(const Message &aMessage, uint8_t aTlvType, Challenge &aBuffer)
+Error Mle::ReadChallengeOrResponse(const Message &aMessage, uint8_t aTlvType, Challenge &aBuffer)
 {
-    otError  error;
+    Error    error;
     uint16_t offset;
     uint16_t length;
 
     SuccessOrExit(error = Tlv::FindTlvValueOffset(aMessage, aTlvType, offset, length));
-    VerifyOrExit(length >= kMinChallengeSize, error = OT_ERROR_PARSE);
+    VerifyOrExit(length >= kMinChallengeSize, error = kErrorParse);
 
     if (length > kMaxChallengeSize)
     {
@@ -1068,17 +1072,17 @@
     return error;
 }
 
-otError Mle::ReadChallenge(const Message &aMessage, Challenge &aChallenge)
+Error Mle::ReadChallenge(const Message &aMessage, Challenge &aChallenge)
 {
     return ReadChallengeOrResponse(aMessage, Tlv::kChallenge, aChallenge);
 }
 
-otError Mle::ReadResponse(const Message &aMessage, Challenge &aResponse)
+Error Mle::ReadResponse(const Message &aMessage, Challenge &aResponse)
 {
     return ReadChallengeOrResponse(aMessage, Tlv::kResponse, aResponse);
 }
 
-otError Mle::AppendLinkFrameCounter(Message &aMessage)
+Error Mle::AppendLinkFrameCounter(Message &aMessage)
 {
     uint32_t counter;
 
@@ -1096,26 +1100,26 @@
     return Tlv::Append<LinkFrameCounterTlv>(aMessage, counter);
 }
 
-otError Mle::AppendMleFrameCounter(Message &aMessage)
+Error Mle::AppendMleFrameCounter(Message &aMessage)
 {
     return Tlv::Append<MleFrameCounterTlv>(aMessage, Get<KeyManager>().GetMleFrameCounter());
 }
 
-otError Mle::ReadFrameCounters(const Message &aMessage, uint32_t &aLinkFrameCounter, uint32_t &aMleFrameCounter) const
+Error Mle::ReadFrameCounters(const Message &aMessage, uint32_t &aLinkFrameCounter, uint32_t &aMleFrameCounter) const
 {
-    otError error;
+    Error error;
 
     SuccessOrExit(error = Tlv::Find<LinkFrameCounterTlv>(aMessage, aLinkFrameCounter));
 
     switch (Tlv::Find<MleFrameCounterTlv>(aMessage, aMleFrameCounter))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         break;
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         aMleFrameCounter = aLinkFrameCounter;
         break;
     default:
-        error = OT_ERROR_PARSE;
+        error = kErrorParse;
         break;
     }
 
@@ -1123,12 +1127,12 @@
     return error;
 }
 
-otError Mle::AppendAddress16(Message &aMessage, uint16_t aRloc16)
+Error Mle::AppendAddress16(Message &aMessage, uint16_t aRloc16)
 {
     return Tlv::Append<Address16Tlv>(aMessage, aRloc16);
 }
 
-otError Mle::AppendLeaderData(Message &aMessage)
+Error Mle::AppendLeaderData(Message &aMessage)
 {
     LeaderDataTlv leaderDataTlv;
 
@@ -1141,26 +1145,26 @@
     return leaderDataTlv.AppendTo(aMessage);
 }
 
-otError Mle::ReadLeaderData(const Message &aMessage, LeaderData &aLeaderData)
+Error Mle::ReadLeaderData(const Message &aMessage, LeaderData &aLeaderData)
 {
-    otError       error;
+    Error         error;
     LeaderDataTlv leaderDataTlv;
 
     SuccessOrExit(error = Tlv::FindTlv(aMessage, leaderDataTlv));
-    VerifyOrExit(leaderDataTlv.IsValid(), error = OT_ERROR_PARSE);
+    VerifyOrExit(leaderDataTlv.IsValid(), error = kErrorParse);
     leaderDataTlv.Get(aLeaderData);
 
 exit:
     return error;
 }
 
-otError Mle::AppendNetworkData(Message &aMessage, bool aStableOnly)
+Error Mle::AppendNetworkData(Message &aMessage, bool aStableOnly)
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     uint8_t networkData[NetworkData::NetworkData::kMaxSize];
     uint8_t length;
 
-    VerifyOrExit(!mRetrieveNewNetworkData, error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(!mRetrieveNewNetworkData, error = kErrorInvalidState);
 
     length = sizeof(networkData);
     IgnoreError(Get<NetworkData::Leader>().GetNetworkData(aStableOnly, networkData, length));
@@ -1171,14 +1175,14 @@
     return error;
 }
 
-otError Mle::AppendTlvRequest(Message &aMessage, const uint8_t *aTlvs, uint8_t aTlvsLength)
+Error Mle::AppendTlvRequest(Message &aMessage, const uint8_t *aTlvs, uint8_t aTlvsLength)
 {
     return Tlv::Append<TlvRequestTlv>(aMessage, aTlvs, aTlvsLength);
 }
 
-otError Mle::FindTlvRequest(const Message &aMessage, RequestedTlvs &aRequestedTlvs)
+Error Mle::FindTlvRequest(const Message &aMessage, RequestedTlvs &aRequestedTlvs)
 {
-    otError  error;
+    Error    error;
     uint16_t offset;
     uint16_t length;
 
@@ -1196,17 +1200,17 @@
     return error;
 }
 
-otError Mle::AppendScanMask(Message &aMessage, uint8_t aScanMask)
+Error Mle::AppendScanMask(Message &aMessage, uint8_t aScanMask)
 {
     return Tlv::Append<ScanMaskTlv>(aMessage, aScanMask);
 }
 
-otError Mle::AppendLinkMargin(Message &aMessage, uint8_t aLinkMargin)
+Error Mle::AppendLinkMargin(Message &aMessage, uint8_t aLinkMargin)
 {
     return Tlv::Append<LinkMarginTlv>(aMessage, aLinkMargin);
 }
 
-otError Mle::AppendVersion(Message &aMessage)
+Error Mle::AppendVersion(Message &aMessage)
 {
     return Tlv::Append<VersionTlv>(aMessage, kThreadVersion);
 }
@@ -1239,9 +1243,9 @@
     return retval;
 }
 
-otError Mle::AppendAddressRegistration(Message &aMessage, AddressRegistrationMode aMode)
+Error Mle::AppendAddressRegistration(Message &aMessage, AddressRegistrationMode aMode)
 {
-    otError                  error = OT_ERROR_NONE;
+    Error                    error = kErrorNone;
     Tlv                      tlv;
     AddressRegistrationEntry entry;
     Lowpan::Context          context;
@@ -1273,7 +1277,7 @@
     {
         error = Get<NetworkData::Leader>().GetContext(domainUnicastAddress, context);
 
-        OT_ASSERT(error == OT_ERROR_NONE);
+        OT_ASSERT(error == kErrorNone);
 
         // Prioritize DUA, compressed entry
         entry.SetContextId(context.mContextId);
@@ -1300,7 +1304,7 @@
         }
 #endif
 
-        if (Get<NetworkData::Leader>().GetContext(addr->GetAddress(), context) == OT_ERROR_NONE)
+        if (Get<NetworkData::Leader>().GetContext(addr->GetAddress(), context) == kErrorNone)
         {
             // compressed entry
             entry.SetContextId(context.mContextId);
@@ -1355,7 +1359,7 @@
 
 exit:
 
-    if (error == OT_ERROR_NONE && length > 0)
+    if (error == kErrorNone && length > 0)
     {
         tlv.SetLength(length);
         aMessage.Write(startOffset, tlv);
@@ -1365,13 +1369,13 @@
 }
 
 #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
-otError Mle::AppendTimeRequest(Message &aMessage)
+Error Mle::AppendTimeRequest(Message &aMessage)
 {
     // `TimeRequestTlv` has no value.
     return Tlv::Append<TimeRequestTlv>(aMessage, nullptr, 0);
 }
 
-otError Mle::AppendTimeParameter(Message &aMessage)
+Error Mle::AppendTimeParameter(Message &aMessage)
 {
     TimeParameterTlv tlv;
 
@@ -1382,20 +1386,20 @@
     return tlv.AppendTo(aMessage);
 }
 
-otError Mle::AppendXtalAccuracy(Message &aMessage)
+Error Mle::AppendXtalAccuracy(Message &aMessage)
 {
     return Tlv::Append<XtalAccuracyTlv>(aMessage, otPlatTimeGetXtalAccuracy());
 }
 #endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
 
-otError Mle::AppendActiveTimestamp(Message &aMessage)
+Error Mle::AppendActiveTimestamp(Message &aMessage)
 {
-    otError                   error;
+    Error                     error;
     ActiveTimestampTlv        timestampTlv;
     const MeshCoP::Timestamp *timestamp;
 
     timestamp = Get<MeshCoP::ActiveDataset>().GetTimestamp();
-    VerifyOrExit(timestamp, error = OT_ERROR_NONE);
+    VerifyOrExit(timestamp, error = kErrorNone);
 
     timestampTlv.Init();
     *static_cast<MeshCoP::Timestamp *>(&timestampTlv) = *timestamp;
@@ -1405,14 +1409,14 @@
     return error;
 }
 
-otError Mle::AppendPendingTimestamp(Message &aMessage)
+Error Mle::AppendPendingTimestamp(Message &aMessage)
 {
-    otError                   error;
+    Error                     error;
     PendingTimestampTlv       timestampTlv;
     const MeshCoP::Timestamp *timestamp;
 
     timestamp = Get<MeshCoP::PendingDataset>().GetTimestamp();
-    VerifyOrExit(timestamp && timestamp->GetSeconds() != 0, error = OT_ERROR_NONE);
+    VerifyOrExit(timestamp && timestamp->GetSeconds() != 0, error = kErrorNone);
 
     timestampTlv.Init();
     *static_cast<MeshCoP::Timestamp *>(&timestampTlv) = *timestamp;
@@ -1423,9 +1427,9 @@
 }
 
 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
-otError Mle::AppendCslChannel(Message &aMessage)
+Error Mle::AppendCslChannel(Message &aMessage)
 {
-    otError       error = OT_ERROR_NONE;
+    Error         error = kErrorNone;
     CslChannelTlv cslChannel;
 
     // In current implementation, it's allowed to set CSL Channel unspecified. As `0` is not valid for Channel value
@@ -1444,7 +1448,7 @@
     return error;
 }
 
-otError Mle::AppendCslTimeout(Message &aMessage)
+Error Mle::AppendCslTimeout(Message &aMessage)
 {
     OT_ASSERT(Get<Mac::Mac>().IsCslEnabled());
     return Tlv::Append<CslTimeoutTlv>(aMessage, Get<Mac::Mac>().GetCslTimeout() == 0 ? mTimeout
@@ -1586,7 +1590,7 @@
     }
 
     // Now add any missing service alocs which should be there, if there is enough space in mServiceAlocs
-    while (Get<NetworkData::Leader>().GetNextServiceId(serviceIterator, rloc, serviceId) == OT_ERROR_NONE)
+    while (Get<NetworkData::Leader>().GetNextServiceId(serviceIterator, rloc, serviceId) == kErrorNone)
     {
         for (i = 0; i < serviceAlocsLength; i++)
         {
@@ -1622,7 +1626,7 @@
 
 void Mle::HandleAttachTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<Mle>().HandleAttachTimer();
+    aTimer.Get<Mle>().HandleAttachTimer();
 }
 
 void Mle::HandleAttachTimer(void)
@@ -1656,7 +1660,7 @@
         if ((linkQuality == 3 || mAttachState != kAttachStateParentRequestRouter) &&
             mParentCandidate.IsStateParentResponse() &&
             (!IsChild() || mReceivedResponseFromParent || mParentRequestMode == kAttachBetter) &&
-            SendChildIdRequest() == OT_ERROR_NONE)
+            SendChildIdRequest() == kErrorNone)
         {
             SetAttachState(kAttachStateChildIdRequest);
             delay = kParentRequestReedTimeout;
@@ -1667,8 +1671,8 @@
     switch (mAttachState)
     {
     case kAttachStateIdle:
-        OT_ASSERT(false);
-        OT_UNREACHABLE_CODE(break);
+        mAttachCounter = 0;
+        break;
 
     case kAttachStateProcessAnnounce:
         ProcessAnnounce();
@@ -1726,19 +1730,19 @@
             break;
         }
 
-        // fall through
+        OT_FALL_THROUGH;
 
     case kAttachStateAnnounce:
         if (shouldAnnounce)
         {
-            if (SendOrphanAnnounce() == OT_ERROR_NONE)
+            if (SendOrphanAnnounce() == kErrorNone)
             {
                 delay = mAnnounceDelay;
                 break;
             }
         }
 
-        // fall through
+        OT_FALL_THROUGH;
 
     case kAttachStateChildIdRequest:
         SetAttachState(kAttachStateIdle);
@@ -1763,7 +1767,7 @@
     VerifyOrExit(!IsChild() && (mReattachState == kReattachStop) &&
                  (Get<MeshCoP::ActiveDataset>().IsPartiallyComplete() || !IsFullThreadDevice()));
 
-    if (Get<MeshCoP::ActiveDataset>().GetChannelMask(channelMask) != OT_ERROR_NONE)
+    if (Get<MeshCoP::ActiveDataset>().GetChannelMask(channelMask) != kErrorNone)
     {
         channelMask = Get<Mac::Mac>().GetSupportedChannelMask();
     }
@@ -1787,7 +1791,7 @@
 
     if (mReattachState == kReattachActive)
     {
-        if (Get<MeshCoP::PendingDataset>().Restore() == OT_ERROR_NONE)
+        if (Get<MeshCoP::PendingDataset>().Restore() == kErrorNone)
         {
             IgnoreError(Get<MeshCoP::PendingDataset>().ApplyConfiguration());
             mReattachState = kReattachPending;
@@ -1820,7 +1824,7 @@
                 IgnoreError(BecomeDetached());
             }
 #if OPENTHREAD_FTD
-            else if (IsFullThreadDevice() && Get<MleRouter>().BecomeLeader() == OT_ERROR_NONE)
+            else if (IsFullThreadDevice() && Get<MleRouter>().BecomeLeader() == kErrorNone)
             {
                 // do nothing
             }
@@ -1858,7 +1862,7 @@
 
 void Mle::HandleDelayedResponseTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<Mle>().HandleDelayedResponseTimer();
+    aTimer.Get<Mle>().HandleDelayedResponseTimer();
 }
 
 void Mle::HandleDelayedResponseTimer(void)
@@ -1887,7 +1891,7 @@
             mDelayedResponses.Dequeue(*message);
             metadata.RemoveFrom(*message);
 
-            if (SendMessage(*message, metadata.mDestination) == OT_ERROR_NONE)
+            if (SendMessage(*message, metadata.mDestination) == kErrorNone)
             {
                 Log(kMessageSend, kTypeGenericDelayed, metadata.mDestination);
 
@@ -1937,9 +1941,9 @@
     }
 }
 
-otError Mle::SendParentRequest(ParentRequestType aType)
+Error Mle::SendParentRequest(ParentRequestType aType)
 {
-    otError      error = OT_ERROR_NONE;
+    Error        error = kErrorNone;
     Message *    message;
     uint8_t      scanMask = 0;
     Ip6::Address destination;
@@ -1957,7 +1961,7 @@
         break;
     }
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = AppendHeader(*message, kCommandParentRequest));
     SuccessOrExit(error = AppendMode(*message, mDeviceMode));
     SuccessOrExit(error = AppendChallenge(*message, mParentRequestChallenge));
@@ -1995,9 +1999,9 @@
     }
 }
 
-otError Mle::SendChildIdRequest(void)
+Error Mle::SendChildIdRequest(void)
 {
-    otError      error   = OT_ERROR_NONE;
+    Error        error   = kErrorNone;
     uint8_t      tlvs[]  = {Tlv::kAddress16, Tlv::kNetworkData, Tlv::kRoute};
     uint8_t      tlvsLen = sizeof(tlvs);
     Message *    message = nullptr;
@@ -2008,7 +2012,7 @@
         if (IsChild())
         {
             otLogInfoMle("Already attached to candidate parent");
-            ExitNow(error = OT_ERROR_ALREADY);
+            ExitNow(error = kErrorAlready);
         }
         else
         {
@@ -2021,7 +2025,7 @@
         }
     }
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     message->SetSubType(Message::kSubTypeMleChildIdRequest);
     SuccessOrExit(error = AppendHeader(*message, kCommandChildIdRequest));
     SuccessOrExit(error = AppendResponse(*message, mParentCandidateChallenge));
@@ -2063,20 +2067,20 @@
     return error;
 }
 
-otError Mle::SendDataRequest(const Ip6::Address &aDestination,
-                             const uint8_t *     aTlvs,
-                             uint8_t             aTlvsLength,
-                             uint16_t            aDelay,
-                             const uint8_t *     aExtraTlvs,
-                             uint8_t             aExtraTlvsLength)
+Error Mle::SendDataRequest(const Ip6::Address &aDestination,
+                           const uint8_t *     aTlvs,
+                           uint8_t             aTlvsLength,
+                           uint16_t            aDelay,
+                           const uint8_t *     aExtraTlvs,
+                           uint8_t             aExtraTlvsLength)
 {
     OT_UNUSED_VARIABLE(aExtraTlvs);
     OT_UNUSED_VARIABLE(aExtraTlvsLength);
 
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     Message *message;
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = AppendHeader(*message, kCommandDataRequest));
     SuccessOrExit(error = AppendTlvRequest(*message, aTlvs, aTlvsLength));
     SuccessOrExit(error = AppendActiveTimestamp(*message));
@@ -2174,7 +2178,7 @@
 
 void Mle::HandleMessageTransmissionTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<Mle>().HandleMessageTransmissionTimer();
+    aTimer.Get<Mle>().HandleMessageTransmissionTimer();
 }
 
 void Mle::HandleMessageTransmissionTimer(void)
@@ -2198,7 +2202,7 @@
 
             destination.SetToLinkLocalAddress(mParent.GetExtAddress());
 
-            if (SendDataRequest(destination, tlvs, sizeof(tlvs), 0) == OT_ERROR_NONE)
+            if (SendDataRequest(destination, tlvs, sizeof(tlvs), 0) == kErrorNone)
             {
                 mDataRequestAttempts++;
             }
@@ -2229,7 +2233,7 @@
 
     VerifyOrExit(mChildUpdateAttempts < kMaxChildKeepAliveAttempts, IgnoreError(BecomeDetached()));
 
-    if (SendChildUpdateRequest() == OT_ERROR_NONE)
+    if (SendChildUpdateRequest() == kErrorNone)
     {
         mChildUpdateAttempts++;
     }
@@ -2238,9 +2242,9 @@
     return;
 }
 
-otError Mle::SendChildUpdateRequest(void)
+Error Mle::SendChildUpdateRequest(void)
 {
-    otError                 error = OT_ERROR_NONE;
+    Error                   error = kErrorNone;
     Ip6::Address            destination;
     Message *               message = nullptr;
     AddressRegistrationMode mode    = kAppendAllAddresses;
@@ -2255,7 +2259,7 @@
     mChildUpdateRequestState = kChildUpdateRequestActive;
     ScheduleMessageTransmissionTimer();
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     message->SetSubType(Message::kSubTypeMleChildUpdateRequest);
     SuccessOrExit(error = AppendHeader(*message, kCommandChildUpdateRequest));
     SuccessOrExit(error = AppendMode(*message, mDeviceMode));
@@ -2317,14 +2321,14 @@
     return error;
 }
 
-otError Mle::SendChildUpdateResponse(const uint8_t *aTlvs, uint8_t aNumTlvs, const Challenge &aChallenge)
+Error Mle::SendChildUpdateResponse(const uint8_t *aTlvs, uint8_t aNumTlvs, const Challenge &aChallenge)
 {
-    otError      error = OT_ERROR_NONE;
+    Error        error = kErrorNone;
     Ip6::Address destination;
     Message *    message;
     bool         checkAddress = false;
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = AppendHeader(*message, kCommandChildUpdateResponse));
     SuccessOrExit(error = AppendSourceAddress(*message));
     SuccessOrExit(error = AppendLeaderData(*message));
@@ -2395,13 +2399,13 @@
 
 void Mle::SendAnnounce(uint8_t aChannel, bool aOrphanAnnounce, const Ip6::Address &aDestination)
 {
-    otError            error = OT_ERROR_NONE;
+    Error              error = kErrorNone;
     ChannelTlv         channel;
     ActiveTimestampTlv activeTimestamp;
     Message *          message = nullptr;
 
-    VerifyOrExit(Get<Mac::Mac>().GetSupportedChannelMask().ContainsChannel(aChannel), error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(Get<Mac::Mac>().GetSupportedChannelMask().ContainsChannel(aChannel), error = kErrorInvalidArgs);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     message->SetLinkSecurityEnabled(true);
     message->SetSubType(Message::kSubTypeMleAnnounce);
     message->SetChannel(aChannel);
@@ -2435,12 +2439,12 @@
     FreeMessageOnError(message, error);
 }
 
-otError Mle::SendOrphanAnnounce(void)
+Error Mle::SendOrphanAnnounce(void)
 {
-    otError          error;
+    Error            error;
     Mac::ChannelMask channelMask;
 
-    if (Get<MeshCoP::ActiveDataset>().GetChannelMask(channelMask) != OT_ERROR_NONE)
+    if (Get<MeshCoP::ActiveDataset>().GetChannelMask(channelMask) != kErrorNone)
     {
         channelMask = Get<Mac::Mac>().GetSupportedChannelMask();
     }
@@ -2454,14 +2458,14 @@
 }
 
 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
-otError Mle::SendLinkMetricsManagementResponse(const Ip6::Address &aDestination, LinkMetrics::LinkMetricsStatus aStatus)
+Error Mle::SendLinkMetricsManagementResponse(const Ip6::Address &aDestination, LinkMetrics::LinkMetricsStatus aStatus)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     Message *message;
     Tlv      tlv;
     ot::Tlv  statusSubTlv;
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = AppendHeader(*message, kCommandLinkMetricsManagementResponse));
 
     tlv.SetType(Tlv::kLinkMetricsManagement);
@@ -2478,13 +2482,13 @@
     return error;
 }
 
-otError Mle::SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t *aBuf, uint8_t aLength)
+Error Mle::SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t *aBuf, uint8_t aLength)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     Message *message;
     Tlv      tlv;
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = AppendHeader(*message, kCommandLinkProbe));
 
     tlv.SetType(Tlv::kLinkProbe);
@@ -2500,9 +2504,9 @@
 }
 #endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
 
-otError Mle::SendMessage(Message &aMessage, const Ip6::Address &aDestination)
+Error Mle::SendMessage(Message &aMessage, const Ip6::Address &aDestination)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     Header           header;
     uint32_t         keySequence;
     uint8_t          nonce[Crypto::AesCcm::kNonceSize];
@@ -2561,9 +2565,9 @@
     return error;
 }
 
-otError Mle::AddDelayedResponse(Message &aMessage, const Ip6::Address &aDestination, uint16_t aDelay)
+Error Mle::AddDelayedResponse(Message &aMessage, const Ip6::Address &aDestination, uint16_t aDelay)
 {
-    otError                 error = OT_ERROR_NONE;
+    Error                   error = kErrorNone;
     DelayedResponseMetadata metadata;
 
     metadata.mSendTime    = TimerMilli::GetNow() + aDelay;
@@ -2586,7 +2590,7 @@
 
 void Mle::HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError         error = OT_ERROR_NONE;
+    Error           error = kErrorNone;
     Header          header;
     uint32_t        keySequence;
     const Key *     mleKey;
@@ -2605,10 +2609,10 @@
     otLogDebgMle("Receive UDP message");
 
     VerifyOrExit(aMessageInfo.GetLinkInfo() != nullptr);
-    VerifyOrExit(aMessageInfo.GetHopLimit() == kMleHopLimit, error = OT_ERROR_PARSE);
+    VerifyOrExit(aMessageInfo.GetHopLimit() == kMleHopLimit, error = kErrorParse);
 
     length = aMessage.ReadBytes(aMessage.GetOffset(), &header, sizeof(header));
-    VerifyOrExit(header.IsValid() && header.GetLength() <= length, error = OT_ERROR_PARSE);
+    VerifyOrExit(header.IsValid() && header.GetLength() <= length, error = kErrorParse);
 
     if (header.GetSecuritySuite() == Header::kNoSecurity)
     {
@@ -2633,8 +2637,8 @@
         ExitNow();
     }
 
-    VerifyOrExit(!IsDisabled(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(header.GetSecuritySuite() == Header::k154Security, error = OT_ERROR_PARSE);
+    VerifyOrExit(!IsDisabled(), error = kErrorInvalidState);
+    VerifyOrExit(header.GetSecuritySuite() == Header::k154Security, error = kErrorParse);
 
     keySequence = header.GetKeyId();
 
@@ -2648,7 +2652,7 @@
     }
 
     VerifyOrExit(aMessage.GetOffset() + header.GetLength() + sizeof(messageTag) <= aMessage.GetLength(),
-                 error = OT_ERROR_PARSE);
+                 error = kErrorParse);
     aMessage.MoveOffset(header.GetLength() - 1);
 
     IgnoreError(aMessage.Read(aMessage.GetLength() - sizeof(messageTag), messageTag));
@@ -2681,7 +2685,7 @@
 
     aesCcm.Finalize(tag);
 #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
-    VerifyOrExit(memcmp(messageTag, tag, sizeof(tag)) == 0, error = OT_ERROR_SECURITY);
+    VerifyOrExit(memcmp(messageTag, tag, sizeof(tag)) == 0, error = kErrorSecurity);
 #endif
 
     if (keySequence > Get<KeyManager>().GetCurrentKeySequence())
@@ -2723,11 +2727,11 @@
                 ExitNow();
             }
 #endif
-            VerifyOrExit(frameCounter >= neighbor->GetMleFrameCounter(), error = OT_ERROR_DUPLICATED);
+            VerifyOrExit(frameCounter >= neighbor->GetMleFrameCounter(), error = kErrorDuplicated);
         }
         else
         {
-            VerifyOrExit(keySequence > neighbor->GetKeySequence(), error = OT_ERROR_DUPLICATED);
+            VerifyOrExit(keySequence > neighbor->GetKeySequence(), error = kErrorDuplicated);
             neighbor->SetKeySequence(keySequence);
             neighbor->GetLinkFrameCounters().Reset();
             neighbor->SetLinkAckFrameCounter(0);
@@ -2841,7 +2845,7 @@
 #endif
 
     default:
-        ExitNow(error = OT_ERROR_DROP);
+        ExitNow(error = kErrorDrop);
     }
 
 #if OPENTHREAD_CONFIG_MULTI_RADIO
@@ -2871,7 +2875,7 @@
 
 void Mle::HandleAdvertisement(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, Neighbor *aNeighbor)
 {
-    otError    error = OT_ERROR_NONE;
+    Error      error = kErrorNone;
     uint16_t   sourceAddress;
     LeaderData leaderData;
     uint8_t    tlvs[] = {Tlv::kNetworkData};
@@ -2922,7 +2926,7 @@
             {
                 RouteTlv route;
 
-                if ((Tlv::FindTlv(aMessage, route) == OT_ERROR_NONE) && route.IsValid())
+                if ((Tlv::FindTlv(aMessage, route) == kErrorNone) && route.IsValid())
                 {
                     // Overwrite Route Data
                     IgnoreError(Get<MleRouter>().ProcessRouteTlv(route));
@@ -2954,7 +2958,7 @@
 
 void Mle::HandleDataResponse(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, const Neighbor *aNeighbor)
 {
-    otError error;
+    Error error;
 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
     uint16_t metricsReportValueOffset;
     uint16_t length;
@@ -2962,10 +2966,10 @@
 
     Log(kMessageReceive, kTypeDataResponse, aMessageInfo.GetPeerAddr());
 
-    VerifyOrExit(aNeighbor && aNeighbor->IsStateValid(), error = OT_ERROR_SECURITY);
+    VerifyOrExit(aNeighbor && aNeighbor->IsStateValid(), error = kErrorSecurity);
 
 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
-    if (Tlv::FindTlvValueOffset(aMessage, Tlv::kLinkMetricsReport, metricsReportValueOffset, length) == OT_ERROR_NONE)
+    if (Tlv::FindTlvValueOffset(aMessage, Tlv::kLinkMetricsReport, metricsReportValueOffset, length) == kErrorNone)
     {
         Get<LinkMetrics>().HandleLinkMetricsReport(aMessage, metricsReportValueOffset, length,
                                                    aMessageInfo.GetPeerAddr());
@@ -3004,9 +3008,9 @@
     return (diff > 0);
 }
 
-otError Mle::HandleLeaderData(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
+Error Mle::HandleLeaderData(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError             error = OT_ERROR_NONE;
+    Error               error = kErrorNone;
     LeaderData          leaderData;
     ActiveTimestampTlv  activeTimestamp;
     PendingTimestampTlv pendingTimestamp;
@@ -3029,7 +3033,7 @@
         }
         else
         {
-            ExitNow(error = OT_ERROR_DROP);
+            ExitNow(error = kErrorDrop);
         }
     }
     else if (!mRetrieveNewNetworkData)
@@ -3038,17 +3042,17 @@
     }
 
     // Active Timestamp
-    if (Tlv::FindTlv(aMessage, activeTimestamp) == OT_ERROR_NONE)
+    if (Tlv::FindTlv(aMessage, activeTimestamp) == kErrorNone)
     {
         const MeshCoP::Timestamp *timestamp;
 
-        VerifyOrExit(activeTimestamp.IsValid(), error = OT_ERROR_PARSE);
+        VerifyOrExit(activeTimestamp.IsValid(), error = kErrorParse);
         timestamp = Get<MeshCoP::ActiveDataset>().GetTimestamp();
 
         // if received timestamp does not match the local value and message does not contain the dataset,
         // send MLE Data Request
         if (!IsLeader() && ((timestamp == nullptr) || (timestamp->Compare(activeTimestamp) != 0)) &&
-            (Tlv::FindTlvOffset(aMessage, Tlv::kActiveDataset, activeDatasetOffset) != OT_ERROR_NONE))
+            (Tlv::FindTlvOffset(aMessage, Tlv::kActiveDataset, activeDatasetOffset) != kErrorNone))
         {
             ExitNow(dataRequest = true);
         }
@@ -3059,17 +3063,17 @@
     }
 
     // Pending Timestamp
-    if (Tlv::FindTlv(aMessage, pendingTimestamp) == OT_ERROR_NONE)
+    if (Tlv::FindTlv(aMessage, pendingTimestamp) == kErrorNone)
     {
         const MeshCoP::Timestamp *timestamp;
 
-        VerifyOrExit(pendingTimestamp.IsValid(), error = OT_ERROR_PARSE);
+        VerifyOrExit(pendingTimestamp.IsValid(), error = kErrorParse);
         timestamp = Get<MeshCoP::PendingDataset>().GetTimestamp();
 
         // if received timestamp does not match the local value and message does not contain the dataset,
         // send MLE Data Request
         if (!IsLeader() && ((timestamp == nullptr) || (timestamp->Compare(pendingTimestamp) != 0)) &&
-            (Tlv::FindTlvOffset(aMessage, Tlv::kPendingDataset, pendingDatasetOffset) != OT_ERROR_NONE))
+            (Tlv::FindTlvOffset(aMessage, Tlv::kPendingDataset, pendingDatasetOffset) != kErrorNone))
         {
             ExitNow(dataRequest = true);
         }
@@ -3079,7 +3083,7 @@
         pendingTimestamp.SetLength(0);
     }
 
-    if (Tlv::FindTlvOffset(aMessage, Tlv::kNetworkData, networkDataOffset) == OT_ERROR_NONE)
+    if (Tlv::FindTlvOffset(aMessage, Tlv::kNetworkData, networkDataOffset) == kErrorNone)
     {
         error =
             Get<NetworkData::Leader>().SetNetworkData(leaderData.GetDataVersion(), leaderData.GetStableDataVersion(),
@@ -3145,7 +3149,7 @@
 
         IgnoreError(SendDataRequest(aMessageInfo.GetPeerAddr(), tlvs, sizeof(tlvs), delay));
     }
-    else if (error == OT_ERROR_NONE)
+    else if (error == kErrorNone)
     {
         mDataRequestAttempts = 0;
         mDataRequestState    = kDataRequestNone;
@@ -3231,7 +3235,7 @@
 
 void Mle::HandleParentResponse(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, uint32_t aKeySequence)
 {
-    otError               error    = OT_ERROR_NONE;
+    Error                 error    = kErrorNone;
     const ThreadLinkInfo *linkInfo = aMessageInfo.GetThreadLinkInfo();
     Challenge             response;
     uint16_t              version;
@@ -3255,11 +3259,11 @@
 
     // Version
     SuccessOrExit(error = Tlv::Find<VersionTlv>(aMessage, version));
-    VerifyOrExit(version >= OT_THREAD_VERSION_1_1, error = OT_ERROR_PARSE);
+    VerifyOrExit(version >= OT_THREAD_VERSION_1_1, error = kErrorParse);
 
     // Response
     SuccessOrExit(error = ReadResponse(aMessage, response));
-    VerifyOrExit(response == mParentRequestChallenge, error = OT_ERROR_PARSE);
+    VerifyOrExit(response == mParentRequestChallenge, error = kErrorParse);
 
     aMessageInfo.GetPeerAddr().GetIid().ConvertToExtAddress(extAddress);
 
@@ -3285,7 +3289,7 @@
 
     // Connectivity
     SuccessOrExit(error = Tlv::FindTlv(aMessage, connectivity));
-    VerifyOrExit(connectivity.IsValid(), error = OT_ERROR_PARSE);
+    VerifyOrExit(connectivity.IsValid(), error = kErrorParse);
 
     // Share data with application, if requested.
     if (mParentResponseCb)
@@ -3367,7 +3371,7 @@
 #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
 
     // Time Parameter
-    if (Tlv::FindTlv(aMessage, timeParameter) == OT_ERROR_NONE)
+    if (Tlv::FindTlv(aMessage, timeParameter) == kErrorNone)
     {
         VerifyOrExit(timeParameter.IsValid());
 
@@ -3424,7 +3428,7 @@
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
 
-    otError             error = OT_ERROR_NONE;
+    Error               error = kErrorNone;
     LeaderData          leaderData;
     uint16_t            sourceAddress;
     uint16_t            shortAddress;
@@ -3439,7 +3443,7 @@
 
     Log(kMessageReceive, kTypeChildIdResponse, aMessageInfo.GetPeerAddr(), sourceAddress);
 
-    VerifyOrExit(aNeighbor && aNeighbor->IsStateValid(), error = OT_ERROR_SECURITY);
+    VerifyOrExit(aNeighbor && aNeighbor->IsStateValid(), error = kErrorSecurity);
 
     VerifyOrExit(mAttachState == kAttachStateChildIdRequest);
 
@@ -3454,12 +3458,12 @@
     SuccessOrExit(error);
 
     // Active Timestamp
-    if (Tlv::FindTlv(aMessage, activeTimestamp) == OT_ERROR_NONE)
+    if (Tlv::FindTlv(aMessage, activeTimestamp) == kErrorNone)
     {
-        VerifyOrExit(activeTimestamp.IsValid(), error = OT_ERROR_PARSE);
+        VerifyOrExit(activeTimestamp.IsValid(), error = kErrorParse);
 
         // Active Dataset
-        if (Tlv::FindTlvOffset(aMessage, Tlv::kActiveDataset, offset) == OT_ERROR_NONE)
+        if (Tlv::FindTlvOffset(aMessage, Tlv::kActiveDataset, offset) == kErrorNone)
         {
             IgnoreError(aMessage.Read(offset, tlv));
             IgnoreError(
@@ -3474,12 +3478,12 @@
     }
 
     // Pending Timestamp
-    if (Tlv::FindTlv(aMessage, pendingTimestamp) == OT_ERROR_NONE)
+    if (Tlv::FindTlv(aMessage, pendingTimestamp) == kErrorNone)
     {
-        VerifyOrExit(pendingTimestamp.IsValid(), error = OT_ERROR_PARSE);
+        VerifyOrExit(pendingTimestamp.IsValid(), error = kErrorParse);
 
         // Pending Dataset
-        if (Tlv::FindTlvOffset(aMessage, Tlv::kPendingDataset, offset) == OT_ERROR_NONE)
+        if (Tlv::FindTlvOffset(aMessage, Tlv::kPendingDataset, offset) == kErrorNone)
         {
             IgnoreError(aMessage.Read(offset, tlv));
             IgnoreError(
@@ -3520,7 +3524,7 @@
     {
         RouteTlv route;
 
-        if (Tlv::FindTlv(aMessage, route) == OT_ERROR_NONE)
+        if (Tlv::FindTlv(aMessage, route) == kErrorNone)
         {
             SuccessOrExit(error = Get<MleRouter>().ProcessRouteTlv(route));
         }
@@ -3546,7 +3550,7 @@
 {
     static const uint8_t kMaxResponseTlvs = 6;
 
-    otError       error = OT_ERROR_NONE;
+    Error         error = kErrorNone;
     uint16_t      sourceAddress;
     Challenge     challenge;
     RequestedTlvs requestedTlvs;
@@ -3561,15 +3565,15 @@
     // Challenge
     switch (ReadChallenge(aMessage, challenge))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         tlvs[numTlvs++] = Tlv::kResponse;
         tlvs[numTlvs++] = Tlv::kMleFrameCounter;
         tlvs[numTlvs++] = Tlv::kLinkFrameCounter;
         break;
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         break;
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
     }
 
     if (aNeighbor == &mParent)
@@ -3578,13 +3582,13 @@
 
         switch (Tlv::Find<StatusTlv>(aMessage, status))
         {
-        case OT_ERROR_NONE:
+        case kErrorNone:
             VerifyOrExit(status != StatusTlv::kError, IgnoreError(BecomeDetached()));
             break;
-        case OT_ERROR_NOT_FOUND:
+        case kErrorNotFound:
             break;
         default:
-            ExitNow(error = OT_ERROR_PARSE);
+            ExitNow(error = kErrorParse);
         }
 
         if (mParent.GetRloc16() != sourceAddress)
@@ -3605,7 +3609,7 @@
     // TLV Request
     switch (FindTlvRequest(aMessage, requestedTlvs))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         for (uint8_t i = 0; i < requestedTlvs.mNumTlvs; i++)
         {
             if (numTlvs >= sizeof(tlvs))
@@ -3617,10 +3621,10 @@
             tlvs[numTlvs++] = requestedTlvs.mTlvs[i];
         }
         break;
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         break;
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
     }
 
 #if OPENTHREAD_CONFIG_MULTI_RADIO
@@ -3640,7 +3644,7 @@
                                     const Ip6::MessageInfo &aMessageInfo,
                                     const Neighbor *        aNeighbor)
 {
-    otError   error = OT_ERROR_NONE;
+    Error     error = kErrorNone;
     uint8_t   status;
     uint8_t   mode;
     Challenge response;
@@ -3655,11 +3659,11 @@
     {
     case kRoleDetached:
         SuccessOrExit(error = ReadResponse(aMessage, response));
-        VerifyOrExit(response == mParentRequestChallenge, error = OT_ERROR_SECURITY);
+        VerifyOrExit(response == mParentRequestChallenge, error = kErrorSecurity);
         break;
 
     case kRoleChild:
-        VerifyOrExit((aNeighbor == &mParent) && mParent.IsStateValid(), error = OT_ERROR_SECURITY);
+        VerifyOrExit((aNeighbor == &mParent) && mParent.IsStateValid(), error = kErrorSecurity);
         break;
 
     default:
@@ -3668,7 +3672,7 @@
     }
 
     // Status
-    if (Tlv::Find<StatusTlv>(aMessage, status) == OT_ERROR_NONE)
+    if (Tlv::Find<StatusTlv>(aMessage, status) == kErrorNone)
     {
         IgnoreError(BecomeDetached());
         ExitNow();
@@ -3676,7 +3680,7 @@
 
     // Mode
     SuccessOrExit(error = Tlv::Find<ModeTlv>(aMessage, mode));
-    VerifyOrExit(DeviceMode(mode) == mDeviceMode, error = OT_ERROR_DROP);
+    VerifyOrExit(DeviceMode(mode) == mDeviceMode, error = kErrorDrop);
 
     switch (mRole)
     {
@@ -3692,7 +3696,7 @@
 
         mRetrieveNewNetworkData = true;
 
-        // fall through
+        OT_FALL_THROUGH;
 
     case kRoleChild:
         // Source Address
@@ -3710,13 +3714,13 @@
         // Timeout optional
         switch (Tlv::Find<TimeoutTlv>(aMessage, timeout))
         {
-        case OT_ERROR_NONE:
+        case kErrorNone:
             mTimeout = timeout;
             break;
-        case OT_ERROR_NOT_FOUND:
+        case kErrorNotFound:
             break;
         default:
-            ExitNow(error = OT_ERROR_PARSE);
+            ExitNow(error = kErrorParse);
         }
 
         if (!IsRxOnWhenIdle())
@@ -3738,7 +3742,7 @@
 
 exit:
 
-    if (error == OT_ERROR_NONE)
+    if (error == kErrorNone)
     {
         if (mChildUpdateRequestState == kChildUpdateRequestActive)
         {
@@ -3755,7 +3759,7 @@
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
 
-    otError                   error = OT_ERROR_NONE;
+    Error                     error = kErrorNone;
     ChannelTlv                channelTlv;
     ActiveTimestampTlv        timestamp;
     const MeshCoP::Timestamp *localTimestamp;
@@ -3765,12 +3769,12 @@
     Log(kMessageReceive, kTypeAnnounce, aMessageInfo.GetPeerAddr());
 
     SuccessOrExit(error = Tlv::FindTlv(aMessage, channelTlv));
-    VerifyOrExit(channelTlv.IsValid(), error = OT_ERROR_PARSE);
+    VerifyOrExit(channelTlv.IsValid(), error = kErrorParse);
 
     channel = static_cast<uint8_t>(channelTlv.GetChannel());
 
     SuccessOrExit(error = Tlv::FindTlv(aMessage, timestamp));
-    VerifyOrExit(timestamp.IsValid(), error = OT_ERROR_PARSE);
+    VerifyOrExit(timestamp.IsValid(), error = kErrorParse);
 
     SuccessOrExit(error = Tlv::Find<PanIdTlv>(aMessage, panId));
 
@@ -3795,6 +3799,7 @@
         mAlternatePanId     = panId;
         SetAttachState(kAttachStateProcessAnnounce);
         mAttachTimer.Start(kAnnounceProcessTimeout);
+        mAttachCounter = 0;
 
         otLogNoteMle("Delay processing Announce - channel %d, panid 0x%02x", channel, panId);
     }
@@ -3823,12 +3828,12 @@
                                              const Ip6::MessageInfo &aMessageInfo,
                                              Neighbor *              aNeighbor)
 {
-    otError                        error = OT_ERROR_NONE;
+    Error                          error = kErrorNone;
     LinkMetrics::LinkMetricsStatus status;
 
     Log(kMessageReceive, kTypeLinkMetricsManagementRequest, aMessageInfo.GetPeerAddr());
 
-    VerifyOrExit(aNeighbor != nullptr, error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(aNeighbor != nullptr, error = kErrorInvalidState);
 
     SuccessOrExit(error = Get<LinkMetrics>().HandleLinkMetricsManagementRequest(aMessage, *aNeighbor, status));
     error = SendLinkMetricsManagementResponse(aMessageInfo.GetPeerAddr(), status);
@@ -3841,11 +3846,11 @@
                                               const Ip6::MessageInfo &aMessageInfo,
                                               Neighbor *              aNeighbor)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     Log(kMessageReceive, kTypeLinkMetricsManagementResponse, aMessageInfo.GetPeerAddr());
 
-    VerifyOrExit(aNeighbor != nullptr, error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(aNeighbor != nullptr, error = kErrorInvalidState);
 
     error = Get<LinkMetrics>().HandleLinkMetricsManagementResponse(aMessage, aMessageInfo.GetPeerAddr());
 
@@ -3855,7 +3860,7 @@
 
 void Mle::HandleLinkProbe(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, Neighbor *aNeighbor)
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     uint8_t seriesId;
 
     Log(kMessageReceive, kTypeLinkProbe, aMessageInfo.GetPeerAddr());
@@ -3912,17 +3917,17 @@
     return (aAddress.GetPrefix() == GetMeshLocalPrefix());
 }
 
-otError Mle::CheckReachability(uint16_t aMeshDest, Ip6::Header &aIp6Header)
+Error Mle::CheckReachability(uint16_t aMeshDest, Ip6::Header &aIp6Header)
 {
-    otError error;
+    Error error;
 
     if ((aMeshDest != GetRloc16()) || Get<ThreadNetif>().HasUnicastAddress(aIp6Header.GetDestination()))
     {
-        error = OT_ERROR_NONE;
+        error = kErrorNone;
     }
     else
     {
-        error = OT_ERROR_NO_ROUTE;
+        error = kErrorNoRoute;
     }
 
     return error;
@@ -3931,7 +3936,7 @@
 #if OPENTHREAD_CONFIG_MLE_INFORM_PREVIOUS_PARENT_ON_REATTACH
 void Mle::InformPreviousParent(void)
 {
-    otError          error   = OT_ERROR_NONE;
+    Error            error   = kErrorNone;
     Message *        message = nullptr;
     Ip6::MessageInfo messageInfo;
 
@@ -3939,7 +3944,7 @@
 
     mCounters.mParentChanges++;
 
-    VerifyOrExit((message = Get<Ip6::Ip6>().NewMessage(0)) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Ip6::Ip6>().NewMessage(0)) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = message->SetLength(0));
 
     messageInfo.SetSockAddr(GetMeshLocal64());
@@ -3952,9 +3957,9 @@
 
 exit:
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogWarnMle("Failed to inform previous parent: %s", otThreadErrorToString(error));
+        otLogWarnMle("Failed to inform previous parent: %s", ErrorToString(error));
 
         FreeMessage(message);
     }
@@ -3964,7 +3969,7 @@
 #if OPENTHREAD_CONFIG_PARENT_SEARCH_ENABLE
 void Mle::HandleParentSearchTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<Mle>().HandleParentSearchTimer();
+    aTimer.Get<Mle>().HandleParentSearchTimer();
 }
 
 void Mle::HandleParentSearchTimer(void)
@@ -4095,153 +4100,137 @@
 #endif
 
 #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_WARN) && (OPENTHREAD_CONFIG_LOG_MLE == 1)
-void Mle::LogProcessError(MessageType aType, otError aError)
+void Mle::LogProcessError(MessageType aType, Error aError)
 {
     LogError(kMessageReceive, aType, aError);
 }
 
-void Mle::LogSendError(MessageType aType, otError aError)
+void Mle::LogSendError(MessageType aType, Error aError)
 {
     LogError(kMessageSend, aType, aError);
 }
 
-void Mle::LogError(MessageAction aAction, MessageType aType, otError aError)
+void Mle::LogError(MessageAction aAction, MessageType aType, Error aError)
 {
-    if (aError != OT_ERROR_NONE)
+    if (aError != kErrorNone)
     {
         otLogWarnMle("Failed to %s %s%s: %s", aAction == kMessageSend ? "send" : "process", MessageTypeToString(aType),
-                     MessageTypeActionToSuffixString(aType, aAction), otThreadErrorToString(aError));
+                     MessageTypeActionToSuffixString(aType, aAction), ErrorToString(aError));
     }
 }
 
 const char *Mle::MessageActionToString(MessageAction aAction)
 {
-    const char *str = "Unknown";
+    static const char *const kMessageActionStrings[] = {
+        "Send",           // (0) kMessageSend
+        "Receive",        // (1) kMessageReceive
+        "Delay",          // (2) kMessageDelay
+        "Remove Delayed", // (3) kMessageRemoveDelayed
+    };
 
-    switch (aAction)
-    {
-    case kMessageSend:
-        str = "Send";
-        break;
-    case kMessageReceive:
-        str = "Receive";
-        break;
-    case kMessageDelay:
-        str = "Delay";
-        break;
-    case kMessageRemoveDelayed:
-        str = "Remove Delayed";
-        break;
-    }
+    static_assert(kMessageSend == 0, "kMessageSend value is incorrect");
+    static_assert(kMessageReceive == 1, "kMessageReceive value is incorrect");
+    static_assert(kMessageDelay == 2, "kMessageDelay value is incorrect");
+    static_assert(kMessageRemoveDelayed == 3, "kMessageRemoveDelayed value is incorrect");
 
-    return str;
+    return kMessageActionStrings[aAction];
 }
 
 const char *Mle::MessageTypeToString(MessageType aType)
 {
-    const char *str = "Unknown";
-
-    switch (aType)
-    {
-    case kTypeAdvertisement:
-        str = "Advertisement";
-        break;
-    case kTypeAnnounce:
-        str = "Announce";
-        break;
-    case kTypeChildIdRequest:
-        str = "Child ID Request";
-        break;
-    case kTypeChildIdRequestShort:
-    case kTypeChildIdResponse:
-        str = "Child ID Response";
-        break;
-    case kTypeChildUpdateRequestOfParent:
+    static const char *const kMessageTypeStrings[] = {
+        "Advertisement",         // (0)  kTypeAdvertisement
+        "Announce",              // (1)  kTypeAnnounce
+        "Child ID Request",      // (2)  kTypeChildIdRequest
+        "Child ID Request",      // (3)  kTypeChildIdRequestShort
+        "Child ID Response",     // (4)  kTypeChildIdResponse
+        "Child Update Request",  // (5)  kTypeChildUpdateRequestOfParent
+        "Child Update Response", // (6)  kTypeChildUpdateResponseOfParent
+        "Data Request",          // (7)  kTypeDataRequest
+        "Data Response",         // (8)  kTypeDataResponse
+        "Discovery Request",     // (9)  kTypeDiscoveryRequest
+        "Discovery Response",    // (10) kTypeDiscoveryResponse
+        "delayed message",       // (11) kTypeGenericDelayed
+        "UDP",                   // (12) kTypeGenericUdp
+        "Parent Request",        // (13) kTypeParentRequestToRouters
+        "Parent Request",        // (14) kTypeParentRequestToRoutersReeds
+        "Parent Response",       // (15) kTypeParentResponse
 #if OPENTHREAD_FTD
-    case kTypeChildUpdateRequestOfChild:
-#endif
-        str = "Child Update Request";
-        break;
-    case kTypeChildUpdateResponseOfParent:
-#if OPENTHREAD_FTD
-    case kTypeChildUpdateResponseOfChild:
-    case kTypeChildUpdateResponseOfUnknownChild:
-#endif
-        str = "Child Update Response";
-        break;
-    case kTypeDataRequest:
-        str = "Data Request";
-        break;
-    case kTypeDataResponse:
-        str = "Data Response";
-        break;
-    case kTypeDiscoveryRequest:
-        str = "Discovery Request";
-        break;
-    case kTypeDiscoveryResponse:
-        str = "Discovery Response";
-        break;
-    case kTypeGenericDelayed:
-        str = "delayed message";
-        break;
-    case kTypeGenericUdp:
-        str = "UDP";
-        break;
-    case kTypeParentRequestToRouters:
-    case kTypeParentRequestToRoutersReeds:
-        str = "Parent Request";
-        break;
-    case kTypeParentResponse:
-        str = "Parent Response";
-        break;
-#if OPENTHREAD_FTD
-    case kTypeAddressRelease:
-        str = "Address Release";
-        break;
-    case kTypeAddressReleaseReply:
-        str = "Address Release Reply";
-        break;
-    case kTypeAddressReply:
-        str = "Address Reply";
-        break;
-    case kTypeAddressSolicit:
-        str = "Address Solicit";
-        break;
-    case kTypeLinkAccept:
-        str = "Link Accept";
-        break;
-    case kTypeLinkAcceptAndRequest:
-        str = "Link Accept and Request";
-        break;
-    case kTypeLinkReject:
-        str = "Link Reject";
-        break;
-    case kTypeLinkRequest:
-        str = "Link Request";
-        break;
-    case kTypeParentRequest:
-        str = "Parent Request";
-        break;
+        "Address Release",         // (16) kTypeAddressRelease
+        "Address Release Reply",   // (17) kTypeAddressReleaseReply
+        "Address Reply",           // (18) kTypeAddressReply
+        "Address Solicit",         // (19) kTypeAddressSolicit
+        "Child Update Request",    // (20) kTypeChildUpdateRequestOfChild
+        "Child Update Response",   // (21) kTypeChildUpdateResponseOfChild
+        "Child Update Response",   // (22) kTypeChildUpdateResponseOfUnknownChild
+        "Link Accept",             // (23) kTypeLinkAccept
+        "Link Accept and Request", // (24) kTypeLinkAcceptAndRequest
+        "Link Reject",             // (25) kTypeLinkReject
+        "Link Request",            // (26) kTypeLinkRequest
+        "Parent Request",          // (27) kTypeParentRequest
 #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
-    case kTypeTimeSync:
-        str = "Time Sync";
-        break;
+        "Time Sync", // (28) kTypeTimeSync
+#endif
+#endif
+#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
+        "Link Metrics Management Request",  // (29) kTypeLinkMetricsManagementRequest
+        "Link Metrics Management Response", // (30) kTypeLinkMetricsManagementResponse
+        "Link Probe",                       // (31) kTypeLinkProbe
+#endif
+    };
+
+    static_assert(kTypeAdvertisement == 0, "kTypeAdvertisement value is incorrect");
+    static_assert(kTypeAnnounce == 1, "kTypeAnnounce value is incorrect");
+    static_assert(kTypeChildIdRequest == 2, "kTypeChildIdRequest value is incorrect");
+    static_assert(kTypeChildIdRequestShort == 3, "kTypeChildIdRequestShort value is incorrect");
+    static_assert(kTypeChildIdResponse == 4, "kTypeChildIdResponse value is incorrect");
+    static_assert(kTypeChildUpdateRequestOfParent == 5, "kTypeChildUpdateRequestOfParent value is incorrect");
+    static_assert(kTypeChildUpdateResponseOfParent == 6, "kTypeChildUpdateResponseOfParent value is incorrect");
+    static_assert(kTypeDataRequest == 7, "kTypeDataRequest value is incorrect");
+    static_assert(kTypeDataResponse == 8, "kTypeDataResponse value is incorrect");
+    static_assert(kTypeDiscoveryRequest == 9, "kTypeDiscoveryRequest value is incorrect");
+    static_assert(kTypeDiscoveryResponse == 10, "kTypeDiscoveryResponse value is incorrect");
+    static_assert(kTypeGenericDelayed == 11, "kTypeGenericDelayed value is incorrect");
+    static_assert(kTypeGenericUdp == 12, "kTypeGenericUdp value is incorrect");
+    static_assert(kTypeParentRequestToRouters == 13, "kTypeParentRequestToRouters value is incorrect");
+    static_assert(kTypeParentRequestToRoutersReeds == 14, "kTypeParentRequestToRoutersReeds value is incorrect");
+    static_assert(kTypeParentResponse == 15, "kTypeParentResponse value is incorrect");
+#if OPENTHREAD_FTD
+    static_assert(kTypeAddressRelease == 16, "kTypeAddressRelease value is incorrect");
+    static_assert(kTypeAddressReleaseReply == 17, "kTypeAddressReleaseReply value is incorrect");
+    static_assert(kTypeAddressReply == 18, "kTypeAddressReply value is incorrect");
+    static_assert(kTypeAddressSolicit == 19, "kTypeAddressSolicit value is incorrect");
+    static_assert(kTypeChildUpdateRequestOfChild == 20, "kTypeChildUpdateRequestOfChild value is incorrect");
+    static_assert(kTypeChildUpdateResponseOfChild == 21, "kTypeChildUpdateResponseOfChild value is incorrect");
+    static_assert(kTypeChildUpdateResponseOfUnknownChild == 22, "kTypeChildUpdateResponseOfUnknownChild is incorrect");
+    static_assert(kTypeLinkAccept == 23, "kTypeLinkAccept value is incorrect");
+    static_assert(kTypeLinkAcceptAndRequest == 24, "kTypeLinkAcceptAndRequest value is incorrect");
+    static_assert(kTypeLinkReject == 25, "kTypeLinkReject value is incorrect");
+    static_assert(kTypeLinkRequest == 26, "kTypeLinkRequest value is incorrect");
+    static_assert(kTypeParentRequest == 27, "kTypeParentRequest value is incorrect");
+#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
+    static_assert(kTypeTimeSync == 28, "kTypeTimeSync value is incorrect");
+#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
+    static_assert(kTypeLinkMetricsManagementRequest == 29, "kTypeLinkMetricsManagementRequest value is incorrect)");
+    static_assert(kTypeLinkMetricsManagementResponse == 30, "kTypeLinkMetricsManagementResponse value is incorrect)");
+    static_assert(kTypeLinkProbe == 31, "kTypeLinkProbe value is incorrect)");
+#endif
+#else // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
+#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
+    static_assert(kTypeLinkMetricsManagementRequest == 28, "kTypeLinkMetricsManagementRequest value is incorrect)");
+    static_assert(kTypeLinkMetricsManagementResponse == 29, "kTypeLinkMetricsManagementResponse value is incorrect)");
+    static_assert(kTypeLinkProbe == 30, "kTypeLinkProbe value is incorrect)");
+#endif
+#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
+#else  // OPENTHREAD_FTD
+#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
+    static_assert(kTypeLinkMetricsManagementRequest == 16, "kTypeLinkMetricsManagementRequest value is incorrect)");
+    static_assert(kTypeLinkMetricsManagementResponse == 17, "kTypeLinkMetricsManagementResponse value is incorrect)");
+    static_assert(kTypeLinkProbe == 18, "kTypeLinkProbe value is incorrect)");
 #endif
 #endif // OPENTHREAD_FTD
-#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
-    case kTypeLinkMetricsManagementRequest:
-        str = "Link Metrics Management Request";
-        break;
-    case kTypeLinkMetricsManagementResponse:
-        str = "Link Metrics Management Response";
-        break;
-    case kTypeLinkProbe:
-        str = "Link Probe";
-        break;
-#endif
-    }
 
-    return str;
+    return kMessageTypeStrings[aType];
 }
 
 const char *Mle::MessageTypeActionToSuffixString(MessageType aType, MessageAction aAction)
@@ -4257,7 +4246,6 @@
     case kTypeChildUpdateResponseOfParent:
         str = (aAction == kMessageReceive) ? " from parent" : " to parent";
         break;
-
     case kTypeParentRequestToRouters:
         str = " to routers";
         break;
@@ -4284,32 +4272,21 @@
 
 const char *Mle::RoleToString(DeviceRole aRole)
 {
-    const char *roleString = "Unknown";
+    static const char *const kRoleStrings[] = {
+        "Disabled", // (0) kRoleDisabled
+        "Detached", // (1) kRoleDetached
+        "Child",    // (2) kRoleChild
+        "Router",   // (3) kRoleRouter
+        "Leader",   // (4) kRoleLeader
+    };
 
-    switch (aRole)
-    {
-    case kRoleDisabled:
-        roleString = "Disabled";
-        break;
+    static_assert(kRoleDisabled == 0, "kRoleDisabled value is incorrect");
+    static_assert(kRoleDetached == 1, "kRoleDetached value is incorrect");
+    static_assert(kRoleChild == 2, "kRoleChild value is incorrect");
+    static_assert(kRoleRouter == 3, "kRoleRouter value is incorrect");
+    static_assert(kRoleLeader == 4, "kRoleLeader value is incorrect");
 
-    case kRoleDetached:
-        roleString = "Detached";
-        break;
-
-    case kRoleChild:
-        roleString = "Child";
-        break;
-
-    case kRoleRouter:
-        roleString = "Router";
-        break;
-
-    case kRoleLeader:
-        roleString = "Leader";
-        break;
-    }
-
-    return roleString;
+    return kRoleStrings[aRole];
 }
 
 // LCOV_EXCL_START
@@ -4318,96 +4295,61 @@
 
 const char *Mle::AttachModeToString(AttachMode aMode)
 {
-    const char *str = "unknown";
+    static const char *const kAttachModeStrings[] = {
+        "any-partition",            // (0) kAttachAny
+        "same-partition-try-1",     // (1) kAttachSame1
+        "same-partition-try-2",     // (2) kAttachSame2
+        "better-partition",         // (3) kAttachBetter
+        "same-partition-downgrade", // (4) kAttachSameDowngrade
+    };
 
-    switch (aMode)
-    {
-    case kAttachAny:
-        str = "any-partition";
-        break;
+    static_assert(kAttachAny == 0, "kAttachAny value is incorrect");
+    static_assert(kAttachSame1 == 1, "kAttachSame1 value is incorrect");
+    static_assert(kAttachSame2 == 2, "kAttachSame2 value is incorrect");
+    static_assert(kAttachBetter == 3, "kAttachBetter value is incorrect");
+    static_assert(kAttachSameDowngrade == 4, "kAttachSameDowngrade value is incorrect");
 
-    case kAttachSame1:
-        str = "same-partition-try-1";
-        break;
-
-    case kAttachSame2:
-        str = "same-partition-try-2";
-        break;
-
-    case kAttachBetter:
-        str = "better-partition";
-        break;
-
-    case kAttachSameDowngrade:
-        str = "same-partition-downgrade";
-        break;
-    }
-
-    return str;
+    return kAttachModeStrings[aMode];
 }
 
 const char *Mle::AttachStateToString(AttachState aState)
 {
-    const char *str = "Unknown";
-
-    switch (aState)
-    {
-    case kAttachStateIdle:
-        str = "Idle";
-        break;
-
-    case kAttachStateProcessAnnounce:
-        str = "ProcessAnnounce";
-        break;
-
-    case kAttachStateStart:
-        str = "Start";
-        break;
-
-    case kAttachStateParentRequestRouter:
-        str = "ParentReqRouters";
-        break;
-
-    case kAttachStateParentRequestReed:
-        str = "ParentReqReeds";
-        break;
-
-    case kAttachStateAnnounce:
-        str = "Announce";
-        break;
-
-    case kAttachStateChildIdRequest:
-        str = "ChildIdReq";
-        break;
+    static const char *const kAttachStateStrings[] = {
+        "Idle",             // (0) kAttachStateIdle
+        "ProcessAnnounce",  // (1) kAttachStateProcessAnnounce
+        "Start",            // (2) kAttachStateStart
+        "ParentReqRouters", // (3) kAttachStateParentRequestRouter
+        "ParentReqReeds",   // (4) kAttachStateParentRequestReed
+        "Announce",         // (5) kAttachStateAnnounce
+        "ChildIdReq",       // (6) kAttachStateChildIdRequest
     };
 
-    return str;
+    static_assert(kAttachStateIdle == 0, "kAttachStateIdle value is incorrect");
+    static_assert(kAttachStateProcessAnnounce == 1, "kAttachStateProcessAnnounce value is incorrect");
+    static_assert(kAttachStateStart == 2, "kAttachStateStart value is incorrect");
+    static_assert(kAttachStateParentRequestRouter == 3, "kAttachStateParentRequestRouter value is incorrect");
+    static_assert(kAttachStateParentRequestReed == 4, "kAttachStateParentRequestReed value is incorrect");
+    static_assert(kAttachStateAnnounce == 5, "kAttachStateAnnounce value is incorrect");
+    static_assert(kAttachStateChildIdRequest == 6, "kAttachStateChildIdRequest value is incorrect");
+
+    return kAttachStateStrings[aState];
 }
 
 const char *Mle::ReattachStateToString(ReattachState aState)
 {
-    const char *str = "unknown";
+    static const char *const kReattachStateStrings[] = {
+        "",                                 // (0) kReattachStop
+        "reattaching",                      // (1) kReattachStart
+        "reattaching with Active Dataset",  // (2) kReattachActive
+        "reattaching with Pending Dataset", // (3) kReattachPending
+    };
 
-    switch (aState)
-    {
-    case kReattachStop:
-        str = "";
-        break;
+    static_assert(kReattachStop == 0, "kReattachStop value is incorrect");
+    static_assert(kReattachStart == 1, "kReattachStart value is incorrect");
+    static_assert(kReattachActive == 2, "kReattachActive value is incorrect");
+    static_assert(kReattachPending == 3, "kReattachPending value is incorrect");
 
-    case kReattachStart:
-        str = "reattaching";
-        break;
-
-    case kReattachActive:
-        str = "reattaching with Active Dataset";
-        break;
-
-    case kReattachPending:
-        str = "reattaching with Pending Dataset";
-        break;
-    }
-
-    return str;
+    return kReattachStateStrings[aState];
 }
 
 #endif // (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_NOTE) && (OPENTHREAD_CONFIG_LOG_MLE == 1)
@@ -4415,15 +4357,13 @@
 // LCOV_EXCL_STOP
 
 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
-otError Mle::SendLinkMetricsManagementRequest(const Ip6::Address &aDestination,
-                                              const uint8_t *     aSubTlvs,
-                                              uint8_t             aLength)
+Error Mle::SendLinkMetricsManagementRequest(const Ip6::Address &aDestination, const uint8_t *aSubTlvs, uint8_t aLength)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     Message *message;
     Tlv      tlv;
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = AppendHeader(*message, kCommandLinkMetricsManagementRequest));
 
     // Link Metrics Management TLV
@@ -4467,8 +4407,8 @@
 
 void Mle::DelayedResponseMetadata::RemoveFrom(Message &aMessage) const
 {
-    otError error = aMessage.SetLength(aMessage.GetLength() - sizeof(*this));
-    OT_ASSERT(error == OT_ERROR_NONE);
+    Error error = aMessage.SetLength(aMessage.GetLength() - sizeof(*this));
+    OT_ASSERT(error == kErrorNone);
     OT_UNUSED_VARIABLE(error);
 }
 
diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp
index 95b5474..62a599d 100644
--- a/src/core/thread/mle.hpp
+++ b/src/core/thread/mle.hpp
@@ -111,19 +111,19 @@
     /**
      * This method enables MLE.
      *
-     * @retval OT_ERROR_NONE     Successfully enabled MLE.
-     * @retval OT_ERROR_ALREADY  MLE was already enabled.
+     * @retval kErrorNone     Successfully enabled MLE.
+     * @retval kErrorAlready  MLE was already enabled.
      *
      */
-    otError Enable(void);
+    Error Enable(void);
 
     /**
      * This method disables MLE.
      *
-     * @retval OT_ERROR_NONE     Successfully disabled MLE.
+     * @retval kErrorNone     Successfully disabled MLE.
      *
      */
-    otError Disable(void);
+    Error Disable(void);
 
     /**
      * This method starts the MLE protocol operation.
@@ -131,11 +131,11 @@
      * @param[in]  aAnnounceAttach True if attach on the announced thread network with newer active timestamp,
      *                             or False if not.
      *
-     * @retval OT_ERROR_NONE     Successfully started the protocol operation.
-     * @retval OT_ERROR_ALREADY  The protocol operation was already started.
+     * @retval kErrorNone     Successfully started the protocol operation.
+     * @retval kErrorAlready  The protocol operation was already started.
      *
      */
-    otError Start(bool aAnnounceAttach);
+    Error Start(bool aAnnounceAttach);
 
     /**
      * This method stops the MLE protocol operation.
@@ -148,20 +148,20 @@
     /**
      * This method restores network information from non-volatile memory.
      *
-     * @retval OT_ERROR_NONE       Successfully restore the network information.
-     * @retval OT_ERROR_NOT_FOUND  There is no valid network information stored in non-volatile memory.
+     * @retval kErrorNone      Successfully restore the network information.
+     * @retval kErrorNotFound  There is no valid network information stored in non-volatile memory.
      *
      */
-    otError Restore(void);
+    Error Restore(void);
 
     /**
      * This method stores network information into non-volatile memory.
      *
-     * @retval OT_ERROR_NONE       Successfully store the network information.
-     * @retval OT_ERROR_NO_BUFS    Could not store the network information due to insufficient memory space.
+     * @retval kErrorNone      Successfully store the network information.
+     * @retval kErrorNoBufs    Could not store the network information due to insufficient memory space.
      *
      */
-    otError Store(void);
+    Error Store(void);
 
     /**
      * This method generates an MLE Announce message.
@@ -175,23 +175,23 @@
     /**
      * This method causes the Thread interface to detach from the Thread network.
      *
-     * @retval OT_ERROR_NONE           Successfully detached from the Thread network.
-     * @retval OT_ERROR_INVALID_STATE  MLE is Disabled.
+     * @retval kErrorNone          Successfully detached from the Thread network.
+     * @retval kErrorInvalidState  MLE is Disabled.
      *
      */
-    otError BecomeDetached(void);
+    Error BecomeDetached(void);
 
     /**
      * This method causes the Thread interface to attempt an MLE attach.
      *
      * @param[in]  aMode  Indicates what partitions to attach to.
      *
-     * @retval OT_ERROR_NONE           Successfully began the attach process.
-     * @retval OT_ERROR_INVALID_STATE  MLE is Disabled.
-     * @retval OT_ERROR_BUSY           An attach process is in progress.
+     * @retval kErrorNone          Successfully began the attach process.
+     * @retval kErrorInvalidState  MLE is Disabled.
+     * @retval kErrorBusy          An attach process is in progress.
      *
      */
-    otError BecomeChild(AttachMode aMode);
+    Error BecomeChild(AttachMode aMode);
 
     /**
      * This method indicates whether or not the Thread device is attached to a Thread network.
@@ -290,11 +290,11 @@
      *
      * @param[in]  aDeviceMode  The device mode to set.
      *
-     * @retval OT_ERROR_NONE          Successfully set the Mode TLV.
-     * @retval OT_ERROR_INVALID_ARGS  The mode combination specified in @p aMode is invalid.
+     * @retval kErrorNone         Successfully set the Mode TLV.
+     * @retval kErrorInvalidArgs  The mode combination specified in @p aMode is invalid.
      *
      */
-    otError SetDeviceMode(DeviceMode aDeviceMode);
+    Error SetDeviceMode(DeviceMode aDeviceMode);
 
     /**
      * This method indicates whether or not the device is rx-on-when-idle.
@@ -486,22 +486,22 @@
      *
      * @param[out]  aAddress  A reference to the Leader's RLOC.
      *
-     * @retval OT_ERROR_NONE      Successfully retrieved the Leader's RLOC.
-     * @retval OT_ERROR_DETACHED  The Thread interface is not currently attached to a Thread Partition.
+     * @retval kErrorNone      Successfully retrieved the Leader's RLOC.
+     * @retval kErrorDetached  The Thread interface is not currently attached to a Thread Partition.
      *
      */
-    otError GetLeaderAddress(Ip6::Address &aAddress) const;
+    Error GetLeaderAddress(Ip6::Address &aAddress) const;
 
     /**
      * This method retrieves the Leader's ALOC.
      *
      * @param[out]  aAddress  A reference to the Leader's ALOC.
      *
-     * @retval OT_ERROR_NONE      Successfully retrieved the Leader's ALOC.
-     * @retval OT_ERROR_DETACHED  The Thread interface is not currently attached to a Thread Partition.
+     * @retval kErrorNone      Successfully retrieved the Leader's ALOC.
+     * @retval kErrorDetached  The Thread interface is not currently attached to a Thread Partition.
      *
      */
-    otError GetLeaderAloc(Ip6::Address &aAddress) const { return GetLocatorAddress(aAddress, kAloc16Leader); }
+    Error GetLeaderAloc(Ip6::Address &aAddress) const { return GetLocatorAddress(aAddress, kAloc16Leader); }
 
     /**
      * This method computes the Commissioner's ALOC.
@@ -509,11 +509,11 @@
      * @param[out]  aAddress        A reference to the Commissioner's ALOC.
      * @param[in]   aSessionId      Commissioner session id.
      *
-     * @retval OT_ERROR_NONE      Successfully retrieved the Commissioner's ALOC.
-     * @retval OT_ERROR_DETACHED  The Thread interface is not currently attached to a Thread Partition.
+     * @retval kErrorNone      Successfully retrieved the Commissioner's ALOC.
+     * @retval kErrorDetached  The Thread interface is not currently attached to a Thread Partition.
      *
      */
-    otError GetCommissionerAloc(Ip6::Address &aAddress, uint16_t aSessionId) const
+    Error GetCommissionerAloc(Ip6::Address &aAddress, uint16_t aSessionId) const
     {
         return GetLocatorAddress(aAddress, CommissionerAloc16FromId(aSessionId));
     }
@@ -524,11 +524,11 @@
      * @param[in]   aServiceId Service ID to get ALOC for.
      * @param[out]  aAddress   A reference to the Service ALOC.
      *
-     * @retval OT_ERROR_NONE      Successfully retrieved the Service ALOC.
-     * @retval OT_ERROR_DETACHED  The Thread interface is not currently attached to a Thread Partition.
+     * @retval kErrorNone      Successfully retrieved the Service ALOC.
+     * @retval kErrorDetached  The Thread interface is not currently attached to a Thread Partition.
      *
      */
-    otError GetServiceAloc(uint8_t aServiceId, Ip6::Address &aAddress) const;
+    Error GetServiceAloc(uint8_t aServiceId, Ip6::Address &aAddress) const;
 
     /**
      * This method returns the most recently received Leader Data.
@@ -691,11 +691,11 @@
      * @param[out]  aAddress  A reference to the RLOC or ALOC.
      * @param[in]   aLocator  RLOC16 or ALOC16.
      *
-     * @retval OT_ERROR_NONE      If got the RLOC or ALOC successfully.
-     * @retval OT_ERROR_DETACHED  If device is detached.
+     * @retval kErrorNone      If got the RLOC or ALOC successfully.
+     * @retval kErrorDetached  If device is detached.
      *
      */
-    otError GetLocatorAddress(Ip6::Address &aAddress, uint16_t aLocator) const;
+    Error GetLocatorAddress(Ip6::Address &aAddress, uint16_t aLocator) const;
 
     /**
      * This method schedules a Child Update Request.
@@ -707,7 +707,7 @@
      * This method indicates whether or not the device has restored the network information from
      * non-volatile settings after boot.
      *
-     * @retval true  Sucessfully restored the network information.
+     * @retval true  Successfully restored the network information.
      * @retval false No valid network information was found.
      *
      */
@@ -929,22 +929,22 @@
      * @param[in]  aMessage  A reference to the message.
      * @param[in]  aCommand  The MLE Command Type.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the header.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the header.
+     * @retval kErrorNone    Successfully appended the header.
+     * @retval kErrorNoBufs  Insufficient buffers available to append the header.
      *
      */
-    otError AppendHeader(Message &aMessage, Command aCommand);
+    Error AppendHeader(Message &aMessage, Command aCommand);
 
     /**
      * This method appends a Source Address TLV to a message.
      *
      * @param[in]  aMessage  A reference to the message.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Source Address TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Source Address TLV.
+     * @retval kErrorNone    Successfully appended the Source Address TLV.
+     * @retval kErrorNoBufs  Insufficient buffers available to append the Source Address TLV.
      *
      */
-    otError AppendSourceAddress(Message &aMessage) const;
+    Error AppendSourceAddress(Message &aMessage) const;
 
     /**
      * This method appends a Mode TLV to a message.
@@ -952,11 +952,11 @@
      * @param[in]  aMessage  A reference to the message.
      * @param[in]  aMode     The Device Mode.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Mode TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Mode TLV.
+     * @retval kErrorNone    Successfully appended the Mode TLV.
+     * @retval kErrorNoBufs  Insufficient buffers available to append the Mode TLV.
      *
      */
-    otError AppendMode(Message &aMessage, DeviceMode aMode);
+    Error AppendMode(Message &aMessage, DeviceMode aMode);
 
     /**
      * This method appends a Timeout TLV to a message.
@@ -964,11 +964,11 @@
      * @param[in]  aMessage  A reference to the message.
      * @param[in]  aTimeout  The Timeout value.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Timeout TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Timeout TLV.
+     * @retval kErrorNone    Successfully appended the Timeout TLV.
+     * @retval kErrorNoBufs  Insufficient buffers available to append the Timeout TLV.
      *
      */
-    otError AppendTimeout(Message &aMessage, uint32_t aTimeout);
+    Error AppendTimeout(Message &aMessage, uint32_t aTimeout);
 
     /**
      * This method appends a Challenge TLV to a message.
@@ -977,11 +977,11 @@
      * @param[in]  aChallenge        A pointer to the Challenge value.
      * @param[in]  aChallengeLength  The length of the Challenge value in bytes.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Challenge TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Challenge TLV.
+     * @retval kErrorNone    Successfully appended the Challenge TLV.
+     * @retval kErrorNoBufs  Insufficient buffers available to append the Challenge TLV.
      *
      */
-    otError AppendChallenge(Message &aMessage, const uint8_t *aChallenge, uint8_t aChallengeLength);
+    Error AppendChallenge(Message &aMessage, const uint8_t *aChallenge, uint8_t aChallengeLength);
 
     /**
      * This method appends a Challenge TLV to a message.
@@ -989,11 +989,11 @@
      * @param[in]  aMessage          A reference to the message.
      * @param[in]  aChallenge        A reference to the Challenge data.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Challenge TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Challenge TLV.
+     * @retval kErrorNone    Successfully appended the Challenge TLV.
+     * @retval kErrorNoBufs  Insufficient buffers available to append the Challenge TLV.
      *
      */
-    otError AppendChallenge(Message &aMessage, const Challenge &aChallenge);
+    Error AppendChallenge(Message &aMessage, const Challenge &aChallenge);
 
     /**
      * This method reads Challenge TLV from a message.
@@ -1001,12 +1001,12 @@
      * @param[in]  aMessage          A reference to the message.
      * @param[out] aChallenge        A reference to the Challenge data where to output the read value.
      *
-     * @retval OT_ERROR_NONE       Successfully read the Challenge TLV.
-     * @retval OT_ERROR_NOT_FOUND  Challenge TLV was not found in the message.
-     * @retval OT_ERROR_PARSE      Challenge TLV was found but could not be parsed.
+     * @retval kErrorNone       Successfully read the Challenge TLV.
+     * @retval kErrorNotFound   Challenge TLV was not found in the message.
+     * @retval kErrorParse      Challenge TLV was found but could not be parsed.
      *
      */
-    otError ReadChallenge(const Message &aMessage, Challenge &aChallenge);
+    Error ReadChallenge(const Message &aMessage, Challenge &aChallenge);
 
     /**
      * This method appends a Response TLV to a message.
@@ -1014,11 +1014,11 @@
      * @param[in]  aMessage         A reference to the message.
      * @param[in]  aResponse        A reference to the Response data.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Response TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Response TLV.
+     * @retval kErrorNone    Successfully appended the Response TLV.
+     * @retval kErrorNoBufs  Insufficient buffers available to append the Response TLV.
      *
      */
-    otError AppendResponse(Message &aMessage, const Challenge &aResponse);
+    Error AppendResponse(Message &aMessage, const Challenge &aResponse);
 
     /**
      * This method reads Response TLV from a message.
@@ -1026,23 +1026,23 @@
      * @param[in]  aMessage         A reference to the message.
      * @param[out] aResponse        A reference to the Response data where to output the read value.
      *
-     * @retval OT_ERROR_NONE       Successfully read the Response TLV.
-     * @retval OT_ERROR_NOT_FOUND  Response TLV was not found in the message.
-     * @retval OT_ERROR_PARSE      Response TLV was found but could not be parsed.
+     * @retval kErrorNone       Successfully read the Response TLV.
+     * @retval kErrorNotFound   Response TLV was not found in the message.
+     * @retval kErrorParse      Response TLV was found but could not be parsed.
      *
      */
-    otError ReadResponse(const Message &aMessage, Challenge &aResponse);
+    Error ReadResponse(const Message &aMessage, Challenge &aResponse);
 
     /**
      * This method appends a Link Frame Counter TLV to a message.
      *
      * @param[in]  aMessage  A reference to the message.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Link Frame Counter TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Link Frame Counter TLV.
+     * @retval kErrorNone     Successfully appended the Link Frame Counter TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the Link Frame Counter TLV.
      *
      */
-    otError AppendLinkFrameCounter(Message &aMessage);
+    Error AppendLinkFrameCounter(Message &aMessage);
 
     /**
      * This method reads Link and MLE Frame Counters from a message.
@@ -1055,23 +1055,23 @@
      * @param[out] aLinkFrameCounter   A reference to an `uint32_t` to output the Link Frame Counter.
      * @param[out] aMleFrameCounter    A reference to an `uint32_t` to output the MLE Frame Counter.
      *
-     * @retval OT_ERROR_NONE       Successfully read the counters.
-     * @retval OT_ERROR_NOT_FOUND  Link Frame Counter TLV was not found in the message.
-     * @retval OT_ERROR_PARSE      TLVs are not well-formed.
+     * @retval kErrorNone       Successfully read the counters.
+     * @retval kErrorNotFound   Link Frame Counter TLV was not found in the message.
+     * @retval kErrorParse      TLVs are not well-formed.
      *
      */
-    otError ReadFrameCounters(const Message &aMessage, uint32_t &aLinkFrameCounter, uint32_t &aMleFrameCounter) const;
+    Error ReadFrameCounters(const Message &aMessage, uint32_t &aLinkFrameCounter, uint32_t &aMleFrameCounter) const;
 
     /**
      * This method appends an MLE Frame Counter TLV to a message.
      *
      * @param[in]  aMessage  A reference to the message.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Frame Counter TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the MLE Frame Counter TLV.
+     * @retval kErrorNone     Successfully appended the Frame Counter TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the MLE Frame Counter TLV.
      *
      */
-    otError AppendMleFrameCounter(Message &aMessage);
+    Error AppendMleFrameCounter(Message &aMessage);
 
     /**
      * This method appends an Address16 TLV to a message.
@@ -1079,11 +1079,11 @@
      * @param[in]  aMessage  A reference to the message.
      * @param[in]  aRloc16   The RLOC16 value.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Address16 TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Address16 TLV.
+     * @retval kErrorNone     Successfully appended the Address16 TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the Address16 TLV.
      *
      */
-    otError AppendAddress16(Message &aMessage, uint16_t aRloc16);
+    Error AppendAddress16(Message &aMessage, uint16_t aRloc16);
 
     /**
      * This method appends a Network Data TLV to the message.
@@ -1091,11 +1091,11 @@
      * @param[in]  aMessage     A reference to the message.
      * @param[in]  aStableOnly  TRUE to append stable data, FALSE otherwise.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Network Data TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Network Data TLV.
+     * @retval kErrorNone     Successfully appended the Network Data TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the Network Data TLV.
      *
      */
-    otError AppendNetworkData(Message &aMessage, bool aStableOnly);
+    Error AppendNetworkData(Message &aMessage, bool aStableOnly);
 
     /**
      * This method appends a TLV Request TLV to a message.
@@ -1104,11 +1104,11 @@
      * @param[in]  aTlvs        A pointer to the list of TLV types.
      * @param[in]  aTlvsLength  The number of TLV types in @p aTlvs
      *
-     * @retval OT_ERROR_NONE     Successfully appended the TLV Request TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the TLV Request TLV.
+     * @retval kErrorNone     Successfully appended the TLV Request TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the TLV Request TLV.
      *
      */
-    otError AppendTlvRequest(Message &aMessage, const uint8_t *aTlvs, uint8_t aTlvsLength);
+    Error AppendTlvRequest(Message &aMessage, const uint8_t *aTlvs, uint8_t aTlvsLength);
 
     /**
      * This method reads TLV Request TLV from a message.
@@ -1116,23 +1116,23 @@
      * @param[in]  aMessage         A reference to the message.
      * @param[out] aRequestedTlvs   A reference to output the read list of requested TLVs.
      *
-     * @retval OT_ERROR_NONE       Successfully read the TLV.
-     * @retval OT_ERROR_NOT_FOUND  TLV was not found in the message.
-     * @retval OT_ERROR_PARSE      TLV was found but could not be parsed.
+     * @retval kErrorNone       Successfully read the TLV.
+     * @retval kErrorNotFound   TLV was not found in the message.
+     * @retval kErrorParse      TLV was found but could not be parsed.
      *
      */
-    otError FindTlvRequest(const Message &aMessage, RequestedTlvs &aRequestedTlvs);
+    Error FindTlvRequest(const Message &aMessage, RequestedTlvs &aRequestedTlvs);
 
     /**
      * This method appends a Leader Data TLV to a message.
      *
      * @param[in]  aMessage  A reference to the message.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Leader Data TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Leader Data TLV.
+     * @retval kErrorNone     Successfully appended the Leader Data TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the Leader Data TLV.
      *
      */
-    otError AppendLeaderData(Message &aMessage);
+    Error AppendLeaderData(Message &aMessage);
 
     /**
      * This method reads Leader Data TLV from a message.
@@ -1140,12 +1140,12 @@
      * @param[in]  aMessage        A reference to the message.
      * @param[out] aLeaderData     A reference to output the Leader Data.
      *
-     * @retval OT_ERROR_NONE       Successfully read the TLV.
-     * @retval OT_ERROR_NOT_FOUND  TLV was not found in the message.
-     * @retval OT_ERROR_PARSE      TLV was found but could not be parsed.
+     * @retval kErrorNone       Successfully read the TLV.
+     * @retval kErrorNotFound   TLV was not found in the message.
+     * @retval kErrorParse      TLV was found but could not be parsed.
      *
      */
-    otError ReadLeaderData(const Message &aMessage, LeaderData &aLeaderData);
+    Error ReadLeaderData(const Message &aMessage, LeaderData &aLeaderData);
 
     /**
      * This method appends a Scan Mask TLV to a message.
@@ -1153,11 +1153,11 @@
      * @param[in]  aMessage   A reference to the message.
      * @param[in]  aScanMask  The Scan Mask value.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Scan Mask TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Scan Mask TLV.
+     * @retval kErrorNone     Successfully appended the Scan Mask TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the Scan Mask TLV.
      *
      */
-    otError AppendScanMask(Message &aMessage, uint8_t aScanMask);
+    Error AppendScanMask(Message &aMessage, uint8_t aScanMask);
 
     /**
      * This method appends a Status TLV to a message.
@@ -1165,11 +1165,11 @@
      * @param[in]  aMessage  A reference to the message.
      * @param[in]  aStatus   The Status value.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Status TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Status TLV.
+     * @retval kErrorNone     Successfully appended the Status TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the Status TLV.
      *
      */
-    otError AppendStatus(Message &aMessage, StatusTlv::Status aStatus);
+    Error AppendStatus(Message &aMessage, StatusTlv::Status aStatus);
 
     /**
      * This method appends a Link Margin TLV to a message.
@@ -1177,22 +1177,22 @@
      * @param[in]  aMessage     A reference to the message.
      * @param[in]  aLinkMargin  The Link Margin value.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Link Margin TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Link Margin TLV.
+     * @retval kErrorNone     Successfully appended the Link Margin TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the Link Margin TLV.
      *
      */
-    otError AppendLinkMargin(Message &aMessage, uint8_t aLinkMargin);
+    Error AppendLinkMargin(Message &aMessage, uint8_t aLinkMargin);
 
     /**
      * This method appends a Version TLV to a message.
      *
      * @param[in]  aMessage  A reference to the message.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Version TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Version TLV.
+     * @retval kErrorNone     Successfully appended the Version TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the Version TLV.
      *
      */
-    otError AppendVersion(Message &aMessage);
+    Error AppendVersion(Message &aMessage);
 
     /**
      * This method appends an Address Registration TLV to a message.
@@ -1200,11 +1200,11 @@
      * @param[in]  aMessage  A reference to the message.
      * @param[in]  aMode     Determines which addresses to include in the TLV (see `AddressRegistrationMode`).
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Address Registration TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Address Registration TLV.
+     * @retval kErrorNone     Successfully appended the Address Registration TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the Address Registration TLV.
      *
      */
-    otError AppendAddressRegistration(Message &aMessage, AddressRegistrationMode aMode = kAppendAllAddresses);
+    Error AppendAddressRegistration(Message &aMessage, AddressRegistrationMode aMode = kAppendAllAddresses);
 
 #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
     /**
@@ -1212,80 +1212,80 @@
      *
      * @param[in]  aMessage  A reference to the message.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Time Request TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Time Request TLV.
+     * @retval kErrorNone     Successfully appended the Time Request TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the Time Request TLV.
      *
      */
-    otError AppendTimeRequest(Message &aMessage);
+    Error AppendTimeRequest(Message &aMessage);
 
     /**
      * This method appends a Time Parameter TLV to a message.
      *
      * @param[in]  aMessage  A reference to the message.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Time Parameter TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Time Parameter TLV.
+     * @retval kErrorNone     Successfully appended the Time Parameter TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the Time Parameter TLV.
      *
      */
-    otError AppendTimeParameter(Message &aMessage);
+    Error AppendTimeParameter(Message &aMessage);
 
     /**
      * This method appends a XTAL Accuracy TLV to a message.
      *
      * @param[in]  aMessage  A reference to the message.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the XTAL Accuracy TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the XTAl Accuracy TLV.
+     * @retval kErrorNone     Successfully appended the XTAL Accuracy TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the XTAl Accuracy TLV.
      *
      */
-    otError AppendXtalAccuracy(Message &aMessage);
+    Error AppendXtalAccuracy(Message &aMessage);
 #endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
 
-#if (!OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE) || OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+#if (OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE) || OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
     /**
      * This method appends a CSL Channel TLV to a message.
      *
      * @param[in]  aMessage  A reference to the message.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the CSL Channel TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the CSL Channel TLV.
+     * @retval kErrorNone     Successfully appended the CSL Channel TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the CSL Channel TLV.
      *
      */
-    otError AppendCslChannel(Message &aMessage);
+    Error AppendCslChannel(Message &aMessage);
 
     /**
      * This method appends a CSL Sync Timeout TLV to a message.
      *
      * @param[in]  aMessage  A reference to the message.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the CSL Timeout TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the CSL Timeout TLV.
+     * @retval kErrorNone     Successfully appended the CSL Timeout TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the CSL Timeout TLV.
      *
      */
-    otError AppendCslTimeout(Message &aMessage);
-#endif // (!OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE) || OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+    Error AppendCslTimeout(Message &aMessage);
+#endif // (OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE) || OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
 
     /**
      * This method appends a Active Timestamp TLV to a message.
      *
      * @param[in]  aMessage  A reference to the message.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Active Timestamp TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Active Timestamp TLV.
+     * @retval kErrorNone     Successfully appended the Active Timestamp TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the Active Timestamp TLV.
      *
      */
-    otError AppendActiveTimestamp(Message &aMessage);
+    Error AppendActiveTimestamp(Message &aMessage);
 
     /**
      * This method appends a Pending Timestamp TLV to a message.
      *
      * @param[in]  aMessage  A reference to the message.
      *
-     * @retval OT_ERROR_NONE     Successfully appended the Pending Timestamp TLV.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers available to append the Pending Timestamp TLV.
+     * @retval kErrorNone     Successfully appended the Pending Timestamp TLV.
+     * @retval kErrorNoBufs   Insufficient buffers available to append the Pending Timestamp TLV.
      *
      */
-    otError AppendPendingTimestamp(Message &aMessage);
+    Error AppendPendingTimestamp(Message &aMessage);
 
     /**
      * This method checks if the destination is reachable.
@@ -1293,11 +1293,11 @@
      * @param[in]  aMeshDest   The RLOC16 of the destination.
      * @param[in]  aIp6Header  The IPv6 header of the message.
      *
-     * @retval OT_ERROR_NONE      The destination is reachable.
-     * @retval OT_ERROR_NO_ROUTE  The destination is not reachable and the message should be dropped.
+     * @retval kErrorNone      The destination is reachable.
+     * @retval kErrorNoRoute   The destination is not reachable and the message should be dropped.
      *
      */
-    otError CheckReachability(uint16_t aMeshDest, Ip6::Header &aIp6Header);
+    Error CheckReachability(uint16_t aMeshDest, Ip6::Header &aIp6Header);
 
     /**
      * This method returns the next hop towards an RLOC16 destination.
@@ -1319,25 +1319,25 @@
      * @param[in]  aExtraTlvs        A pointer to extra TLVs.
      * @param[in]  aExtraTlvsLength  Length of extra TLVs.
      *
-     * @retval OT_ERROR_NONE     Successfully generated an MLE Data Request message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers to generate the MLE Data Request message.
+     * @retval kErrorNone     Successfully generated an MLE Data Request message.
+     * @retval kErrorNoBufs   Insufficient buffers to generate the MLE Data Request message.
      *
      */
-    otError SendDataRequest(const Ip6::Address &aDestination,
-                            const uint8_t *     aTlvs,
-                            uint8_t             aTlvsLength,
-                            uint16_t            aDelay,
-                            const uint8_t *     aExtraTlvs       = nullptr,
-                            uint8_t             aExtraTlvsLength = 0);
+    Error SendDataRequest(const Ip6::Address &aDestination,
+                          const uint8_t *     aTlvs,
+                          uint8_t             aTlvsLength,
+                          uint16_t            aDelay,
+                          const uint8_t *     aExtraTlvs       = nullptr,
+                          uint8_t             aExtraTlvsLength = 0);
 
     /**
      * This method generates an MLE Child Update Request message.
      *
-     * @retval OT_ERROR_NONE     Successfully generated an MLE Child Update Request message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers to generate the MLE Child Update Request message.
+     * @retval kErrorNone    Successfully generated an MLE Child Update Request message.
+     * @retval kErrorNoBufs  Insufficient buffers to generate the MLE Child Update Request message.
      *
      */
-    otError SendChildUpdateRequest(void);
+    Error SendChildUpdateRequest(void);
 
     /**
      * This method generates an MLE Child Update Response message.
@@ -1346,11 +1346,11 @@
      * @param[in]  aNumTlvs      The number of TLV types in @p aTlvs.
      * @param[in]  aChallenge    The Challenge for the response.
      *
-     * @retval OT_ERROR_NONE     Successfully generated an MLE Child Update Response message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers to generate the MLE Child Update Response message.
+     * @retval kErrorNone     Successfully generated an MLE Child Update Response message.
+     * @retval kErrorNoBufs   Insufficient buffers to generate the MLE Child Update Response message.
      *
      */
-    otError SendChildUpdateResponse(const uint8_t *aTlvs, uint8_t aNumTlvs, const Challenge &aChallenge);
+    Error SendChildUpdateResponse(const uint8_t *aTlvs, uint8_t aNumTlvs, const Challenge &aChallenge);
 
     /**
      * This method submits an MLE message to the UDP socket.
@@ -1358,11 +1358,11 @@
      * @param[in]  aMessage      A reference to the message.
      * @param[in]  aDestination  A reference to the IPv6 address of the destination.
      *
-     * @retval OT_ERROR_NONE     Successfully submitted the MLE message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers to form the rest of the MLE message.
+     * @retval kErrorNone     Successfully submitted the MLE message.
+     * @retval kErrorNoBufs   Insufficient buffers to form the rest of the MLE message.
      *
      */
-    otError SendMessage(Message &aMessage, const Ip6::Address &aDestination);
+    Error SendMessage(Message &aMessage, const Ip6::Address &aDestination);
 
     /**
      * This method sets the RLOC16 assigned to the Thread interface.
@@ -1401,11 +1401,11 @@
      * @param[in]  aDestination  The IPv6 address of the recipient of the message.
      * @param[in]  aDelay        The delay in milliseconds before transmission of the message.
      *
-     * @retval OT_ERROR_NONE     Successfully queued the message to transmit after the delay.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers to queue the message.
+     * @retval kErrorNone     Successfully queued the message to transmit after the delay.
+     * @retval kErrorNoBufs   Insufficient buffers to queue the message.
      *
      */
-    otError AddDelayedResponse(Message &aMessage, const Ip6::Address &aDestination, uint16_t aDelay);
+    Error AddDelayedResponse(Message &aMessage, const Ip6::Address &aDestination, uint16_t aDelay);
 
 #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_INFO) && (OPENTHREAD_CONFIG_LOG_MLE == 1)
     /**
@@ -1437,29 +1437,29 @@
     /**
      * This static method emits a log message indicating an error in processing of a message.
      *
-     * Note that log message is emitted only if there is an error, i.e., @p aError is not `OT_ERROR_NONE`. The log
+     * Note that log message is emitted only if there is an error, i.e., @p aError is not `kErrorNone`. The log
      * message will have the format "Failed to process {aMessageString} : {ErrorString}".
      *
      * @param[in]  aType      The message type.
      * @param[in]  aError     The error in processing of the message.
      *
      */
-    static void LogProcessError(MessageType aType, otError aError);
+    static void LogProcessError(MessageType aType, Error aError);
 
     /**
      * This static method emits a log message indicating an error when sending a message.
      *
-     * Note that log message is emitted only if there is an error, i.e. @p aError is not `OT_ERROR_NONE`. The log
+     * Note that log message is emitted only if there is an error, i.e. @p aError is not `kErrorNone`. The log
      * message will have the format "Failed to send {Message Type} : {ErrorString}".
      *
      * @param[in]  aType    The message type.
      * @param[in]  aError   The error in sending the message.
      *
      */
-    static void LogSendError(MessageType aType, otError aError);
+    static void LogSendError(MessageType aType, Error aError);
 #else
-    static void LogProcessError(MessageType, otError) {}
-    static void LogSendError(MessageType, otError) {}
+    static void LogProcessError(MessageType, Error) {}
+    static void LogSendError(MessageType, Error) {}
 #endif // #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_WARN) && (OPENTHREAD_CONFIG_LOG_MLE == 1)
 
     /**
@@ -1520,13 +1520,11 @@
      * @param[in]  aSubTlvs      A pointer to the buffer of the sub-TLVs in the message.
      * @param[in]  aLength       The overall length of @p aSubTlvs.
      *
-     * @retval OT_ERROR_NONE     Successfully sent a Link Metrics Management Request.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers to generate the MLE Link Metrics Management Request message.
+     * @retval kErrorNone     Successfully sent a Link Metrics Management Request.
+     * @retval kErrorNoBufs   Insufficient buffers to generate the MLE Link Metrics Management Request message.
      *
      */
-    otError SendLinkMetricsManagementRequest(const Ip6::Address &aDestination,
-                                             const uint8_t *     aSubTlvs,
-                                             uint8_t             aLength);
+    Error SendLinkMetricsManagementRequest(const Ip6::Address &aDestination, const uint8_t *aSubTlvs, uint8_t aLength);
 
     /**
      * This method sends an MLE Link Probe message.
@@ -1536,12 +1534,12 @@
      * @param[in]  aBuf          A pointer to the data payload.
      * @param[in]  aLength       The length of the data payload in Link Probe TLV, [0, 64].
      *
-     * @retval OT_ERROR_NONE          Successfully sent a Link Metrics Management Request.
-     * @retval OT_ERROR_NO_BUFS       Insufficient buffers to generate the MLE Link Metrics Management Request message.
-     * @retval OT_ERROR_INVALID_ARGS  Series ID is not a valid value, not within range [1, 254].
+     * @retval kErrorNone         Successfully sent a Link Metrics Management Request.
+     * @retval kErrorNoBufs       Insufficient buffers to generate the MLE Link Metrics Management Request message.
+     * @retval kErrorInvalidArgs  Series ID is not a valid value, not within range [1, 254].
      *
      */
-    otError SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t *aBuf, uint8_t aLength);
+    Error SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t *aBuf, uint8_t aLength);
 
 #endif
 
@@ -1575,11 +1573,15 @@
         kParentSearchBackoffInterval = (OPENTHREAD_CONFIG_PARENT_SEARCH_BACKOFF_INTERVAL * 1000u),
         kParentSearchJitterInterval  = (15 * 1000u),
         kParentSearchRssThreadhold   = OPENTHREAD_CONFIG_PARENT_SEARCH_RSS_THRESHOLD,
+    };
 
-        // Parameters for "attach backoff" feature (CONFIG_ENABLE_ATTACH_BACKOFF) - Intervals are in milliseconds.
-        kAttachBackoffMinInterval = OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_MINIMUM_INTERVAL,
-        kAttachBackoffMaxInterval = OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_MAXIMUM_INTERVAL,
-        kAttachBackoffJitter      = OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_JITTER_INTERVAL,
+    // Parameters for "attach backoff" feature (CONFIG_ENABLE_ATTACH_BACKOFF) - Intervals are in milliseconds.
+    enum : uint32_t
+    {
+        kAttachBackoffMinInterval         = OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_MINIMUM_INTERVAL,
+        kAttachBackoffMaxInterval         = OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_MAXIMUM_INTERVAL,
+        kAttachBackoffJitter              = OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_JITTER_INTERVAL,
+        kAttachBackoffDelayToResetCounter = OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_DELAY_TO_RESET_BACKOFF_INTERVAL,
     };
 
     enum ParentRequestType
@@ -1603,9 +1605,9 @@
 
     struct DelayedResponseMetadata
     {
-        otError AppendTo(Message &aMessage) const { return aMessage.Append(*this); }
-        void    ReadFrom(const Message &aMessage);
-        void    RemoveFrom(Message &aMessage) const;
+        Error AppendTo(Message &aMessage) const { return aMessage.Append(*this); }
+        void  ReadFrom(const Message &aMessage);
+        void  RemoveFrom(Message &aMessage) const;
 
         Ip6::Address mDestination; // IPv6 address of the message destination.
         TimeMilli    mSendTime;    // Time when the message shall be sent.
@@ -1713,7 +1715,7 @@
     static void HandleUdpReceive(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
     void        HandleUdpReceive(Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
     void        ScheduleMessageTransmissionTimer(void);
-    otError     ReadChallengeOrResponse(const Message &aMessage, uint8_t aTlvType, Challenge &aBuffer);
+    Error       ReadChallengeOrResponse(const Message &aMessage, uint8_t aTlvType, Challenge &aBuffer);
 
     void HandleAdvertisement(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, Neighbor *aNeighbor);
     void HandleChildIdResponse(const Message &         aMessage,
@@ -1735,18 +1737,18 @@
                                              Neighbor *              aNeighbor);
     void HandleLinkProbe(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, Neighbor *aNeighbor);
 #endif
-    otError HandleLeaderData(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
-    void    ProcessAnnounce(void);
-    bool    HasUnregisteredAddress(void);
+    Error HandleLeaderData(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    void  ProcessAnnounce(void);
+    bool  HasUnregisteredAddress(void);
 
     uint32_t GetAttachStartDelay(void) const;
-    otError  SendParentRequest(ParentRequestType aType);
-    otError  SendChildIdRequest(void);
-    otError  SendOrphanAnnounce(void);
+    Error    SendParentRequest(ParentRequestType aType);
+    Error    SendChildIdRequest(void);
+    Error    SendOrphanAnnounce(void);
     bool     PrepareAnnounceState(void);
     void     SendAnnounce(uint8_t aChannel, bool aOrphanAnnounce, const Ip6::Address &aDestination);
 #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_ENABLE
-    otError SendLinkMetricsManagementResponse(const Ip6::Address &aDestination, LinkMetrics::LinkMetricsStatus aStatus);
+    Error SendLinkMetricsManagementResponse(const Ip6::Address &aDestination, LinkMetrics::LinkMetricsStatus aStatus);
 #endif
     uint32_t Reattach(void);
 
@@ -1777,7 +1779,7 @@
 #endif
 
 #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_WARN) && (OPENTHREAD_CONFIG_LOG_MLE == 1)
-    static void        LogError(MessageAction aAction, MessageType aType, otError aError);
+    static void        LogError(MessageAction aAction, MessageType aType, Error aError);
     static const char *MessageActionToString(MessageAction aAction);
     static const char *MessageTypeToString(MessageType aType);
     static const char *MessageTypeActionToSuffixString(MessageType aType, MessageAction aAction);
diff --git a/src/core/thread/mle_router.cpp b/src/core/thread/mle_router.cpp
index ae1b03a..06853d4 100644
--- a/src/core/thread/mle_router.cpp
+++ b/src/core/thread/mle_router.cpp
@@ -56,7 +56,7 @@
 
 MleRouter::MleRouter(Instance &aInstance)
     : Mle(aInstance)
-    , mAdvertiseTimer(aInstance, MleRouter::HandleAdvertiseTimer, nullptr, this)
+    , mAdvertiseTrickleTimer(aInstance, MleRouter::HandleAdvertiseTrickleTimer)
     , mAddressSolicit(UriPath::kAddressSolicit, &MleRouter::HandleAddressSolicit, this)
     , mAddressRelease(UriPath::kAddressRelease, &MleRouter::HandleAddressRelease, this)
     , mChildTable(aInstance)
@@ -114,11 +114,11 @@
     return mRouterEligible && IsFullThreadDevice();
 }
 
-otError MleRouter::SetRouterEligible(bool aEligible)
+Error MleRouter::SetRouterEligible(bool aEligible)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(IsFullThreadDevice() || !aEligible, error = OT_ERROR_NOT_CAPABLE);
+    VerifyOrExit(IsFullThreadDevice() || !aEligible, error = kErrorNotCapable);
 
     mRouterEligible = aEligible;
 
@@ -146,13 +146,13 @@
     return error;
 }
 
-otError MleRouter::BecomeRouter(ThreadStatusTlv::Status aStatus)
+Error MleRouter::BecomeRouter(ThreadStatusTlv::Status aStatus)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(!IsDisabled(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(!IsRouter(), error = OT_ERROR_NONE);
-    VerifyOrExit(IsRouterEligible(), error = OT_ERROR_NOT_CAPABLE);
+    VerifyOrExit(!IsDisabled(), error = kErrorInvalidState);
+    VerifyOrExit(!IsRouter(), error = kErrorNone);
+    VerifyOrExit(IsRouterEligible(), error = kErrorNotCapable);
 
     otLogInfoMle("Attempt to become router");
 
@@ -179,17 +179,17 @@
     return error;
 }
 
-otError MleRouter::BecomeLeader(void)
+Error MleRouter::BecomeLeader(void)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     Router * router;
     uint32_t partitionId;
     uint8_t  leaderId;
 
-    VerifyOrExit(!Get<MeshCoP::ActiveDataset>().IsPartiallyComplete(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(!IsDisabled(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(!IsLeader(), error = OT_ERROR_NONE);
-    VerifyOrExit(IsRouterEligible(), error = OT_ERROR_NOT_CAPABLE);
+    VerifyOrExit(!Get<MeshCoP::ActiveDataset>().IsPartiallyComplete(), error = kErrorInvalidState);
+    VerifyOrExit(!IsDisabled(), error = kErrorInvalidState);
+    VerifyOrExit(!IsLeader(), error = kErrorNone);
+    VerifyOrExit(IsRouterEligible(), error = kErrorNotCapable);
 
     mRouterTable.Clear();
 
@@ -225,7 +225,7 @@
     Get<Tmf::TmfAgent>().RemoveResource(mAddressRelease);
     Get<MeshCoP::ActiveDataset>().StopLeader();
     Get<MeshCoP::PendingDataset>().StopLeader();
-    StopAdvertiseTimer();
+    StopAdvertiseTrickleTimer();
     Get<NetworkData::Leader>().Stop();
     Get<ThreadNetif>().UnsubscribeAllRoutersMulticast();
 }
@@ -295,7 +295,8 @@
             RemoveChildren();
         }
 
-        // fall through
+        OT_FALL_THROUGH;
+
     case kAttachBetter:
         if (HasChildren() && mPreviousPartitionIdRouter != mLeaderData.GetPartitionId())
         {
@@ -323,7 +324,7 @@
     mAttachCounter = 0;
     mAttachTimer.Stop();
     mMessageTransmissionTimer.Stop();
-    StopAdvertiseTimer();
+    StopAdvertiseTrickleTimer();
     ResetAdvertiseInterval();
 
     Get<ThreadNetif>().SubscribeAllRoutersMulticast();
@@ -354,7 +355,7 @@
     mAttachCounter = 0;
     mAttachTimer.Stop();
     mMessageTransmissionTimer.Stop();
-    StopAdvertiseTimer();
+    StopAdvertiseTrickleTimer();
     ResetAdvertiseInterval();
     IgnoreError(GetLeaderAloc(mLeaderAloc.GetAddress()));
     Get<ThreadNetif>().AddUnicastAddress(mLeaderAloc);
@@ -385,39 +386,37 @@
     otLogNoteMle("Leader partition id 0x%x", mLeaderData.GetPartitionId());
 }
 
-bool MleRouter::HandleAdvertiseTimer(TrickleTimer &aTimer)
+void MleRouter::HandleAdvertiseTrickleTimer(TrickleTimer &aTimer)
 {
-    return aTimer.GetOwner<MleRouter>().HandleAdvertiseTimer();
+    aTimer.Get<MleRouter>().HandleAdvertiseTrickleTimer();
 }
 
-bool MleRouter::HandleAdvertiseTimer(void)
+void MleRouter::HandleAdvertiseTrickleTimer(void)
 {
-    bool continueTrickle = true;
-
-    VerifyOrExit(IsRouterEligible(), continueTrickle = false);
+    VerifyOrExit(IsRouterEligible(), mAdvertiseTrickleTimer.Stop());
 
     SendAdvertisement();
 
 exit:
-    return continueTrickle;
+    return;
 }
 
-void MleRouter::StopAdvertiseTimer(void)
+void MleRouter::StopAdvertiseTrickleTimer(void)
 {
-    mAdvertiseTimer.Stop();
+    mAdvertiseTrickleTimer.Stop();
 }
 
 void MleRouter::ResetAdvertiseInterval(void)
 {
     VerifyOrExit(IsRouterOrLeader());
 
-    if (!mAdvertiseTimer.IsRunning())
+    if (!mAdvertiseTrickleTimer.IsRunning())
     {
-        mAdvertiseTimer.Start(Time::SecToMsec(kAdvertiseIntervalMin), Time::SecToMsec(kAdvertiseIntervalMax),
-                              TrickleTimer::kModeNormal);
+        mAdvertiseTrickleTimer.Start(TrickleTimer::kModeTrickle, Time::SecToMsec(kAdvertiseIntervalMin),
+                                     Time::SecToMsec(kAdvertiseIntervalMax));
     }
 
-    mAdvertiseTimer.IndicateInconsistent();
+    mAdvertiseTrickleTimer.IndicateInconsistent();
 
 exit:
     return;
@@ -425,7 +424,7 @@
 
 void MleRouter::SendAdvertisement(void)
 {
-    otError      error = OT_ERROR_NONE;
+    Error        error = kErrorNone;
     Ip6::Address destination;
     Message *    message = nullptr;
 
@@ -442,7 +441,7 @@
     // children to detach.
     VerifyOrExit(!mAddressSolicitPending);
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = AppendHeader(*message, kCommandAdvertisement));
     SuccessOrExit(error = AppendSourceAddress(*message));
     SuccessOrExit(error = AppendLeaderData(*message));
@@ -473,18 +472,18 @@
     LogSendError(kTypeAdvertisement, error);
 }
 
-otError MleRouter::SendLinkRequest(Neighbor *aNeighbor)
+Error MleRouter::SendLinkRequest(Neighbor *aNeighbor)
 {
     static const uint8_t detachedTlvs[]      = {Tlv::kAddress16, Tlv::kRoute};
     static const uint8_t routerTlvs[]        = {Tlv::kLinkMargin};
     static const uint8_t validNeighborTlvs[] = {Tlv::kLinkMargin, Tlv::kRoute};
-    otError              error               = OT_ERROR_NONE;
+    Error                error               = kErrorNone;
     Message *            message;
     Ip6::Address         destination;
 
     destination.Clear();
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = AppendHeader(*message, kCommandLinkRequest));
     SuccessOrExit(error = AppendVersion(*message));
 
@@ -560,7 +559,7 @@
 
 void MleRouter::HandleLinkRequest(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, Neighbor *aNeighbor)
 {
-    otError       error    = OT_ERROR_NONE;
+    Error         error    = kErrorNone;
     Neighbor *    neighbor = nullptr;
     Challenge     challenge;
     uint16_t      version;
@@ -570,33 +569,33 @@
 
     Log(kMessageReceive, kTypeLinkRequest, aMessageInfo.GetPeerAddr());
 
-    VerifyOrExit(IsRouterOrLeader(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(IsRouterOrLeader(), error = kErrorInvalidState);
 
-    VerifyOrExit(!IsAttaching(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(!IsAttaching(), error = kErrorInvalidState);
 
     // Challenge
     SuccessOrExit(error = ReadChallenge(aMessage, challenge));
 
     // Version
     SuccessOrExit(error = Tlv::Find<VersionTlv>(aMessage, version));
-    VerifyOrExit(version >= OT_THREAD_VERSION_1_1, error = OT_ERROR_PARSE);
+    VerifyOrExit(version >= OT_THREAD_VERSION_1_1, error = kErrorParse);
 
     // Leader Data
     switch (ReadLeaderData(aMessage, leaderData))
     {
-    case OT_ERROR_NONE:
-        VerifyOrExit(leaderData.GetPartitionId() == mLeaderData.GetPartitionId(), error = OT_ERROR_INVALID_STATE);
+    case kErrorNone:
+        VerifyOrExit(leaderData.GetPartitionId() == mLeaderData.GetPartitionId(), error = kErrorInvalidState);
         break;
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         break;
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
     }
 
     // Source Address
     switch (Tlv::Find<SourceAddressTlv>(aMessage, sourceAddress))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         if (IsActiveRouter(sourceAddress))
         {
             Mac::ExtAddress extAddr;
@@ -604,8 +603,8 @@
             aMessageInfo.GetPeerAddr().GetIid().ConvertToExtAddress(extAddr);
 
             neighbor = mRouterTable.GetRouter(RouterIdFromRloc16(sourceAddress));
-            VerifyOrExit(neighbor != nullptr, error = OT_ERROR_PARSE);
-            VerifyOrExit(!neighbor->IsStateLinkRequest(), error = OT_ERROR_ALREADY);
+            VerifyOrExit(neighbor != nullptr, error = kErrorParse);
+            VerifyOrExit(!neighbor->IsStateLinkRequest(), error = kErrorAlready);
 
             if (!neighbor->IsStateValid())
             {
@@ -624,33 +623,33 @@
 
         break;
 
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         // lack of source address indicates router coming out of reset
         VerifyOrExit(aNeighbor && aNeighbor->IsStateValid() && IsActiveRouter(aNeighbor->GetRloc16()),
-                     error = OT_ERROR_DROP);
+                     error = kErrorDrop);
         neighbor = aNeighbor;
         break;
 
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
     }
 
     // TLV Request
     switch (FindTlvRequest(aMessage, requestedTlvs))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         break;
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         requestedTlvs.mNumTlvs = 0;
         break;
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
     }
 
 #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
     if (neighbor != nullptr)
     {
-        neighbor->SetTimeSyncEnabled(Tlv::Find<TimeRequestTlv>(aMessage, nullptr, 0) == OT_ERROR_NONE);
+        neighbor->SetTimeSyncEnabled(Tlv::Find<TimeRequestTlv>(aMessage, nullptr, 0) == kErrorNone);
     }
 #endif
 
@@ -667,12 +666,12 @@
     LogProcessError(kTypeLinkRequest, error);
 }
 
-otError MleRouter::SendLinkAccept(const Ip6::MessageInfo &aMessageInfo,
-                                  Neighbor *              aNeighbor,
-                                  const RequestedTlvs &   aRequestedTlvs,
-                                  const Challenge &       aChallenge)
+Error MleRouter::SendLinkAccept(const Ip6::MessageInfo &aMessageInfo,
+                                Neighbor *              aNeighbor,
+                                const RequestedTlvs &   aRequestedTlvs,
+                                const Challenge &       aChallenge)
 {
-    otError              error        = OT_ERROR_NONE;
+    Error                error        = kErrorNone;
     static const uint8_t routerTlvs[] = {Tlv::kLinkMargin};
     Message *            message;
     Command              command;
@@ -680,7 +679,7 @@
 
     command = (aNeighbor == nullptr || aNeighbor->IsStateValid()) ? kCommandLinkAccept : kCommandLinkAcceptAndRequest;
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = AppendHeader(*message, command));
     SuccessOrExit(error = AppendVersion(*message));
     SuccessOrExit(error = AppendSourceAddress(*message));
@@ -708,7 +707,7 @@
             break;
 
         case Tlv::kAddress16:
-            VerifyOrExit(aNeighbor != nullptr, error = OT_ERROR_DROP);
+            VerifyOrExit(aNeighbor != nullptr, error = kErrorDrop);
             SuccessOrExit(error = AppendAddress16(*message, aNeighbor->GetRloc16()));
             break;
 
@@ -716,7 +715,7 @@
             break;
 
         default:
-            ExitNow(error = OT_ERROR_DROP);
+            ExitNow(error = kErrorDrop);
         }
     }
 
@@ -761,7 +760,7 @@
                                  uint32_t                aKeySequence,
                                  Neighbor *              aNeighbor)
 {
-    otError error = HandleLinkAccept(aMessage, aMessageInfo, aKeySequence, aNeighbor, false);
+    Error error = HandleLinkAccept(aMessage, aMessageInfo, aKeySequence, aNeighbor, false);
 
     LogProcessError(kTypeLinkAccept, error);
 }
@@ -771,20 +770,20 @@
                                            uint32_t                aKeySequence,
                                            Neighbor *              aNeighbor)
 {
-    otError error = HandleLinkAccept(aMessage, aMessageInfo, aKeySequence, aNeighbor, true);
+    Error error = HandleLinkAccept(aMessage, aMessageInfo, aKeySequence, aNeighbor, true);
 
     LogProcessError(kTypeLinkAcceptAndRequest, error);
 }
 
-otError MleRouter::HandleLinkAccept(const Message &         aMessage,
-                                    const Ip6::MessageInfo &aMessageInfo,
-                                    uint32_t                aKeySequence,
-                                    Neighbor *              aNeighbor,
-                                    bool                    aRequest)
+Error MleRouter::HandleLinkAccept(const Message &         aMessage,
+                                  const Ip6::MessageInfo &aMessageInfo,
+                                  uint32_t                aKeySequence,
+                                  Neighbor *              aNeighbor,
+                                  bool                    aRequest)
 {
     static const uint8_t dataRequestTlvs[] = {Tlv::kNetworkData};
 
-    otError         error = OT_ERROR_NONE;
+    Error           error = kErrorNone;
     Router *        router;
     Neighbor::State neighborState;
     Mac::ExtAddress extAddr;
@@ -805,7 +804,7 @@
     Log(kMessageReceive, aRequest ? kTypeLinkAcceptAndRequest : kTypeLinkAccept, aMessageInfo.GetPeerAddr(),
         sourceAddress);
 
-    VerifyOrExit(IsActiveRouter(sourceAddress), error = OT_ERROR_PARSE);
+    VerifyOrExit(IsActiveRouter(sourceAddress), error = kErrorParse);
 
     routerId      = RouterIdFromRloc16(sourceAddress);
     router        = mRouterTable.GetRouter(routerId);
@@ -818,17 +817,19 @@
     switch (neighborState)
     {
     case Neighbor::kStateLinkRequest:
-        VerifyOrExit(response.Matches(router->GetChallenge(), router->GetChallengeSize()), error = OT_ERROR_SECURITY);
+        VerifyOrExit(response.Matches(router->GetChallenge(), router->GetChallengeSize()), error = kErrorSecurity);
         break;
 
     case Neighbor::kStateInvalid:
-        VerifyOrExit((mChallengeTimeout > 0) && (response == mChallenge), error = OT_ERROR_SECURITY);
+        VerifyOrExit((mChallengeTimeout > 0) && (response == mChallenge), error = kErrorSecurity);
+
+        OT_FALL_THROUGH;
 
     case Neighbor::kStateValid:
         break;
 
     default:
-        ExitNow(error = OT_ERROR_SECURITY);
+        ExitNow(error = kErrorSecurity);
     }
 
     // Remove stale neighbors
@@ -839,7 +840,7 @@
 
     // Version
     SuccessOrExit(error = Tlv::Find<VersionTlv>(aMessage, version));
-    VerifyOrExit(version >= OT_THREAD_VERSION_1_1, error = OT_ERROR_PARSE);
+    VerifyOrExit(version >= OT_THREAD_VERSION_1_1, error = kErrorParse);
 
     // Link and MLE Frame Counters
     SuccessOrExit(error = ReadFrameCounters(aMessage, linkFrameCounter, mleFrameCounter));
@@ -847,16 +848,16 @@
     // Link Margin
     switch (Tlv::Find<LinkMarginTlv>(aMessage, linkMargin))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         break;
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         // Link Margin TLV may be skipped in Router Synchronization process after Reset
-        VerifyOrExit(IsDetached(), error = OT_ERROR_NOT_FOUND);
+        VerifyOrExit(IsDetached(), error = kErrorNotFound);
         // Wait for an MLE Advertisement to establish a routing cost to the neighbor
         linkMargin = 0;
         break;
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
     }
 
     switch (mRole)
@@ -868,7 +869,7 @@
     case kRoleDetached:
         // Address16
         SuccessOrExit(error = Tlv::Find<Address16Tlv>(aMessage, address16));
-        VerifyOrExit(GetRloc16() == address16, error = OT_ERROR_DROP);
+        VerifyOrExit(GetRloc16() == address16, error = kErrorDrop);
 
         // Leader Data
         SuccessOrExit(error = ReadLeaderData(aMessage, leaderData));
@@ -876,7 +877,7 @@
 
         // Route
         SuccessOrExit(error = Tlv::FindTlv(aMessage, Tlv::kRoute, sizeof(route), route));
-        VerifyOrExit(route.IsValid(), error = OT_ERROR_PARSE);
+        VerifyOrExit(route.IsValid(), error = kErrorParse);
         mRouterTable.Clear();
         SuccessOrExit(error = ProcessRouteTlv(route));
         router = mRouterTable.GetRouter(routerId);
@@ -918,9 +919,9 @@
         }
 
         // Route (optional)
-        if (Tlv::FindTlv(aMessage, route) == OT_ERROR_NONE)
+        if (Tlv::FindTlv(aMessage, route) == kErrorNone)
         {
-            VerifyOrExit(route.IsValid(), error = OT_ERROR_PARSE);
+            VerifyOrExit(route.IsValid(), error = kErrorParse);
             SuccessOrExit(error = ProcessRouteTlv(route));
             UpdateRoutes(route, routerId);
             // need to update router after ProcessRouteTlv
@@ -945,6 +946,7 @@
     router->SetLinkAckFrameCounter(linkFrameCounter);
     router->SetMleFrameCounter(mleFrameCounter);
     router->SetLastHeard(TimerMilli::GetNow());
+    router->SetVersion(static_cast<uint8_t>(version));
     router->SetDeviceMode(DeviceMode(DeviceMode::kModeFullThreadDevice | DeviceMode::kModeRxOnWhenIdle |
                                      DeviceMode::kModeFullNetworkData));
     router->GetLinkInfo().Clear();
@@ -967,13 +969,13 @@
         // TLV Request
         switch (FindTlvRequest(aMessage, requestedTlvs))
         {
-        case OT_ERROR_NONE:
+        case kErrorNone:
             break;
-        case OT_ERROR_NOT_FOUND:
+        case kErrorNotFound:
             requestedTlvs.mNumTlvs = 0;
             break;
         default:
-            ExitNow(error = OT_ERROR_PARSE);
+            ExitNow(error = kErrorParse);
         }
 
         SuccessOrExit(error = SendLinkAccept(aMessageInfo, router, requestedTlvs, challenge));
@@ -1025,11 +1027,11 @@
     return rval;
 }
 
-otError MleRouter::SetRouterSelectionJitter(uint8_t aRouterJitter)
+Error MleRouter::SetRouterSelectionJitter(uint8_t aRouterJitter)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(aRouterJitter > 0, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aRouterJitter > 0, error = kErrorInvalidArgs);
 
     mRouterSelectionJitter = aRouterJitter;
 
@@ -1037,16 +1039,16 @@
     return error;
 }
 
-otError MleRouter::ProcessRouteTlv(const RouteTlv &aRoute)
+Error MleRouter::ProcessRouteTlv(const RouteTlv &aRoute)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     mRouterTable.UpdateRouterIdSet(aRoute.GetRouterIdSequence(), aRoute.GetRouterIdMask());
 
     if (IsRouter() && !mRouterTable.IsAllocated(mRouterId))
     {
         IgnoreError(BecomeDetached());
-        error = OT_ERROR_NO_ROUTE;
+        error = kErrorNoRoute;
     }
 
     return error;
@@ -1119,11 +1121,9 @@
     return rval;
 }
 
-otError MleRouter::HandleAdvertisement(const Message &         aMessage,
-                                       const Ip6::MessageInfo &aMessageInfo,
-                                       Neighbor *              aNeighbor)
+Error MleRouter::HandleAdvertisement(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, Neighbor *aNeighbor)
 {
-    otError               error    = OT_ERROR_NONE;
+    Error                 error    = kErrorNone;
     const ThreadLinkInfo *linkInfo = aMessageInfo.GetThreadLinkInfo();
     uint8_t linkMargin = LinkQualityInfo::ConvertRssToLinkMargin(Get<Mac::Mac>().GetNoiseFloor(), linkInfo->GetRss());
     Mac::ExtAddress extAddr;
@@ -1144,9 +1144,9 @@
     SuccessOrExit(error = ReadLeaderData(aMessage, leaderData));
 
     // Route Data (optional)
-    if (Tlv::FindTlv(aMessage, route) == OT_ERROR_NONE)
+    if (Tlv::FindTlv(aMessage, route) == kErrorNone)
     {
-        VerifyOrExit(route.IsValid(), error = OT_ERROR_PARSE);
+        VerifyOrExit(route.IsValid(), error = kErrorParse);
     }
     else
     {
@@ -1160,13 +1160,13 @@
     {
         otLogNoteMle("Different partition (peer:%u, local:%u)", partitionId, mLeaderData.GetPartitionId());
 
-        VerifyOrExit(linkMargin >= OPENTHREAD_CONFIG_MLE_PARTITION_MERGE_MARGIN_MIN, error = OT_ERROR_LINK_MARGIN_LOW);
+        VerifyOrExit(linkMargin >= OPENTHREAD_CONFIG_MLE_PARTITION_MERGE_MARGIN_MIN, error = kErrorLinkMarginLow);
 
         if (route.IsValid() && IsFullThreadDevice() && (mPreviousPartitionIdTimeout > 0) &&
             (partitionId == mPreviousPartitionId))
         {
             VerifyOrExit((static_cast<int8_t>(route.GetRouterIdSequence() - mPreviousPartitionRouterIdSequence) > 0),
-                         error = OT_ERROR_DROP);
+                         error = kErrorDrop);
         }
 
         if (IsChild() && (aNeighbor == &mParent || !IsFullThreadDevice()))
@@ -1184,7 +1184,7 @@
             IgnoreError(BecomeChild(kAttachBetter));
         }
 
-        ExitNow(error = OT_ERROR_DROP);
+        ExitNow(error = kErrorDrop);
     }
     else if (leaderData.GetLeaderRouterId() != GetLeaderId())
     {
@@ -1194,7 +1194,7 @@
         {
             otLogInfoMle("Leader ID mismatch");
             IgnoreError(BecomeDetached());
-            error = OT_ERROR_DROP;
+            error = kErrorDrop;
         }
 
         ExitNow();
@@ -1267,7 +1267,7 @@
             if (mParent.GetRloc16() != sourceAddress)
             {
                 IgnoreError(BecomeDetached());
-                ExitNow(error = OT_ERROR_NO_ROUTE);
+                ExitNow(error = kErrorNoRoute);
             }
 
             if (IsFullThreadDevice())
@@ -1330,7 +1330,7 @@
                 router->SetLastHeard(TimerMilli::GetNow());
                 router->SetState(Neighbor::kStateLinkRequest);
                 IgnoreError(SendLinkRequest(router));
-                ExitNow(error = OT_ERROR_NO_ROUTE);
+                ExitNow(error = kErrorNoRoute);
             }
         }
 
@@ -1360,7 +1360,7 @@
             mRouterSelectionJitterTimeout = 1 + Random::NonCrypto::GetUint8InRange(0, mRouterSelectionJitter);
         }
 
-        // fall through
+        OT_FALL_THROUGH;
 
     case kRoleLeader:
         router = mRouterTable.GetRouter(routerId);
@@ -1377,7 +1377,7 @@
             router->SetLastHeard(TimerMilli::GetNow());
             router->SetState(Neighbor::kStateLinkRequest);
             IgnoreError(SendLinkRequest(router));
-            ExitNow(error = OT_ERROR_NO_ROUTE);
+            ExitNow(error = kErrorNoRoute);
         }
 
         router->SetLastHeard(TimerMilli::GetNow());
@@ -1549,7 +1549,7 @@
 
 void MleRouter::HandleParentRequest(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError         error = OT_ERROR_NONE;
+    Error           error = kErrorNone;
     Mac::ExtAddress extAddr;
     uint16_t        version;
     uint8_t         scanMask;
@@ -1559,12 +1559,12 @@
 
     Log(kMessageReceive, kTypeParentRequest, aMessageInfo.GetPeerAddr());
 
-    VerifyOrExit(IsRouterEligible(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(IsRouterEligible(), error = kErrorInvalidState);
 
     // A Router/REED MUST NOT send an MLE Parent Response if:
 
     // 0. It is detached or attempting to another partition
-    VerifyOrExit(!IsDetached() && !IsAttaching(), error = OT_ERROR_DROP);
+    VerifyOrExit(!IsDetached() && !IsAttaching(), error = kErrorDrop);
 
     // 1. It has no available Child capacity (if Max Child Count minus
     // Child Count would be equal to zero)
@@ -1573,7 +1573,7 @@
     // 2. It is disconnected from its Partition (that is, it has not
     // received an updated ID sequence number within LEADER_TIMEOUT
     // seconds)
-    VerifyOrExit(mRouterTable.GetLeaderAge() < mNetworkIdTimeout, error = OT_ERROR_DROP);
+    VerifyOrExit(mRouterTable.GetLeaderAge() < mNetworkIdTimeout, error = kErrorDrop);
 
     // 3. Its current routing path cost to the Leader is infinite.
     leader = mRouterTable.GetLeader();
@@ -1582,7 +1582,7 @@
     VerifyOrExit(IsLeader() || GetLinkCost(GetLeaderId()) < kMaxRouteCost ||
                      (IsChild() && leader->GetCost() + 1 < kMaxRouteCost) ||
                      (leader->GetCost() + GetLinkCost(leader->GetNextHop()) < kMaxRouteCost),
-                 error = OT_ERROR_DROP);
+                 error = kErrorDrop);
 
     // 4. It is a REED and there are already `kMaxRouters` active routers in
     // the network (because Leader would reject any further address solicit).
@@ -1592,7 +1592,7 @@
 
     // Version
     SuccessOrExit(error = Tlv::Find<VersionTlv>(aMessage, version));
-    VerifyOrExit(version >= OT_THREAD_VERSION_1_1, error = OT_ERROR_PARSE);
+    VerifyOrExit(version >= OT_THREAD_VERSION_1_1, error = kErrorParse);
 
     // Scan Mask
     SuccessOrExit(error = Tlv::Find<ScanMaskTlv>(aMessage, scanMask));
@@ -1605,7 +1605,7 @@
 
     case kRoleChild:
         VerifyOrExit(ScanMaskTlv::IsEndDeviceFlagSet(scanMask));
-        VerifyOrExit(mRouterTable.GetActiveRouterCount() < kMaxRouters, error = OT_ERROR_DROP);
+        VerifyOrExit(mRouterTable.GetActiveRouterCount() < kMaxRouters, error = kErrorDrop);
         break;
 
     case kRoleRouter:
@@ -1621,7 +1621,7 @@
 
     if (child == nullptr)
     {
-        VerifyOrExit((child = mChildTable.GetNewChild()) != nullptr, error = OT_ERROR_NO_BUFS);
+        VerifyOrExit((child = mChildTable.GetNewChild()) != nullptr, error = kErrorNoBufs);
 
         // MAC Address
         child->SetExtAddress(extAddr);
@@ -1630,12 +1630,12 @@
         child->ResetLinkFailures();
         child->SetState(Neighbor::kStateParentRequest);
 #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
-        child->SetTimeSyncEnabled(Tlv::Find<TimeRequestTlv>(aMessage, nullptr, 0) == OT_ERROR_NONE);
+        child->SetTimeSyncEnabled(Tlv::Find<TimeRequestTlv>(aMessage, nullptr, 0) == kErrorNone);
 #endif
     }
     else if (TimerMilli::GetNow() - child->GetLastHeard() < kParentRequestRouterTimeout - kParentRequestDuplicateMargin)
     {
-        ExitNow(error = OT_ERROR_DUPLICATED);
+        ExitNow(error = kErrorDuplicated);
     }
 
     if (!child->IsStateValidOrRestoring())
@@ -1687,7 +1687,7 @@
             // If no Backbone Router service after jitter, try to register its own Backbone Router Service.
             if (!Get<BackboneRouter::Leader>().HasPrimary())
             {
-                if (Get<BackboneRouter::Local>().AddService() == OT_ERROR_NONE)
+                if (Get<BackboneRouter::Local>().AddService() == kErrorNone)
                 {
                     Get<NetworkData::Notifier>().HandleServerDataUpdated();
                 }
@@ -1725,19 +1725,18 @@
                 InformPreviousChannel();
             }
 
-            if (!mAdvertiseTimer.IsRunning())
+            if (!mAdvertiseTrickleTimer.IsRunning())
             {
                 SendAdvertisement();
 
-                mAdvertiseTimer.Start(Time::SecToMsec(kReedAdvertiseInterval),
-                                      Time::SecToMsec(kReedAdvertiseInterval + kReedAdvertiseJitter),
-                                      TrickleTimer::kModePlainTimer);
+                mAdvertiseTrickleTimer.Start(TrickleTimer::kModePlainTimer, Time::SecToMsec(kReedAdvertiseInterval),
+                                             Time::SecToMsec(kReedAdvertiseInterval + kReedAdvertiseJitter));
             }
 
             ExitNow();
         }
 
-        // fall through
+        OT_FALL_THROUGH;
 
     case kRoleRouter:
         // verify path to leader
@@ -1786,7 +1785,7 @@
             OT_UNREACHABLE_CODE(break);
         }
 
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
         if (child.IsCslSynchronized() &&
             TimerMilli::GetNow() - child.GetCslLastHeard() >= Time::SecToMsec(child.GetCslTimeout()))
         {
@@ -1887,12 +1886,12 @@
 
 void MleRouter::SendParentResponse(Child *aChild, const Challenge &aChallenge, bool aRoutersOnlyRequest)
 {
-    otError      error = OT_ERROR_NONE;
+    Error        error = kErrorNone;
     Ip6::Address destination;
     Message *    message;
     uint16_t     delay;
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     message->SetDirectTransmission();
 
     SuccessOrExit(error = AppendHeader(*message, kCommandParentResponse));
@@ -1952,11 +1951,11 @@
 }
 
 #if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
-otError MleRouter::SetMaxChildIpAddresses(uint8_t aMaxIpAddresses)
+Error MleRouter::SetMaxChildIpAddresses(uint8_t aMaxIpAddresses)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(aMaxIpAddresses <= OPENTHREAD_CONFIG_MLE_IP_ADDRS_PER_CHILD, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aMaxIpAddresses <= OPENTHREAD_CONFIG_MLE_IP_ADDRS_PER_CHILD, error = kErrorInvalidArgs);
 
     mMaxChildIpAddresses = aMaxIpAddresses;
 
@@ -1965,9 +1964,9 @@
 }
 #endif
 
-otError MleRouter::UpdateChildAddresses(const Message &aMessage, uint16_t aOffset, Child &aChild)
+Error MleRouter::UpdateChildAddresses(const Message &aMessage, uint16_t aOffset, Child &aChild)
 {
-    otError                  error = OT_ERROR_NONE;
+    Error                    error = kErrorNone;
     AddressRegistrationEntry entry;
     Ip6::Address             address;
     Lowpan::Context          context;
@@ -1988,7 +1987,7 @@
 #endif
 
     SuccessOrExit(error = aMessage.Read(aOffset, tlv));
-    VerifyOrExit(tlv.GetLength() <= (aMessage.GetLength() - aOffset - sizeof(tlv)), error = OT_ERROR_PARSE);
+    VerifyOrExit(tlv.GetLength() <= (aMessage.GetLength() - aOffset - sizeof(tlv)), error = kErrorParse);
 
     offset = aOffset + sizeof(tlv);
     end    = offset + tlv.GetLength();
@@ -2035,7 +2034,7 @@
 
         if (entry.IsCompressed())
         {
-            if (Get<NetworkData::Leader>().GetContext(entry.GetContextId(), context) != OT_ERROR_NONE)
+            if (Get<NetworkData::Leader>().GetContext(entry.GetContextId(), context) != kErrorNone)
             {
                 otLogWarnMle("Failed to get context %d for compressed address from child 0x%04x", entry.GetContextId(),
                              aChild.GetRloc16());
@@ -2074,7 +2073,7 @@
         if (mMaxChildIpAddresses > 0 && storedCount >= mMaxChildIpAddresses)
         {
             // Skip remaining address registration entries but keep logging skipped addresses.
-            error = OT_ERROR_NO_BUFS;
+            error = kErrorNoBufs;
         }
         else
 #endif
@@ -2085,7 +2084,7 @@
             error = aChild.AddIp6Address(address);
         }
 
-        if (error == OT_ERROR_NONE)
+        if (error == kErrorNone)
         {
             storedCount++;
             otLogInfoMle("Child 0x%04x IPv6 address[%d]=%s", aChild.GetRloc16(), storedCount,
@@ -2093,7 +2092,7 @@
         }
         else
         {
-            otLogWarnMle("Error %s adding IPv6 address %s to child 0x%04x", otThreadErrorToString(error),
+            otLogWarnMle("Error %s adding IPv6 address %s to child 0x%04x", ErrorToString(error),
                          address.ToString().AsCString(), aChild.GetRloc16());
         }
 
@@ -2147,7 +2146,7 @@
                      registeredCount, (registeredCount == 1) ? "" : "es", storedCount, (storedCount == 1) ? "" : "es");
     }
 
-    error = OT_ERROR_NONE;
+    error = kErrorNone;
 
 exit:
     return error;
@@ -2157,7 +2156,7 @@
                                      const Ip6::MessageInfo &aMessageInfo,
                                      uint32_t                aKeySequence)
 {
-    otError             error = OT_ERROR_NONE;
+    Error               error = kErrorNone;
     Mac::ExtAddress     extAddr;
     uint16_t            version;
     Challenge           response;
@@ -2176,24 +2175,24 @@
 
     Log(kMessageReceive, kTypeChildIdRequest, aMessageInfo.GetPeerAddr());
 
-    VerifyOrExit(IsRouterEligible(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(IsRouterEligible(), error = kErrorInvalidState);
 
     // only process message when operating as a child, router, or leader
-    VerifyOrExit(IsAttached(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(IsAttached(), error = kErrorInvalidState);
 
     // Find Child
     aMessageInfo.GetPeerAddr().GetIid().ConvertToExtAddress(extAddr);
 
     child = mChildTable.FindChild(extAddr, Child::kInStateAnyExceptInvalid);
-    VerifyOrExit(child != nullptr, error = OT_ERROR_ALREADY);
+    VerifyOrExit(child != nullptr, error = kErrorAlready);
 
     // Version
     SuccessOrExit(error = Tlv::Find<VersionTlv>(aMessage, version));
-    VerifyOrExit(version >= OT_THREAD_VERSION_1_1, error = OT_ERROR_PARSE);
+    VerifyOrExit(version >= OT_THREAD_VERSION_1_1, error = kErrorParse);
 
     // Response
     SuccessOrExit(error = ReadResponse(aMessage, response));
-    VerifyOrExit(response.Matches(child->GetChallenge(), child->GetChallengeSize()), error = OT_ERROR_SECURITY);
+    VerifyOrExit(response.Matches(child->GetChallenge(), child->GetChallengeSize()), error = kErrorSecurity);
 
     // Remove existing MLE messages
     Get<MeshForwarder>().RemoveMessages(*child, Message::kSubTypeMleGeneral);
@@ -2213,22 +2212,22 @@
 
     // TLV Request
     SuccessOrExit(error = FindTlvRequest(aMessage, requestedTlvs));
-    VerifyOrExit(requestedTlvs.mNumTlvs <= Child::kMaxRequestTlvs, error = OT_ERROR_PARSE);
+    VerifyOrExit(requestedTlvs.mNumTlvs <= Child::kMaxRequestTlvs, error = kErrorParse);
 
     // Active Timestamp
     activeTimestamp.SetLength(0);
 
-    if (Tlv::FindTlv(aMessage, activeTimestamp) == OT_ERROR_NONE)
+    if (Tlv::FindTlv(aMessage, activeTimestamp) == kErrorNone)
     {
-        VerifyOrExit(activeTimestamp.IsValid(), error = OT_ERROR_PARSE);
+        VerifyOrExit(activeTimestamp.IsValid(), error = kErrorParse);
     }
 
     // Pending Timestamp
     pendingTimestamp.SetLength(0);
 
-    if (Tlv::FindTlv(aMessage, pendingTimestamp) == OT_ERROR_NONE)
+    if (Tlv::FindTlv(aMessage, pendingTimestamp) == kErrorNone)
     {
-        VerifyOrExit(pendingTimestamp.IsValid(), error = OT_ERROR_PARSE);
+        VerifyOrExit(pendingTimestamp.IsValid(), error = kErrorParse);
     }
 
     if (!mode.IsFullThreadDevice())
@@ -2321,7 +2320,7 @@
 {
     static const uint8_t kMaxResponseTlvs = 10;
 
-    otError         error = OT_ERROR_NONE;
+    Error           error = kErrorNone;
     Mac::ExtAddress extAddr;
     uint8_t         modeBitmask;
     DeviceMode      mode;
@@ -2345,14 +2344,14 @@
     // Challenge
     switch (ReadChallenge(aMessage, challenge))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         tlvs[tlvslength++] = Tlv::kResponse;
         break;
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         challenge.mLength = 0;
         break;
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
     }
 
     // Find Child
@@ -2390,7 +2389,7 @@
     }
 
     // Ip6 Address TLV
-    if (Tlv::FindTlvOffset(aMessage, Tlv::kAddressRegistration, addressRegistrationOffset) == OT_ERROR_NONE)
+    if (Tlv::FindTlvOffset(aMessage, Tlv::kAddressRegistration, addressRegistrationOffset) == kErrorNone)
     {
         SuccessOrExit(error = UpdateChildAddresses(aMessage, addressRegistrationOffset, *child));
         tlvs[tlvslength++] = Tlv::kAddressRegistration;
@@ -2399,17 +2398,17 @@
     // Leader Data
     switch (ReadLeaderData(aMessage, leaderData))
     {
-    case OT_ERROR_NONE:
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNone:
+    case kErrorNotFound:
         break;
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
     }
 
     // Timeout
     switch (Tlv::Find<TimeoutTlv>(aMessage, timeout))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         if (child->GetTimeout() != timeout)
         {
             child->SetTimeout(timeout);
@@ -2419,18 +2418,18 @@
         tlvs[tlvslength++] = Tlv::kTimeout;
         break;
 
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         break;
 
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
     }
 
     // TLV Request
     switch (FindTlvRequest(aMessage, requestedTlvs))
     {
-    case OT_ERROR_NONE:
-        VerifyOrExit(requestedTlvs.mNumTlvs <= (kMaxResponseTlvs - tlvslength), error = OT_ERROR_PARSE);
+    case kErrorNone:
+        VerifyOrExit(requestedTlvs.mNumTlvs <= (kMaxResponseTlvs - tlvslength), error = kErrorParse);
         for (uint8_t i = 0; i < requestedTlvs.mNumTlvs; i++)
         {
             // Skip LeaderDataTlv since it is already included by default.
@@ -2440,24 +2439,24 @@
             }
         }
         break;
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         break;
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
     }
 
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     if (child->IsCslSynchronized())
     {
         CslChannelTlv cslChannel;
         uint32_t      cslTimeout;
 
-        if (Tlv::Find<CslTimeoutTlv>(aMessage, cslTimeout) == OT_ERROR_NONE)
+        if (Tlv::Find<CslTimeoutTlv>(aMessage, cslTimeout) == kErrorNone)
         {
             child->SetCslTimeout(cslTimeout);
         }
 
-        if (Tlv::FindTlv(aMessage, cslChannel) == OT_ERROR_NONE)
+        if (Tlv::FindTlv(aMessage, cslChannel) == kErrorNone)
         {
             child->SetCslChannel(static_cast<uint8_t>(cslChannel.GetChannel()));
         }
@@ -2467,7 +2466,7 @@
             child->SetCslChannel(0);
         }
     }
-#endif // !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#endif // OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
 
     child->SetLastHeard(TimerMilli::GetNow());
 
@@ -2478,7 +2477,7 @@
 
         childDidChange = true;
 
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
         if (child->IsRxOnWhenIdle())
         {
             // Clear CSL synchronization state
@@ -2522,7 +2521,7 @@
                                           uint32_t                aKeySequence,
                                           Neighbor *              aNeighbor)
 {
-    otError    error = OT_ERROR_NONE;
+    Error      error = kErrorNone;
     uint16_t   sourceAddress;
     uint32_t   timeout;
     Challenge  response;
@@ -2536,7 +2535,7 @@
     if ((aNeighbor == nullptr) || IsActiveRouter(aNeighbor->GetRloc16()))
     {
         Log(kMessageReceive, kTypeChildUpdateResponseOfUnknownChild, aMessageInfo.GetPeerAddr());
-        ExitNow(error = OT_ERROR_NOT_FOUND);
+        ExitNow(error = kErrorNotFound);
     }
 
     child = static_cast<Child *>(aNeighbor);
@@ -2544,14 +2543,14 @@
     // Response
     switch (ReadResponse(aMessage, response))
     {
-    case OT_ERROR_NONE:
-        VerifyOrExit(response.Matches(child->GetChallenge(), child->GetChallengeSize()), error = OT_ERROR_SECURITY);
+    case kErrorNone:
+        VerifyOrExit(response.Matches(child->GetChallenge(), child->GetChallengeSize()), error = kErrorSecurity);
         break;
-    case OT_ERROR_NOT_FOUND:
-        VerifyOrExit(child->IsStateValid(), error = OT_ERROR_SECURITY);
+    case kErrorNotFound:
+        VerifyOrExit(child->IsStateValid(), error = kErrorSecurity);
         break;
     default:
-        ExitNow(error = OT_ERROR_NONE);
+        ExitNow(error = kErrorNone);
     }
 
     Log(kMessageReceive, kTypeChildUpdateResponseOfChild, aMessageInfo.GetPeerAddr(), child->GetRloc16());
@@ -2559,7 +2558,7 @@
     // Source Address
     switch (Tlv::Find<SourceAddressTlv>(aMessage, sourceAddress))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         if (child->GetRloc16() != sourceAddress)
         {
             RemoveNeighbor(*child);
@@ -2568,65 +2567,65 @@
 
         break;
 
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         break;
 
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
     }
 
     // Status
     switch (Tlv::Find<ThreadStatusTlv>(aMessage, status))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         VerifyOrExit(status != StatusTlv::kError, RemoveNeighbor(*child));
         break;
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         break;
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
     }
 
     // Link-Layer Frame Counter
 
     switch (Tlv::Find<LinkFrameCounterTlv>(aMessage, linkFrameCounter))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         child->GetLinkFrameCounters().SetAll(linkFrameCounter);
         child->SetLinkAckFrameCounter(linkFrameCounter);
         break;
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         break;
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
     }
 
     // MLE Frame Counter
     switch (Tlv::Find<MleFrameCounterTlv>(aMessage, mleFrameCounter))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         child->SetMleFrameCounter(mleFrameCounter);
         break;
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         break;
     default:
-        ExitNow(error = OT_ERROR_NONE);
+        ExitNow(error = kErrorNone);
     }
 
     // Timeout
     switch (Tlv::Find<TimeoutTlv>(aMessage, timeout))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         child->SetTimeout(timeout);
         break;
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         break;
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
     }
 
     // Ip6 Address
-    if (Tlv::FindTlvOffset(aMessage, Tlv::kAddressRegistration, addressRegistrationOffset) == OT_ERROR_NONE)
+    if (Tlv::FindTlvOffset(aMessage, Tlv::kAddressRegistration, addressRegistrationOffset) == kErrorNone)
     {
         SuccessOrExit(error = UpdateChildAddresses(aMessage, addressRegistrationOffset, *child));
     }
@@ -2634,7 +2633,7 @@
     // Leader Data
     switch (ReadLeaderData(aMessage, leaderData))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         if (child->IsFullNetworkData())
         {
             child->SetNetworkDataVersion(leaderData.GetDataVersion());
@@ -2644,10 +2643,10 @@
             child->SetNetworkDataVersion(leaderData.GetStableDataVersion());
         }
         break;
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         break;
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
     }
 
     SetChildStateToValid(*child);
@@ -2663,7 +2662,7 @@
                                   const Ip6::MessageInfo &aMessageInfo,
                                   const Neighbor *        aNeighbor)
 {
-    otError             error = OT_ERROR_NONE;
+    Error               error = kErrorNone;
     RequestedTlvs       requestedTlvs;
     ActiveTimestampTlv  activeTimestamp;
     PendingTimestampTlv pendingTimestamp;
@@ -2672,26 +2671,26 @@
 
     Log(kMessageReceive, kTypeDataRequest, aMessageInfo.GetPeerAddr());
 
-    VerifyOrExit(aNeighbor && aNeighbor->IsStateValid(), error = OT_ERROR_SECURITY);
+    VerifyOrExit(aNeighbor && aNeighbor->IsStateValid(), error = kErrorSecurity);
 
     // TLV Request
     SuccessOrExit(error = FindTlvRequest(aMessage, requestedTlvs));
-    VerifyOrExit(requestedTlvs.mNumTlvs <= sizeof(tlvs), error = OT_ERROR_PARSE);
+    VerifyOrExit(requestedTlvs.mNumTlvs <= sizeof(tlvs), error = kErrorParse);
 
     // Active Timestamp
     activeTimestamp.SetLength(0);
 
-    if (Tlv::FindTlv(aMessage, activeTimestamp) == OT_ERROR_NONE)
+    if (Tlv::FindTlv(aMessage, activeTimestamp) == kErrorNone)
     {
-        VerifyOrExit(activeTimestamp.IsValid(), error = OT_ERROR_PARSE);
+        VerifyOrExit(activeTimestamp.IsValid(), error = kErrorParse);
     }
 
     // Pending Timestamp
     pendingTimestamp.SetLength(0);
 
-    if (Tlv::FindTlv(aMessage, pendingTimestamp) == OT_ERROR_NONE)
+    if (Tlv::FindTlv(aMessage, pendingTimestamp) == kErrorNone)
     {
-        VerifyOrExit(pendingTimestamp.IsValid(), error = OT_ERROR_PARSE);
+        VerifyOrExit(pendingTimestamp.IsValid(), error = kErrorParse);
     }
 
     memset(tlvs, Tlv::kInvalid, sizeof(tlvs));
@@ -2797,7 +2796,7 @@
 
 void MleRouter::HandleDiscoveryRequest(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError                      error = OT_ERROR_NONE;
+    Error                        error = kErrorNone;
     Tlv                          tlv;
     MeshCoP::Tlv                 meshcopTlv;
     MeshCoP::DiscoveryRequestTlv discoveryRequest;
@@ -2810,10 +2809,10 @@
     discoveryRequest.SetLength(0);
 
     // only Routers and REEDs respond
-    VerifyOrExit(IsRouterEligible(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(IsRouterEligible(), error = kErrorInvalidState);
 
     // find MLE Discovery TLV
-    VerifyOrExit(Tlv::FindTlvOffset(aMessage, Tlv::kDiscovery, offset) == OT_ERROR_NONE, error = OT_ERROR_PARSE);
+    VerifyOrExit(Tlv::FindTlvOffset(aMessage, Tlv::kDiscovery, offset) == kErrorNone, error = kErrorParse);
     IgnoreError(aMessage.Read(offset, tlv));
 
     offset += sizeof(tlv);
@@ -2827,13 +2826,13 @@
         {
         case MeshCoP::Tlv::kDiscoveryRequest:
             IgnoreError(aMessage.Read(offset, discoveryRequest));
-            VerifyOrExit(discoveryRequest.IsValid(), error = OT_ERROR_PARSE);
+            VerifyOrExit(discoveryRequest.IsValid(), error = kErrorParse);
 
             break;
 
         case MeshCoP::Tlv::kExtendedPanId:
             SuccessOrExit(error = Tlv::Read<MeshCoP::ExtendedPanIdTlv>(aMessage, offset, extPanId));
-            VerifyOrExit(Get<Mac::Mac>().GetExtendedPanId() != extPanId, error = OT_ERROR_DROP);
+            VerifyOrExit(Get<Mac::Mac>().GetExtendedPanId() != extPanId, error = kErrorDrop);
 
             break;
 
@@ -2866,7 +2865,7 @@
             else // if steering data is not set out of band, fall back to network data
 #endif
             {
-                VerifyOrExit(Get<NetworkData::Leader>().IsJoiningEnabled(), error = OT_ERROR_SECURITY);
+                VerifyOrExit(Get<NetworkData::Leader>().IsJoiningEnabled(), error = kErrorSecurity);
             }
         }
     }
@@ -2877,9 +2876,9 @@
     LogProcessError(kTypeDiscoveryRequest, error);
 }
 
-otError MleRouter::SendDiscoveryResponse(const Ip6::Address &aDestination, const Message &aDiscoverRequestMessage)
+Error MleRouter::SendDiscoveryResponse(const Ip6::Address &aDestination, const Message &aDiscoverRequestMessage)
 {
-    otError                       error = OT_ERROR_NONE;
+    Error                         error = kErrorNone;
     Message *                     message;
     uint16_t                      startOffset;
     Tlv                           tlv;
@@ -2887,7 +2886,7 @@
     MeshCoP::NetworkNameTlv       networkName;
     uint16_t                      delay;
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     message->SetSubType(Message::kSubTypeMleDiscoverResponse);
     message->SetPanId(aDiscoverRequestMessage.GetPanId());
 #if OPENTHREAD_CONFIG_MULTI_RADIO
@@ -2968,13 +2967,13 @@
     return error;
 }
 
-otError MleRouter::SendChildIdResponse(Child &aChild)
+Error MleRouter::SendChildIdResponse(Child &aChild)
 {
-    otError      error = OT_ERROR_NONE;
+    Error        error = kErrorNone;
     Ip6::Address destination;
     Message *    message;
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = AppendHeader(*message, kCommandChildIdResponse));
     SuccessOrExit(error = AppendSourceAddress(*message));
     SuccessOrExit(error = AppendLeaderData(*message));
@@ -3059,10 +3058,10 @@
     return error;
 }
 
-otError MleRouter::SendChildUpdateRequest(Child &aChild)
+Error MleRouter::SendChildUpdateRequest(Child &aChild)
 {
     static const uint8_t tlvs[] = {Tlv::kTimeout, Tlv::kAddressRegistration};
-    otError              error  = OT_ERROR_NONE;
+    Error                error  = kErrorNone;
     Ip6::Address         destination;
     Message *            message;
 
@@ -3088,7 +3087,7 @@
         }
     }
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     message->SetSubType(Message::kSubTypeMleChildUpdateRequest);
     SuccessOrExit(error = AppendHeader(*message, kCommandChildUpdateRequest));
     SuccessOrExit(error = AppendSourceAddress(*message));
@@ -3126,10 +3125,10 @@
                                         uint8_t                 aTlvsLength,
                                         const Challenge &       aChallenge)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     Message *message;
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = AppendHeader(*message, kCommandChildUpdateResponse));
 
     for (int i = 0; i < aTlvsLength; i++)
@@ -3203,7 +3202,7 @@
 {
     OT_UNUSED_VARIABLE(aRequestMessage);
 
-    otError   error   = OT_ERROR_NONE;
+    Error     error   = kErrorNone;
     Message * message = nullptr;
     Neighbor *neighbor;
     bool      stableOnly;
@@ -3214,7 +3213,7 @@
         ExitNow();
     }
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     message->SetSubType(Message::kSubTypeMleDataResponse);
     SuccessOrExit(error = AppendHeader(*message, kCommandDataResponse));
     SuccessOrExit(error = AppendSourceAddress(*message));
@@ -3244,7 +3243,7 @@
         case Tlv::kLinkMetricsReport:
             OT_ASSERT(aRequestMessage != nullptr);
             neighbor = mNeighborTable.FindNeighbor(aDestination);
-            VerifyOrExit(neighbor != nullptr, error = OT_ERROR_INVALID_STATE);
+            VerifyOrExit(neighbor != nullptr, error = kErrorInvalidState);
             SuccessOrExit(error = Get<LinkMetrics>().AppendLinkMetricsReport(*message, *aRequestMessage, *neighbor));
             break;
 #endif
@@ -3440,11 +3439,11 @@
     return rval;
 }
 
-otError MleRouter::SetPreferredRouterId(uint8_t aRouterId)
+Error MleRouter::SetPreferredRouterId(uint8_t aRouterId)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(IsDetached() || IsDisabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(IsDetached() || IsDisabled(), error = kErrorInvalidState);
 
     mPreviousRouterId = aRouterId;
 
@@ -3479,9 +3478,9 @@
     return;
 }
 
-otError MleRouter::CheckReachability(uint16_t aMeshDest, Ip6::Header &aIp6Header)
+Error MleRouter::CheckReachability(uint16_t aMeshDest, Ip6::Header &aIp6Header)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     if (IsChild())
     {
@@ -3517,21 +3516,21 @@
         ExitNow();
     }
 
-    error = OT_ERROR_NO_ROUTE;
+    error = kErrorNoRoute;
 
 exit:
     return error;
 }
 
-otError MleRouter::SendAddressSolicit(ThreadStatusTlv::Status aStatus)
+Error MleRouter::SendAddressSolicit(ThreadStatusTlv::Status aStatus)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     Ip6::MessageInfo messageInfo;
     Coap::Message *  message = nullptr;
 
     VerifyOrExit(!mAddressSolicitPending);
 
-    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewPriorityMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewPriorityMessage()) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsConfirmablePost(UriPath::kAddressSolicit));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -3566,11 +3565,11 @@
 
 void MleRouter::SendAddressRelease(void)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     Ip6::MessageInfo messageInfo;
     Coap::Message *  message;
 
-    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsConfirmablePost(UriPath::kAddressRelease));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -3594,7 +3593,7 @@
 void MleRouter::HandleAddressSolicitResponse(void *               aContext,
                                              otMessage *          aMessage,
                                              const otMessageInfo *aMessageInfo,
-                                             otError              aResult)
+                                             Error                aResult)
 {
     static_cast<MleRouter *>(aContext)->HandleAddressSolicitResponse(
         static_cast<Coap::Message *>(aMessage), static_cast<const Ip6::MessageInfo *>(aMessageInfo), aResult);
@@ -3602,7 +3601,7 @@
 
 void MleRouter::HandleAddressSolicitResponse(Coap::Message *         aMessage,
                                              const Ip6::MessageInfo *aMessageInfo,
-                                             otError                 aResult)
+                                             Error                   aResult)
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
 
@@ -3615,7 +3614,7 @@
 
     mAddressSolicitPending = false;
 
-    VerifyOrExit(aResult == OT_ERROR_NONE && aMessage != nullptr && aMessage != nullptr);
+    VerifyOrExit(aResult == kErrorNone && aMessage != nullptr && aMessage != nullptr);
 
     VerifyOrExit(aMessage->GetCode() == Coap::kCodeChanged);
 
@@ -3707,7 +3706,7 @@
 
 void MleRouter::HandleAddressSolicit(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError         error = OT_ERROR_NONE;
+    Error           error = kErrorNone;
     Mac::ExtAddress extAddress;
     uint16_t        rloc16;
     uint8_t         status;
@@ -3716,7 +3715,7 @@
     uint16_t xtalAccuracy;
 #endif
 
-    VerifyOrExit(aMessage.IsConfirmablePostRequest(), error = OT_ERROR_PARSE);
+    VerifyOrExit(aMessage.IsConfirmablePostRequest(), error = kErrorParse);
 
     Log(kMessageReceive, kTypeAddressSolicit, aMessageInfo.GetPeerAddr());
 
@@ -3749,19 +3748,19 @@
         break;
 
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
         OT_UNREACHABLE_CODE(break);
     }
 
     switch (Tlv::Find<ThreadRloc16Tlv>(aMessage, rloc16))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         router = mRouterTable.Allocate(RouterIdFromRloc16(rloc16));
         break;
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         break;
     default:
-        ExitNow(error = OT_ERROR_PARSE);
+        ExitNow(error = kErrorParse);
     }
 
     // allocate new router id
@@ -3785,7 +3784,7 @@
 
 exit:
 
-    if (error == OT_ERROR_NONE)
+    if (error == kErrorNone)
     {
         SendAddressSolicitResponse(aMessage, router, aMessageInfo);
     }
@@ -3795,11 +3794,11 @@
                                            const Router *          aRouter,
                                            const Ip6::MessageInfo &aMessageInfo)
 {
-    otError             error = OT_ERROR_NONE;
+    Error               error = kErrorNone;
     ThreadRouterMaskTlv routerMaskTlv;
     Coap::Message *     message;
 
-    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewPriorityMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewPriorityMessage()) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->SetDefaultResponseHeader(aRequest));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -3987,7 +3986,7 @@
     aTlv.SetSedDatagramCount(OPENTHREAD_CONFIG_DEFAULT_SED_DATAGRAM_COUNT);
 }
 
-otError MleRouter::AppendConnectivity(Message &aMessage)
+Error MleRouter::AppendConnectivity(Message &aMessage)
 {
     ConnectivityTlv tlv;
 
@@ -3997,9 +3996,9 @@
     return tlv.AppendTo(aMessage);
 }
 
-otError MleRouter::AppendChildAddresses(Message &aMessage, Child &aChild)
+Error MleRouter::AppendChildAddresses(Message &aMessage, Child &aChild)
 {
-    otError                  error;
+    Error                    error;
     Tlv                      tlv;
     AddressRegistrationEntry entry;
     Lowpan::Context          context;
@@ -4011,7 +4010,7 @@
 
     for (const Ip6::Address &address : aChild.IterateIp6Addresses())
     {
-        if (address.IsMulticast() || Get<NetworkData::Leader>().GetContext(address, context) != OT_ERROR_NONE)
+        if (address.IsMulticast() || Get<NetworkData::Leader>().GetContext(address, context) != kErrorNone)
         {
             // uncompressed entry
             entry.SetUncompressed();
@@ -4140,7 +4139,7 @@
     aTlv.SetRouteDataLength(routerCount);
 }
 
-otError MleRouter::AppendRoute(Message &aMessage, Neighbor *aNeighbor)
+Error MleRouter::AppendRoute(Message &aMessage, Neighbor *aNeighbor)
 {
     RouteTlv tlv;
 
@@ -4150,12 +4149,12 @@
     return tlv.AppendTo(aMessage);
 }
 
-otError MleRouter::AppendActiveDataset(Message &aMessage)
+Error MleRouter::AppendActiveDataset(Message &aMessage)
 {
     return Get<MeshCoP::ActiveDataset>().AppendMleDatasetTlv(aMessage);
 }
 
-otError MleRouter::AppendPendingDataset(Message &aMessage)
+Error MleRouter::AppendPendingDataset(Message &aMessage)
 {
     return Get<MeshCoP::PendingDataset>().AppendMleDatasetTlv(aMessage);
 }
@@ -4301,12 +4300,12 @@
     return false;
 }
 
-otError MleRouter::SetAssignParentPriority(int8_t aParentPriority)
+Error MleRouter::SetAssignParentPriority(int8_t aParentPriority)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     VerifyOrExit(aParentPriority <= kParentPriorityHigh && aParentPriority >= kParentPriorityUnspecified,
-                 error = OT_ERROR_INVALID_ARGS);
+                 error = kErrorInvalidArgs);
 
     mParentPriority = aParentPriority;
 
@@ -4314,11 +4313,11 @@
     return error;
 }
 
-otError MleRouter::GetMaxChildTimeout(uint32_t &aTimeout) const
+Error MleRouter::GetMaxChildTimeout(uint32_t &aTimeout) const
 {
-    otError error = OT_ERROR_NOT_FOUND;
+    Error error = kErrorNotFound;
 
-    VerifyOrExit(IsRouterOrLeader(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(IsRouterOrLeader(), error = kErrorInvalidState);
 
     for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
     {
@@ -4332,7 +4331,7 @@
             aTimeout = child.GetTimeout();
         }
 
-        error = OT_ERROR_NONE;
+        error = kErrorNone;
     }
 
 exit:
@@ -4352,13 +4351,13 @@
     return;
 }
 
-otError MleRouter::SendTimeSync(void)
+Error MleRouter::SendTimeSync(void)
 {
-    otError      error = OT_ERROR_NONE;
+    Error        error = kErrorNone;
     Ip6::Address destination;
     Message *    message = nullptr;
 
-    VerifyOrExit((message = NewMleMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = NewMleMessage()) != nullptr, error = kErrorNoBufs);
     SuccessOrExit(error = AppendHeader(*message, kCommandTimeSync));
 
     message->SetTimeSync(true);
diff --git a/src/core/thread/mle_router.hpp b/src/core/thread/mle_router.hpp
index 01470c6..ffb9ea6 100644
--- a/src/core/thread/mle_router.hpp
+++ b/src/core/thread/mle_router.hpp
@@ -104,11 +104,11 @@
      *
      * @param[in]  aEligible  TRUE to configure device router-eligible, FALSE otherwise.
      *
-     * @retval OT_ERROR_NONE         Successfully set the router-eligible configuration.
-     * @retval OT_ERROR_NOT_CAPABLE  The device is not capable of becoming a router.
+     * @retval kErrorNone         Successfully set the router-eligible configuration.
+     * @retval kErrorNotCapable   The device is not capable of becoming a router.
      *
      */
-    otError SetRouterEligible(bool aEligible);
+    Error SetRouterEligible(bool aEligible);
 
     /**
      * This method indicates whether a node is the only router on the network.
@@ -124,22 +124,22 @@
      *
      * @param[in]  aStatus  The reason for requesting a Router ID.
      *
-     * @retval OT_ERROR_NONE           Successfully generated an Address Solicit message.
-     * @retval OT_ERROR_NOT_CAPABLE    Device is not capable of becoming a router
-     * @retval OT_ERROR_INVALID_STATE  Thread is not enabled
+     * @retval kErrorNone           Successfully generated an Address Solicit message.
+     * @retval kErrorNotCapable     Device is not capable of becoming a router
+     * @retval kErrorInvalidState   Thread is not enabled
      *
      */
-    otError BecomeRouter(ThreadStatusTlv::Status aStatus);
+    Error BecomeRouter(ThreadStatusTlv::Status aStatus);
 
     /**
      * This method causes the Thread interface to become a Leader and start a new partition.
      *
-     * @retval OT_ERROR_NONE           Successfully become a Leader and started a new partition.
-     * @retval OT_ERROR_NOT_CAPABLE    Device is not capable of becoming a leader
-     * @retval OT_ERROR_INVALID_STATE  Thread is not enabled
+     * @retval kErrorNone           Successfully become a Leader and started a new partition.
+     * @retval kErrorNotCapable     Device is not capable of becoming a leader
+     * @retval kErrorInvalidState   Thread is not enabled
      *
      */
-    otError BecomeLeader(void);
+    Error BecomeLeader(void);
 
     /**
      * This method returns the Leader Weighting value for this Thread interface.
@@ -184,11 +184,11 @@
      *
      * @param[in]  aRouterId             The preferred Router Id.
      *
-     * @retval OT_ERROR_NONE          Successfully set the preferred Router Id.
-     * @retval OT_ERROR_INVALID_STATE Could not set (role is other than detached and disabled)
+     * @retval kErrorNone          Successfully set the preferred Router Id.
+     * @retval kErrorInvalidState  Could not set (role is other than detached and disabled)
      *
      */
-    otError SetPreferredRouterId(uint8_t aRouterId);
+    Error SetPreferredRouterId(uint8_t aRouterId);
 
     /**
      * This method gets the Partition Id which the device joined successfully once.
@@ -282,7 +282,7 @@
      * @returns The ROUTER_SELECTION_JITTER value.
      *
      */
-    otError SetRouterSelectionJitter(uint8_t aRouterJitter);
+    Error SetRouterSelectionJitter(uint8_t aRouterJitter);
 
     /**
      * This method returns the current router selection jitter timeout value.
@@ -384,11 +384,11 @@
      * @param[in]  aMeshDest   The RLOC16 of the destination.
      * @param[in]  aIp6Header  A reference to the IPv6 header of the message.
      *
-     * @retval OT_ERROR_NONE      The destination is reachable.
-     * @retval OT_ERROR_NO_ROUTE  The destination is not reachable and the message should be dropped.
+     * @retval kErrorNone      The destination is reachable.
+     * @retval kErrorNoRoute   The destination is not reachable and the message should be dropped.
      *
      */
-    otError CheckReachability(uint16_t aMeshDest, Ip6::Header &aIp6Header);
+    Error CheckReachability(uint16_t aMeshDest, Ip6::Header &aIp6Header);
 
     /**
      * This method resolves 2-hop routing loops.
@@ -429,13 +429,13 @@
     /**
      * This method generates an MLE Child Update Request message to be sent to the parent.
      *
-     * @retval OT_ERROR_NONE     Successfully generated an MLE Child Update Request message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers to generate the MLE Child Update Request message.
+     * @retval kErrorNone     Successfully generated an MLE Child Update Request message.
+     * @retval kErrorNoBufs   Insufficient buffers to generate the MLE Child Update Request message.
      *
      */
-    otError SendChildUpdateRequest(void) { return Mle::SendChildUpdateRequest(); }
+    Error SendChildUpdateRequest(void) { return Mle::SendChildUpdateRequest(); }
 
-    otError SendLinkRequest(Neighbor *aNeighbor);
+    Error SendLinkRequest(Neighbor *aNeighbor);
 
 #if OPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE
     /**
@@ -463,23 +463,23 @@
      *
      * @param[in]  aParentPriority  The parent priority value.
      *
-     * @retval OT_ERROR_NONE           Successfully set the parent priority.
-     * @retval OT_ERROR_INVALID_ARGS   If the parent priority value is not among 1, 0, -1 and -2.
+     * @retval kErrorNone           Successfully set the parent priority.
+     * @retval kErrorInvalidArgs    If the parent priority value is not among 1, 0, -1 and -2.
      *
      */
-    otError SetAssignParentPriority(int8_t aParentPriority);
+    Error SetAssignParentPriority(int8_t aParentPriority);
 
     /**
      * This method gets the longest MLE Timeout TLV for all active MTD children.
      *
      * @param[out]  aTimeout  A reference to where the information is placed.
      *
-     * @retval OT_ERROR_NONE           Successfully get the max child timeout
-     * @retval OT_ERROR_INVALID_STATE  Not an active router
-     * @retval OT_ERROR_NOT_FOUND      NO MTD child
+     * @retval kErrorNone           Successfully get the max child timeout
+     * @retval kErrorInvalidState   Not an active router
+     * @retval kErrorNotFound       NO MTD child
      *
      */
-    otError GetMaxChildTimeout(uint32_t &aTimeout) const;
+    Error GetMaxChildTimeout(uint32_t &aTimeout) const;
 
     /**
      * This function sets the callback that is called when processing an MLE Discovery Request message.
@@ -514,11 +514,11 @@
     /**
      * This method generates an MLE Time Synchronization message.
      *
-     * @retval OT_ERROR_NONE     Successfully sent an MLE Time Synchronization message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient buffers to generate the MLE Time Synchronization message.
+     * @retval kErrorNone     Successfully sent an MLE Time Synchronization message.
+     * @retval kErrorNoBufs   Insufficient buffers to generate the MLE Time Synchronization message.
      *
      */
-    otError SendTimeSync(void);
+    Error SendTimeSync(void);
 #endif
 
 #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
@@ -547,11 +547,11 @@
      * @param[in]  aMaxIpAddresses  The maximum number of IP addresses that each MTD child may register with this
      *                              device as parent. 0 to clear the setting and restore the default.
      *
-     * @retval OT_ERROR_NONE           Successfully set/cleared the number.
-     * @retval OT_ERROR_INVALID_ARGS   If exceeds the allowed maximum number.
+     * @retval kErrorNone           Successfully set/cleared the number.
+     * @retval kErrorInvalidArgs    If exceeds the allowed maximum number.
      *
      */
-    otError SetMaxChildIpAddresses(uint8_t aMaxIpAddresses);
+    Error SetMaxChildIpAddresses(uint8_t aMaxIpAddresses);
 #endif
 
 private:
@@ -562,30 +562,30 @@
         kUnsolicitedDataResponseJitter = 500u, ///< Maximum delay before unsolicited Data Response in milliseconds.
     };
 
-    otError AppendConnectivity(Message &aMessage);
-    otError AppendChildAddresses(Message &aMessage, Child &aChild);
-    otError AppendRoute(Message &aMessage, Neighbor *aNeighbor = nullptr);
-    otError AppendActiveDataset(Message &aMessage);
-    otError AppendPendingDataset(Message &aMessage);
-    void    HandleDetachStart(void);
-    void    HandleChildStart(AttachMode aMode);
-    void    HandleLinkRequest(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, Neighbor *aNeighbor);
-    void    HandleLinkAccept(const Message &         aMessage,
-                             const Ip6::MessageInfo &aMessageInfo,
-                             uint32_t                aKeySequence,
-                             Neighbor *              aNeighbor);
-    otError HandleLinkAccept(const Message &         aMessage,
-                             const Ip6::MessageInfo &aMessageInfo,
-                             uint32_t                aKeySequence,
-                             Neighbor *              aNeighbor,
-                             bool                    aRequest);
-    void    HandleLinkAcceptAndRequest(const Message &         aMessage,
-                                       const Ip6::MessageInfo &aMessageInfo,
-                                       uint32_t                aKeySequence,
-                                       Neighbor *              aNeighbor);
-    otError HandleAdvertisement(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, Neighbor *);
-    void    HandleParentRequest(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
-    void    HandleChildIdRequest(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, uint32_t aKeySequence);
+    Error AppendConnectivity(Message &aMessage);
+    Error AppendChildAddresses(Message &aMessage, Child &aChild);
+    Error AppendRoute(Message &aMessage, Neighbor *aNeighbor = nullptr);
+    Error AppendActiveDataset(Message &aMessage);
+    Error AppendPendingDataset(Message &aMessage);
+    void  HandleDetachStart(void);
+    void  HandleChildStart(AttachMode aMode);
+    void  HandleLinkRequest(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, Neighbor *aNeighbor);
+    void  HandleLinkAccept(const Message &         aMessage,
+                           const Ip6::MessageInfo &aMessageInfo,
+                           uint32_t                aKeySequence,
+                           Neighbor *              aNeighbor);
+    Error HandleLinkAccept(const Message &         aMessage,
+                           const Ip6::MessageInfo &aMessageInfo,
+                           uint32_t                aKeySequence,
+                           Neighbor *              aNeighbor,
+                           bool                    aRequest);
+    void  HandleLinkAcceptAndRequest(const Message &         aMessage,
+                                     const Ip6::MessageInfo &aMessageInfo,
+                                     uint32_t                aKeySequence,
+                                     Neighbor *              aNeighbor);
+    Error HandleAdvertisement(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, Neighbor *);
+    void  HandleParentRequest(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    void  HandleChildIdRequest(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, uint32_t aKeySequence);
     void HandleChildUpdateRequest(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, uint32_t aKeySequence);
     void HandleChildUpdateResponse(const Message &         aMessage,
                                    const Ip6::MessageInfo &aMessageInfo,
@@ -598,45 +598,45 @@
     void HandleTimeSync(const Message &aMessage, const Ip6::MessageInfo &aMessageInfo, const Neighbor *aNeighbor);
 #endif
 
-    otError ProcessRouteTlv(const RouteTlv &aRoute);
-    void    StopAdvertiseTimer(void);
-    otError SendAddressSolicit(ThreadStatusTlv::Status aStatus);
-    void    SendAddressRelease(void);
-    void    SendAddressSolicitResponse(const Coap::Message &   aRequest,
-                                       const Router *          aRouter,
-                                       const Ip6::MessageInfo &aMessageInfo);
-    void    SendAdvertisement(void);
-    otError SendLinkAccept(const Ip6::MessageInfo &aMessageInfo,
-                           Neighbor *              aNeighbor,
-                           const RequestedTlvs &   aRequestedTlvs,
-                           const Challenge &       aChallenge);
-    void    SendParentResponse(Child *aChild, const Challenge &aChallenge, bool aRoutersOnlyRequest);
-    otError SendChildIdResponse(Child &aChild);
-    otError SendChildUpdateRequest(Child &aChild);
-    void    SendChildUpdateResponse(Child *                 aChild,
-                                    const Ip6::MessageInfo &aMessageInfo,
-                                    const uint8_t *         aTlvs,
-                                    uint8_t                 aTlvsLength,
-                                    const Challenge &       aChallenge);
-    void    SendDataResponse(const Ip6::Address &aDestination,
-                             const uint8_t *     aTlvs,
-                             uint8_t             aTlvsLength,
-                             uint16_t            aDelay,
-                             const Message *     aRequestMessage = nullptr);
-    otError SendDiscoveryResponse(const Ip6::Address &aDestination, const Message &aDiscoverRequestMessage);
-    void    SetStateRouter(uint16_t aRloc16);
-    void    SetStateLeader(uint16_t aRloc16);
-    void    StopLeader(void);
-    void    SynchronizeChildNetworkData(void);
-    otError UpdateChildAddresses(const Message &aMessage, uint16_t aOffset, Child &aChild);
-    void    UpdateRoutes(const RouteTlv &aRoute, uint8_t aRouterId);
-    bool    UpdateLinkQualityOut(const RouteTlv &aRoute, Router &aNeighbor, bool &aResetAdvInterval);
+    Error ProcessRouteTlv(const RouteTlv &aRoute);
+    void  StopAdvertiseTrickleTimer(void);
+    Error SendAddressSolicit(ThreadStatusTlv::Status aStatus);
+    void  SendAddressRelease(void);
+    void  SendAddressSolicitResponse(const Coap::Message &   aRequest,
+                                     const Router *          aRouter,
+                                     const Ip6::MessageInfo &aMessageInfo);
+    void  SendAdvertisement(void);
+    Error SendLinkAccept(const Ip6::MessageInfo &aMessageInfo,
+                         Neighbor *              aNeighbor,
+                         const RequestedTlvs &   aRequestedTlvs,
+                         const Challenge &       aChallenge);
+    void  SendParentResponse(Child *aChild, const Challenge &aChallenge, bool aRoutersOnlyRequest);
+    Error SendChildIdResponse(Child &aChild);
+    Error SendChildUpdateRequest(Child &aChild);
+    void  SendChildUpdateResponse(Child *                 aChild,
+                                  const Ip6::MessageInfo &aMessageInfo,
+                                  const uint8_t *         aTlvs,
+                                  uint8_t                 aTlvsLength,
+                                  const Challenge &       aChallenge);
+    void  SendDataResponse(const Ip6::Address &aDestination,
+                           const uint8_t *     aTlvs,
+                           uint8_t             aTlvsLength,
+                           uint16_t            aDelay,
+                           const Message *     aRequestMessage = nullptr);
+    Error SendDiscoveryResponse(const Ip6::Address &aDestination, const Message &aDiscoverRequestMessage);
+    void  SetStateRouter(uint16_t aRloc16);
+    void  SetStateLeader(uint16_t aRloc16);
+    void  StopLeader(void);
+    void  SynchronizeChildNetworkData(void);
+    Error UpdateChildAddresses(const Message &aMessage, uint16_t aOffset, Child &aChild);
+    void  UpdateRoutes(const RouteTlv &aRoute, uint8_t aRouterId);
+    bool  UpdateLinkQualityOut(const RouteTlv &aRoute, Router &aNeighbor, bool &aResetAdvInterval);
 
     static void HandleAddressSolicitResponse(void *               aContext,
                                              otMessage *          aMessage,
                                              const otMessageInfo *aMessageInfo,
-                                             otError              aResult);
-    void HandleAddressSolicitResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, otError aResult);
+                                             Error                aResult);
+    void HandleAddressSolicitResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, Error aResult);
     static void HandleAddressRelease(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
     void        HandleAddressRelease(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
     static void HandleAddressSolicit(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
@@ -653,11 +653,11 @@
     bool HasOneNeighborWithComparableConnectivity(const RouteTlv &aRoute, uint8_t aRouterId);
     bool HasSmallNumberOfChildren(void);
 
-    static bool HandleAdvertiseTimer(TrickleTimer &aTimer);
-    bool        HandleAdvertiseTimer(void);
+    static void HandleAdvertiseTrickleTimer(TrickleTimer &aTimer);
+    void        HandleAdvertiseTrickleTimer(void);
     void        HandleTimeTick(void);
 
-    TrickleTimer mAdvertiseTimer;
+    TrickleTimer mAdvertiseTrickleTimer;
 
     Coap::Resource mAddressSolicit;
     Coap::Resource mAddressRelease;
@@ -728,14 +728,14 @@
 
     uint8_t GetCost(uint16_t) { return 0; }
 
-    otError RemoveNeighbor(Neighbor &) { return BecomeDetached(); }
-    void    RemoveRouterLink(Router &) { IgnoreError(BecomeDetached()); }
+    Error RemoveNeighbor(Neighbor &) { return BecomeDetached(); }
+    void  RemoveRouterLink(Router &) { IgnoreError(BecomeDetached()); }
 
     static bool IsRouterIdValid(uint8_t aRouterId) { return aRouterId <= kMaxRouterId; }
 
-    otError SendChildUpdateRequest(void) { return Mle::SendChildUpdateRequest(); }
+    Error SendChildUpdateRequest(void) { return Mle::SendChildUpdateRequest(); }
 
-    otError CheckReachability(uint16_t aMeshDest, Ip6::Header &aIp6Header)
+    Error CheckReachability(uint16_t aMeshDest, Ip6::Header &aIp6Header)
     {
         return Mle::CheckReachability(aMeshDest, aIp6Header);
     }
diff --git a/src/core/thread/mle_tlvs.hpp b/src/core/thread/mle_tlvs.hpp
index a70c3a1..4da6c30 100644
--- a/src/core/thread/mle_tlvs.hpp
+++ b/src/core/thread/mle_tlvs.hpp
@@ -1265,7 +1265,7 @@
     bool IsValid(void) const { return GetLength() >= sizeof(*this) - sizeof(Tlv); }
 } OT_TOOL_PACKED_END;
 
-#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || (!OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE)
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || (OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE)
 /**
  * This class implements CSL Channel TLV generation and parsing.
  *
@@ -1330,7 +1330,7 @@
     uint16_t mChannel;
 } OT_TOOL_PACKED_END;
 
-#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || (!OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE)
+#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || (OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE)
 
 /**
  * @}
diff --git a/src/core/thread/mle_types.hpp b/src/core/thread/mle_types.hpp
index f409b3d..b210fa6 100644
--- a/src/core/thread/mle_types.hpp
+++ b/src/core/thread/mle_types.hpp
@@ -345,10 +345,7 @@
      * @param[in] aMode   A mode TLV bitmask to initialize the `DeviceMode` object.
      *
      */
-    explicit DeviceMode(uint8_t aMode)
-        : mMode(aMode)
-    {
-    }
+    explicit DeviceMode(uint8_t aMode) { Set(aMode); }
 
     /**
      * This constructor initializes a `DeviceMode` object from a given mode configuration structure.
diff --git a/src/core/thread/mlr_manager.cpp b/src/core/thread/mlr_manager.cpp
index 287ec60..e66766a 100644
--- a/src/core/thread/mlr_manager.cpp
+++ b/src/core/thread/mlr_manager.cpp
@@ -225,22 +225,22 @@
 
 void MlrManager::SendMulticastListenerRegistration(void)
 {
-    otError         error;
+    Error           error;
     Mle::MleRouter &mle = Get<Mle::MleRouter>();
-    Ip6::Address    addresses[kIPv6AddressesNumMax];
+    Ip6::Address    addresses[kIp6AddressesNumMax];
     uint8_t         addressesNum = 0;
 
-    VerifyOrExit(!mMlrPending, error = OT_ERROR_BUSY);
-    VerifyOrExit(mle.IsAttached(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(mle.IsFullThreadDevice() || mle.GetParent().IsThreadVersion1p1(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(Get<BackboneRouter::Leader>().HasPrimary(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(!mMlrPending, error = kErrorBusy);
+    VerifyOrExit(mle.IsAttached(), error = kErrorInvalidState);
+    VerifyOrExit(mle.IsFullThreadDevice() || mle.GetParent().IsThreadVersion1p1(), error = kErrorInvalidState);
+    VerifyOrExit(Get<BackboneRouter::Leader>().HasPrimary(), error = kErrorInvalidState);
 
 #if OPENTHREAD_CONFIG_MLR_ENABLE
     // Append Netif multicast addresses
     for (Ip6::ExternalNetifMulticastAddress &addr :
          Get<ThreadNetif>().IterateExternalMulticastAddresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal))
     {
-        if (addressesNum >= kIPv6AddressesNumMax)
+        if (addressesNum >= kIp6AddressesNumMax)
         {
             break;
         }
@@ -257,7 +257,7 @@
     // Append Child multicast addresses
     for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValid))
     {
-        if (addressesNum >= kIPv6AddressesNumMax)
+        if (addressesNum >= kIp6AddressesNumMax)
         {
             break;
         }
@@ -269,7 +269,7 @@
 
         for (const Ip6::Address &address : child.IterateIp6Addresses(Ip6::Address::kTypeMulticastLargerThanRealmLocal))
         {
-            if (addressesNum >= kIPv6AddressesNumMax)
+            if (addressesNum >= kIp6AddressesNumMax)
             {
                 break;
             }
@@ -283,25 +283,27 @@
     }
 #endif
 
-    VerifyOrExit(addressesNum > 0, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(addressesNum > 0, error = kErrorNotFound);
     SuccessOrExit(
         error = SendMulticastListenerRegistrationMessage(
             addresses, addressesNum, nullptr, &MlrManager::HandleMulticastListenerRegistrationResponse, this));
 
     mMlrPending = true;
 
-    // TODO: not enable fast polls for SSED
+    // Generally Thread 1.2 Router would send MLR.req on bebelf for MA (scope >=4) subscribed by its MTD child.
+    // When Thread 1.2 MTD attaches to Thread 1.1 parent, 1.2 MTD should send MLR.req to PBBR itself.
+    // In this case, Thread 1.2 sleepy end device relies on fast data poll to fetch the response timely.
     if (!Get<Mle::Mle>().IsRxOnWhenIdle())
     {
-        Get<DataPollSender>().SendFastPolls(DataPollSender::kDefaultFastPolls);
+        Get<DataPollSender>().SendFastPolls();
     }
 
 exit:
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         SetMulticastAddressMlrState(kMlrStateRegistering, kMlrStateToRegister);
 
-        if (error == OT_ERROR_NO_BUFS)
+        if (error == kErrorNoBufs)
         {
             ScheduleSend(1);
         }
@@ -312,21 +314,21 @@
 }
 
 #if (OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE) && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
-otError MlrManager::RegisterMulticastListeners(const otIp6Address *                    aAddresses,
-                                               uint8_t                                 aAddressNum,
-                                               const uint32_t *                        aTimeout,
-                                               otIp6RegisterMulticastListenersCallback aCallback,
-                                               void *                                  aContext)
+Error MlrManager::RegisterMulticastListeners(const otIp6Address *                    aAddresses,
+                                             uint8_t                                 aAddressNum,
+                                             const uint32_t *                        aTimeout,
+                                             otIp6RegisterMulticastListenersCallback aCallback,
+                                             void *                                  aContext)
 {
-    otError error;
+    Error error;
 
-    VerifyOrExit(aAddresses != nullptr, error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit(aAddressNum > 0 && aAddressNum <= kIPv6AddressesNumMax, error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit(aContext == nullptr || aCallback != nullptr, error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit(Get<MeshCoP::Commissioner>().IsActive(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(aAddresses != nullptr, error = kErrorInvalidArgs);
+    VerifyOrExit(aAddressNum > 0 && aAddressNum <= kIp6AddressesNumMax, error = kErrorInvalidArgs);
+    VerifyOrExit(aContext == nullptr || aCallback != nullptr, error = kErrorInvalidArgs);
+    VerifyOrExit(Get<MeshCoP::Commissioner>().IsActive(), error = kErrorInvalidState);
 
     // Only allow one outstanding registration if callback is specified.
-    VerifyOrExit(!mRegisterMulticastListenersPending, error = OT_ERROR_BUSY);
+    VerifyOrExit(!mRegisterMulticastListenersPending, error = kErrorBusy);
 
     SuccessOrExit(error = SendMulticastListenerRegistrationMessage(
                       aAddresses, aAddressNum, aTimeout, &MlrManager::HandleRegisterMulticastListenersResponse, this));
@@ -342,7 +344,7 @@
 void MlrManager::HandleRegisterMulticastListenersResponse(void *               aContext,
                                                           otMessage *          aMessage,
                                                           const otMessageInfo *aMessageInfo,
-                                                          otError              aResult)
+                                                          Error                aResult)
 {
     static_cast<MlrManager *>(aContext)->HandleRegisterMulticastListenersResponse(
         static_cast<Coap::Message *>(aMessage), static_cast<const Ip6::MessageInfo *>(aMessageInfo), aResult);
@@ -350,13 +352,13 @@
 
 void MlrManager::HandleRegisterMulticastListenersResponse(otMessage *          aMessage,
                                                           const otMessageInfo *aMessageInfo,
-                                                          otError              aResult)
+                                                          Error                aResult)
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
 
     uint8_t                                 status;
-    otError                                 error;
-    Ip6::Address                            failedAddresses[kIPv6AddressesNumMax];
+    Error                                   error;
+    Ip6::Address                            failedAddresses[kIp6AddressesNumMax];
     uint8_t                                 failedAddressNum = 0;
     otIp6RegisterMulticastListenersCallback callback         = mRegisterMulticastListenersCallback;
     void *                                  context          = mRegisterMulticastListenersContext;
@@ -376,23 +378,23 @@
 
 #endif // (OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE) && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
 
-otError MlrManager::SendMulticastListenerRegistrationMessage(const otIp6Address *  aAddresses,
-                                                             uint8_t               aAddressNum,
-                                                             const uint32_t *      aTimeout,
-                                                             Coap::ResponseHandler aResponseHandler,
-                                                             void *                aResponseContext)
+Error MlrManager::SendMulticastListenerRegistrationMessage(const otIp6Address *  aAddresses,
+                                                           uint8_t               aAddressNum,
+                                                           const uint32_t *      aTimeout,
+                                                           Coap::ResponseHandler aResponseHandler,
+                                                           void *                aResponseContext)
 {
     OT_UNUSED_VARIABLE(aTimeout);
 
-    otError          error   = OT_ERROR_NONE;
+    Error            error   = kErrorNone;
     Mle::MleRouter & mle     = Get<Mle::MleRouter>();
     Coap::Message *  message = nullptr;
     Ip6::MessageInfo messageInfo;
-    IPv6AddressesTlv addressesTlv;
+    Ip6AddressesTlv  addressesTlv;
 
-    VerifyOrExit(Get<BackboneRouter::Leader>().HasPrimary(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(Get<BackboneRouter::Leader>().HasPrimary(), error = kErrorInvalidState);
 
-    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = kErrorNoBufs);
 
     message->InitAsConfirmablePost();
     SuccessOrExit(message->GenerateRandomToken(Coap::Message::kDefaultTokenLength));
@@ -438,7 +440,7 @@
     error = Get<Tmf::TmfAgent>().SendMessage(*message, messageInfo, aResponseHandler, aResponseContext);
 
 exit:
-    otLogInfoMlr("Send MLR.req: %s, addressNum=%d", otThreadErrorToString(error), aAddressNum);
+    otLogInfoMlr("Send MLR.req: %s, addressNum=%d", ErrorToString(error), aAddressNum);
     FreeMessageOnError(message, error);
     return error;
 }
@@ -446,7 +448,7 @@
 void MlrManager::HandleMulticastListenerRegistrationResponse(void *               aContext,
                                                              otMessage *          aMessage,
                                                              const otMessageInfo *aMessageInfo,
-                                                             otError              aResult)
+                                                             Error                aResult)
 {
     static_cast<MlrManager *>(aContext)->HandleMulticastListenerRegistrationResponse(
         static_cast<Coap::Message *>(aMessage), static_cast<const Ip6::MessageInfo *>(aMessageInfo), aResult);
@@ -454,21 +456,21 @@
 
 void MlrManager::HandleMulticastListenerRegistrationResponse(Coap::Message *         aMessage,
                                                              const Ip6::MessageInfo *aMessageInfo,
-                                                             otError                 aResult)
+                                                             Error                   aResult)
 {
     OT_UNUSED_VARIABLE(aMessageInfo);
 
     uint8_t      status;
-    otError      error;
-    Ip6::Address failedAddresses[kIPv6AddressesNumMax];
+    Error        error;
+    Ip6::Address failedAddresses[kIp6AddressesNumMax];
     uint8_t      failedAddressNum = 0;
 
     error = ParseMulticastListenerRegistrationResponse(aResult, aMessage, status, failedAddresses, failedAddressNum);
 
-    FinishMulticastListenerRegistration(error == OT_ERROR_NONE && status == ThreadStatusTlv::MlrStatus::kMlrSuccess,
+    FinishMulticastListenerRegistration(error == kErrorNone && status == ThreadStatusTlv::MlrStatus::kMlrSuccess,
                                         failedAddresses, failedAddressNum);
 
-    if (error == OT_ERROR_NONE && status == ThreadStatusTlv::MlrStatus::kMlrSuccess)
+    if (error == kErrorNone && status == ThreadStatusTlv::MlrStatus::kMlrSuccess)
     {
         // keep sending until all multicast addresses are registered.
         ScheduleSend(0);
@@ -481,7 +483,7 @@
         // The Device has just attempted a Multicast Listener Registration which failed, and it retries the same
         // registration with a random time delay chosen in the interval [0, Reregistration Delay].
         // This is required by Thread 1.2 Specification 5.24.2.3
-        if (Get<BackboneRouter::Leader>().GetConfig(config) == OT_ERROR_NONE)
+        if (Get<BackboneRouter::Leader>().GetConfig(config) == kErrorNone)
         {
             reregDelay = config.mReregistrationDelay > 1
                              ? Random::NonCrypto::GetUint16InRange(1, config.mReregistrationDelay)
@@ -491,27 +493,27 @@
     }
 }
 
-otError MlrManager::ParseMulticastListenerRegistrationResponse(otError        aResult,
-                                                               Coap::Message *aMessage,
-                                                               uint8_t &      aStatus,
-                                                               Ip6::Address * aFailedAddresses,
-                                                               uint8_t &      aFailedAddressNum)
+Error MlrManager::ParseMulticastListenerRegistrationResponse(Error          aResult,
+                                                             Coap::Message *aMessage,
+                                                             uint8_t &      aStatus,
+                                                             Ip6::Address * aFailedAddresses,
+                                                             uint8_t &      aFailedAddressNum)
 {
-    otError  error;
+    Error    error;
     uint16_t addressesOffset, addressesLength;
 
     aStatus = ThreadStatusTlv::MlrStatus::kMlrGeneralFailure;
 
-    VerifyOrExit(aResult == OT_ERROR_NONE && aMessage != nullptr, error = OT_ERROR_PARSE);
-    VerifyOrExit(aMessage->GetCode() == Coap::kCodeChanged, error = OT_ERROR_PARSE);
+    VerifyOrExit(aResult == kErrorNone && aMessage != nullptr, error = kErrorParse);
+    VerifyOrExit(aMessage->GetCode() == Coap::kCodeChanged, error = kErrorParse);
 
     SuccessOrExit(error = Tlv::Find<ThreadStatusTlv>(*aMessage, aStatus));
 
-    if (ThreadTlv::FindTlvValueOffset(*aMessage, IPv6AddressesTlv::kIPv6Addresses, addressesOffset, addressesLength) ==
-        OT_ERROR_NONE)
+    if (ThreadTlv::FindTlvValueOffset(*aMessage, Ip6AddressesTlv::kIp6Addresses, addressesOffset, addressesLength) ==
+        kErrorNone)
     {
-        VerifyOrExit(addressesLength % sizeof(Ip6::Address) == 0, error = OT_ERROR_PARSE);
-        VerifyOrExit(addressesLength / sizeof(Ip6::Address) <= kIPv6AddressesNumMax, error = OT_ERROR_PARSE);
+        VerifyOrExit(addressesLength % sizeof(Ip6::Address) == 0, error = kErrorParse);
+        VerifyOrExit(addressesLength / sizeof(Ip6::Address) <= kIp6AddressesNumMax, error = kErrorParse);
 
         for (uint16_t offset = 0; offset < addressesLength; offset += sizeof(Ip6::Address))
         {
@@ -520,11 +522,11 @@
         }
     }
 
-    VerifyOrExit(aFailedAddressNum == 0 || aStatus != ThreadStatusTlv::MlrStatus::kMlrSuccess, error = OT_ERROR_PARSE);
+    VerifyOrExit(aFailedAddressNum == 0 || aStatus != ThreadStatusTlv::MlrStatus::kMlrSuccess, error = kErrorParse);
 
 exit:
     LogMlrResponse(aResult, error, aStatus, aFailedAddresses, aFailedAddressNum);
-    return aResult != OT_ERROR_NONE ? aResult : error;
+    return aResult != kErrorNone ? aResult : error;
 }
 
 void MlrManager::SetMulticastAddressMlrState(MlrState aFromState, MlrState aToState)
@@ -692,7 +694,7 @@
 #endif // OPENTHREAD_CONFIG_LOG_MLR && OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_DEBG
 }
 
-void MlrManager::AppendToUniqueAddressList(Ip6::Address (&aAddresses)[kIPv6AddressesNumMax],
+void MlrManager::AppendToUniqueAddressList(Ip6::Address (&aAddresses)[kIp6AddressesNumMax],
                                            uint8_t &           aAddressNum,
                                            const Ip6::Address &aAddress)
 {
@@ -735,8 +737,8 @@
     return contains;
 }
 
-void MlrManager::LogMlrResponse(otError             aResult,
-                                otError             aError,
+void MlrManager::LogMlrResponse(Error               aResult,
+                                Error               aError,
                                 uint8_t             aStatus,
                                 const Ip6::Address *aFailedAddresses,
                                 uint8_t             aFailedAddressNum)
@@ -748,14 +750,14 @@
     OT_UNUSED_VARIABLE(aFailedAddressNum);
 
 #if OPENTHREAD_CONFIG_LOG_BBR
-    if (aResult == OT_ERROR_NONE && aError == OT_ERROR_NONE && aStatus == ThreadStatusTlv::MlrStatus::kMlrSuccess)
+    if (aResult == kErrorNone && aError == kErrorNone && aStatus == ThreadStatusTlv::MlrStatus::kMlrSuccess)
     {
-        otLogInfoBbr("Receive MLR.rsp OK", otThreadErrorToString(aResult));
+        otLogInfoBbr("Receive MLR.rsp OK", ErrorToString(aResult));
     }
     else
     {
-        otLogWarnBbr("Receive MLR.rsp: result=%s, error=%s, status=%d, failedAddressNum=%d",
-                     otThreadErrorToString(aResult), otThreadErrorToString(aError), aStatus, aFailedAddressNum);
+        otLogWarnBbr("Receive MLR.rsp: result=%s, error=%s, status=%d, failedAddressNum=%d", ErrorToString(aResult),
+                     ErrorToString(aError), aStatus, aFailedAddressNum);
 
         for (uint8_t i = 0; i < aFailedAddressNum; i++)
         {
diff --git a/src/core/thread/mlr_manager.hpp b/src/core/thread/mlr_manager.hpp
index fa2a9f1..65abcea 100644
--- a/src/core/thread/mlr_manager.hpp
+++ b/src/core/thread/mlr_manager.hpp
@@ -38,6 +38,10 @@
 
 #if OPENTHREAD_CONFIG_MLR_ENABLE || (OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE)
 
+#if OPENTHREAD_CONFIG_MLR_ENABLE && (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2)
+#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_MLR_ENABLE"
+#endif
+
 #include "backbone_router/bbr_leader.hpp"
 #include "coap/coap_message.hpp"
 #include "common/locator.hpp"
@@ -122,53 +126,53 @@
      * @param aCallback    A callback function.
      * @param aContext     A user context pointer.
      *
-     * @retval OT_ERROR_NONE           Successfully sent MLR.req. The @p aCallback will be called iff this method
-     *                                 returns OT_ERROR_NONE.
-     * @retval OT_ERROR_BUSY           If a previous registration was ongoing.
-     * @retval OT_ERROR_INVALID_ARGS   If one or more arguments are invalid.
-     * @retval OT_ERROR_INVALID_STATE  If the device was not in a valid state to send MLR.req (e.g. Commissioner not
-     *                                 started, Primary Backbone Router not found).
-     * @retval OT_ERROR_NO_BUFS        If insufficient message buffers available.
+     * @retval kErrorNone          Successfully sent MLR.req. The @p aCallback will be called iff this method
+     *                             returns kErrorNone.
+     * @retval kErrorBusy          If a previous registration was ongoing.
+     * @retval kErrorInvalidArgs   If one or more arguments are invalid.
+     * @retval kErrorInvalidState  If the device was not in a valid state to send MLR.req (e.g. Commissioner not
+     *                             started, Primary Backbone Router not found).
+     * @retval kErrorNoBufs        If insufficient message buffers available.
      *
      */
-    otError RegisterMulticastListeners(const otIp6Address *                    aAddresses,
-                                       uint8_t                                 aAddressNum,
-                                       const uint32_t *                        aTimeout,
-                                       otIp6RegisterMulticastListenersCallback aCallback,
-                                       void *                                  aContext);
+    Error RegisterMulticastListeners(const otIp6Address *                    aAddresses,
+                                     uint8_t                                 aAddressNum,
+                                     const uint32_t *                        aTimeout,
+                                     otIp6RegisterMulticastListenersCallback aCallback,
+                                     void *                                  aContext);
 #endif
 
 private:
     void HandleNotifierEvents(Events aEvents);
 
-    void    SendMulticastListenerRegistration(void);
-    otError SendMulticastListenerRegistrationMessage(const otIp6Address *  aAddresses,
-                                                     uint8_t               aAddressNum,
-                                                     const uint32_t *      aTimeout,
-                                                     Coap::ResponseHandler aResponseHandler,
-                                                     void *                aResponseContext);
+    void  SendMulticastListenerRegistration(void);
+    Error SendMulticastListenerRegistrationMessage(const otIp6Address *  aAddresses,
+                                                   uint8_t               aAddressNum,
+                                                   const uint32_t *      aTimeout,
+                                                   Coap::ResponseHandler aResponseHandler,
+                                                   void *                aResponseContext);
 
-    static void    HandleMulticastListenerRegistrationResponse(void *               aContext,
-                                                               otMessage *          aMessage,
-                                                               const otMessageInfo *aMessageInfo,
-                                                               otError              aResult);
-    void           HandleMulticastListenerRegistrationResponse(Coap::Message *         aMessage,
-                                                               const Ip6::MessageInfo *aMessageInfo,
-                                                               otError                 aResult);
-    static otError ParseMulticastListenerRegistrationResponse(otError        aResult,
-                                                              Coap::Message *aMessage,
-                                                              uint8_t &      aStatus,
-                                                              Ip6::Address * aFailedAddresses,
-                                                              uint8_t &      aFailedAddressNum);
+    static void  HandleMulticastListenerRegistrationResponse(void *               aContext,
+                                                             otMessage *          aMessage,
+                                                             const otMessageInfo *aMessageInfo,
+                                                             Error                aResult);
+    void         HandleMulticastListenerRegistrationResponse(Coap::Message *         aMessage,
+                                                             const Ip6::MessageInfo *aMessageInfo,
+                                                             Error                   aResult);
+    static Error ParseMulticastListenerRegistrationResponse(Error          aResult,
+                                                            Coap::Message *aMessage,
+                                                            uint8_t &      aStatus,
+                                                            Ip6::Address * aFailedAddresses,
+                                                            uint8_t &      aFailedAddressNum);
 
 #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
     static void HandleRegisterMulticastListenersResponse(void *               aContext,
                                                          otMessage *          aMessage,
                                                          const otMessageInfo *aMessageInfo,
-                                                         otError              aResult);
+                                                         Error                aResult);
     void        HandleRegisterMulticastListenersResponse(otMessage *          aMessage,
                                                          const otMessageInfo *aMessageInfo,
-                                                         otError              aResult);
+                                                         Error                aResult);
 #endif
 
 #if OPENTHREAD_CONFIG_MLR_ENABLE
@@ -189,7 +193,7 @@
                                              const Ip6::Address *aFailedAddresses,
                                              uint8_t             aFailedAddressNum);
 
-    void        AppendToUniqueAddressList(Ip6::Address (&aAddresses)[kIPv6AddressesNumMax],
+    void        AppendToUniqueAddressList(Ip6::Address (&aAddresses)[kIp6AddressesNumMax],
                                           uint8_t &           aAddressNum,
                                           const Ip6::Address &aAddress);
     static bool AddressListContains(const Ip6::Address *aAddressList,
@@ -204,8 +208,8 @@
 
     void        LogMulticastAddresses(void);
     void        CheckInvariants(void) const;
-    static void LogMlrResponse(otError             aResult,
-                               otError             aError,
+    static void LogMlrResponse(Error               aResult,
+                               Error               aError,
                                uint8_t             aStatus,
                                const Ip6::Address *aFailedAddresses,
                                uint8_t             aFailedAddressNum);
diff --git a/src/core/thread/neighbor_table.cpp b/src/core/thread/neighbor_table.cpp
index 1a25a01..6d86a1b 100644
--- a/src/core/thread/neighbor_table.cpp
+++ b/src/core/thread/neighbor_table.cpp
@@ -137,21 +137,21 @@
 Neighbor *NeighborTable::FindNeighbor(const Ip6::Address &aIp6Address, Neighbor::StateFilter aFilter)
 {
     Neighbor *   neighbor = nullptr;
-    Mac::Address macAddresss;
+    Mac::Address macAddress;
 
     if (aIp6Address.IsLinkLocal())
     {
-        aIp6Address.GetIid().ConvertToMacAddress(macAddresss);
+        aIp6Address.GetIid().ConvertToMacAddress(macAddress);
     }
 
     if (Get<Mle::Mle>().IsRoutingLocator(aIp6Address))
     {
-        macAddresss.SetShort(aIp6Address.GetIid().GetLocator());
+        macAddress.SetShort(aIp6Address.GetIid().GetLocator());
     }
 
-    if (!macAddresss.IsNone())
+    if (!macAddress.IsNone())
     {
-        neighbor = FindChildOrRouter(Neighbor::AddressMatcher(macAddresss, aFilter));
+        neighbor = FindChildOrRouter(Neighbor::AddressMatcher(macAddress, aFilter));
         ExitNow();
     }
 
@@ -178,9 +178,9 @@
     return neighbor;
 }
 
-otError NeighborTable::GetNextNeighborInfo(otNeighborInfoIterator &aIterator, Neighbor::Info &aNeighInfo)
+Error NeighborTable::GetNextNeighborInfo(otNeighborInfoIterator &aIterator, Neighbor::Info &aNeighInfo)
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     int16_t index;
 
     // Non-negative iterator value gives the Child index into child table
@@ -226,7 +226,7 @@
     }
 
     aIterator = -index;
-    error     = OT_ERROR_NOT_FOUND;
+    error     = kErrorNotFound;
 
 exit:
     return error;
@@ -236,9 +236,9 @@
 
 #if OPENTHREAD_MTD
 
-otError NeighborTable::GetNextNeighborInfo(otNeighborInfoIterator &aIterator, Neighbor::Info &aNeighInfo)
+Error NeighborTable::GetNextNeighborInfo(otNeighborInfoIterator &aIterator, Neighbor::Info &aNeighInfo)
 {
-    otError error = OT_ERROR_NOT_FOUND;
+    Error error = kErrorNotFound;
 
     VerifyOrExit(aIterator == OT_NEIGHBOR_INFO_ITERATOR_INIT);
 
@@ -247,7 +247,7 @@
 
     aNeighInfo.SetFrom(Get<Mle::Mle>().GetParent());
     aNeighInfo.mIsChild = false;
-    error               = OT_ERROR_NONE;
+    error               = kErrorNone;
 
 exit:
     return error;
diff --git a/src/core/thread/neighbor_table.hpp b/src/core/thread/neighbor_table.hpp
index b10f66f..af6ed85 100644
--- a/src/core/thread/neighbor_table.hpp
+++ b/src/core/thread/neighbor_table.hpp
@@ -191,11 +191,11 @@
                                  it should be set to OT_NEIGHBOR_INFO_ITERATOR_INIT.
      * @param[out]    aNeighInfo The neighbor information.
      *
-     * @retval OT_ERROR_NONE          Successfully found the next neighbor entry in table.
-     * @retval OT_ERROR_NOT_FOUND     No subsequent neighbor entry exists in the table.
+     * @retval kErrorNone         Successfully found the next neighbor entry in table.
+     * @retval kErrorNotFound     No subsequent neighbor entry exists in the table.
      *
      */
-    otError GetNextNeighborInfo(otNeighborInfoIterator &aIterator, Neighbor::Info &aNeighInfo);
+    Error GetNextNeighborInfo(otNeighborInfoIterator &aIterator, Neighbor::Info &aNeighInfo);
 
     /**
      * This method registers the "neighbor table changed" callback function.
diff --git a/src/core/thread/network_data.cpp b/src/core/thread/network_data.cpp
index 7dc6cd0..6f6358a 100644
--- a/src/core/thread/network_data.cpp
+++ b/src/core/thread/network_data.cpp
@@ -170,12 +170,12 @@
     return tlv;
 }
 
-otError NetworkData::GetNetworkData(bool aStable, uint8_t *aData, uint8_t &aDataLength) const
+Error NetworkData::GetNetworkData(bool aStable, uint8_t *aData, uint8_t &aDataLength) const
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     OT_ASSERT(aData != nullptr);
-    VerifyOrExit(aDataLength >= mLength, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(aDataLength >= mLength, error = kErrorNoBufs);
 
     memcpy(aData, mTlvs, mLength);
     aDataLength = mLength;
@@ -189,12 +189,12 @@
     return error;
 }
 
-otError NetworkData::GetNextOnMeshPrefix(Iterator &aIterator, OnMeshPrefixConfig &aConfig) const
+Error NetworkData::GetNextOnMeshPrefix(Iterator &aIterator, OnMeshPrefixConfig &aConfig) const
 {
     return GetNextOnMeshPrefix(aIterator, Mac::kShortAddrBroadcast, aConfig);
 }
 
-otError NetworkData::GetNextOnMeshPrefix(Iterator &aIterator, uint16_t aRloc16, OnMeshPrefixConfig &aConfig) const
+Error NetworkData::GetNextOnMeshPrefix(Iterator &aIterator, uint16_t aRloc16, OnMeshPrefixConfig &aConfig) const
 {
     Config config;
 
@@ -205,12 +205,12 @@
     return Iterate(aIterator, aRloc16, config);
 }
 
-otError NetworkData::GetNextExternalRoute(Iterator &aIterator, ExternalRouteConfig &aConfig) const
+Error NetworkData::GetNextExternalRoute(Iterator &aIterator, ExternalRouteConfig &aConfig) const
 {
     return GetNextExternalRoute(aIterator, Mac::kShortAddrBroadcast, aConfig);
 }
 
-otError NetworkData::GetNextExternalRoute(Iterator &aIterator, uint16_t aRloc16, ExternalRouteConfig &aConfig) const
+Error NetworkData::GetNextExternalRoute(Iterator &aIterator, uint16_t aRloc16, ExternalRouteConfig &aConfig) const
 {
     Config config;
 
@@ -221,12 +221,12 @@
     return Iterate(aIterator, aRloc16, config);
 }
 
-otError NetworkData::GetNextService(Iterator &aIterator, ServiceConfig &aConfig) const
+Error NetworkData::GetNextService(Iterator &aIterator, ServiceConfig &aConfig) const
 {
     return GetNextService(aIterator, Mac::kShortAddrBroadcast, aConfig);
 }
 
-otError NetworkData::GetNextService(Iterator &aIterator, uint16_t aRloc16, ServiceConfig &aConfig) const
+Error NetworkData::GetNextService(Iterator &aIterator, uint16_t aRloc16, ServiceConfig &aConfig) const
 {
     Config config;
 
@@ -237,7 +237,7 @@
     return Iterate(aIterator, aRloc16, config);
 }
 
-otError NetworkData::Iterate(Iterator &aIterator, uint16_t aRloc16, Config &aConfig) const
+Error NetworkData::Iterate(Iterator &aIterator, uint16_t aRloc16, Config &aConfig) const
 {
     // Iterate to the next entry in Network Data matching `aRloc16`
     // (can be set to `Mac::kShortAddrBroadcast` to allow any RLOC).
@@ -249,7 +249,7 @@
     // indicating the type of entry and the non-nullptr config is updated
     // with the entry info.
 
-    otError             error = OT_ERROR_NOT_FOUND;
+    Error               error = kErrorNotFound;
     NetworkDataIterator iterator(aIterator);
 
     for (const NetworkDataTlv *cur; (cur = iterator.GetTlv(mTlvs)) < GetTlvsEnd(); iterator.AdvanceTlv(mTlvs))
@@ -307,7 +307,7 @@
                             aConfig.mService       = nullptr;
                             aConfig.mOnMeshPrefix->SetFrom(*prefixTlv, *borderRouter, *borderRouterEntry);
 
-                            ExitNow(error = OT_ERROR_NONE);
+                            ExitNow(error = kErrorNone);
                         }
                     }
 
@@ -333,7 +333,7 @@
                             aConfig.mService      = nullptr;
                             aConfig.mExternalRoute->SetFrom(GetInstance(), *prefixTlv, *hasRoute, *hasRouteEntry);
 
-                            ExitNow(error = OT_ERROR_NONE);
+                            ExitNow(error = kErrorNone);
                         }
                     }
 
@@ -370,7 +370,7 @@
 
                         iterator.MarkEntryAsNotNew();
 
-                        ExitNow(error = OT_ERROR_NONE);
+                        ExitNow(error = kErrorNone);
                     }
                 }
             }
@@ -381,9 +381,9 @@
     return error;
 }
 
-otError NetworkData::GetNextServiceId(Iterator &aIterator, uint16_t aRloc16, uint8_t &aServiceId) const
+Error NetworkData::GetNextServiceId(Iterator &aIterator, uint16_t aRloc16, uint8_t &aServiceId) const
 {
-    otError       error;
+    Error         error;
     ServiceConfig config;
 
     SuccessOrExit(error = GetNextService(aIterator, aRloc16, config));
@@ -399,13 +399,13 @@
     OnMeshPrefixConfig outerConfig;
     bool               rval = true;
 
-    while (aCompare.GetNextOnMeshPrefix(outerIterator, aRloc16, outerConfig) == OT_ERROR_NONE)
+    while (aCompare.GetNextOnMeshPrefix(outerIterator, aRloc16, outerConfig) == kErrorNone)
     {
         Iterator           innerIterator = kIteratorInit;
         OnMeshPrefixConfig innerConfig;
-        otError            error;
+        Error              error;
 
-        while ((error = GetNextOnMeshPrefix(innerIterator, aRloc16, innerConfig)) == OT_ERROR_NONE)
+        while ((error = GetNextOnMeshPrefix(innerIterator, aRloc16, innerConfig)) == kErrorNone)
         {
             if (outerConfig == innerConfig)
             {
@@ -413,7 +413,7 @@
             }
         }
 
-        if (error != OT_ERROR_NONE)
+        if (error != kErrorNone)
         {
             ExitNow(rval = false);
         }
@@ -429,13 +429,13 @@
     ExternalRouteConfig outerConfig;
     bool                rval = true;
 
-    while (aCompare.GetNextExternalRoute(outerIterator, aRloc16, outerConfig) == OT_ERROR_NONE)
+    while (aCompare.GetNextExternalRoute(outerIterator, aRloc16, outerConfig) == kErrorNone)
     {
         Iterator            innerIterator = kIteratorInit;
         ExternalRouteConfig innerConfig;
-        otError             error;
+        Error               error;
 
-        while ((error = GetNextExternalRoute(innerIterator, aRloc16, innerConfig)) == OT_ERROR_NONE)
+        while ((error = GetNextExternalRoute(innerIterator, aRloc16, innerConfig)) == kErrorNone)
         {
             if (outerConfig == innerConfig)
             {
@@ -443,7 +443,7 @@
             }
         }
 
-        if (error != OT_ERROR_NONE)
+        if (error != kErrorNone)
         {
             ExitNow(rval = false);
         }
@@ -459,13 +459,13 @@
     ServiceConfig outerConfig;
     bool          rval = true;
 
-    while (aCompare.GetNextService(outerIterator, aRloc16, outerConfig) == OT_ERROR_NONE)
+    while (aCompare.GetNextService(outerIterator, aRloc16, outerConfig) == kErrorNone)
     {
         Iterator      innerIterator = kIteratorInit;
         ServiceConfig innerConfig;
-        otError       error;
+        Error         error;
 
-        while ((error = GetNextService(innerIterator, aRloc16, innerConfig)) == OT_ERROR_NONE)
+        while ((error = GetNextService(innerIterator, aRloc16, innerConfig)) == kErrorNone)
         {
             if (outerConfig == innerConfig)
             {
@@ -473,7 +473,7 @@
             }
         }
 
-        if (error != OT_ERROR_NONE)
+        if (error != kErrorNone)
         {
             ExitNow(rval = false);
         }
@@ -489,7 +489,7 @@
     uint8_t  serviceId;
     bool     rval = false;
 
-    while (GetNextServiceId(iterator, aRloc16, serviceId) == OT_ERROR_NONE)
+    while (GetNextServiceId(iterator, aRloc16, serviceId) == kErrorNone)
     {
         if (serviceId == aServiceId)
         {
@@ -800,13 +800,13 @@
     NetworkData::RemoveTlv(mTlvs, mLength, aTlv);
 }
 
-otError NetworkData::SendServerDataNotification(uint16_t aRloc16, Coap::ResponseHandler aHandler, void *aContext)
+Error NetworkData::SendServerDataNotification(uint16_t aRloc16, Coap::ResponseHandler aHandler, void *aContext)
 {
-    otError          error   = OT_ERROR_NONE;
+    Error            error   = kErrorNone;
     Coap::Message *  message = nullptr;
     Ip6::MessageInfo messageInfo;
 
-    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsConfirmablePost(UriPath::kServerData));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -837,9 +837,9 @@
     return error;
 }
 
-otError NetworkData::GetNextServer(Iterator &aIterator, uint16_t &aRloc16) const
+Error NetworkData::GetNextServer(Iterator &aIterator, uint16_t &aRloc16) const
 {
-    otError             error;
+    Error               error;
     OnMeshPrefixConfig  prefixConfig;
     ExternalRouteConfig routeConfig;
     ServiceConfig       serviceConfig;
diff --git a/src/core/thread/network_data.hpp b/src/core/thread/network_data.hpp
index 38ad173..7311666 100644
--- a/src/core/thread/network_data.hpp
+++ b/src/core/thread/network_data.hpp
@@ -76,6 +76,10 @@
  */
 namespace NetworkData {
 
+namespace Service {
+class Manager;
+}
+
 /**
  * @addtogroup core-netdata-core
  *
@@ -157,6 +161,14 @@
      */
     Ip6::Prefix &GetPrefix(void) { return static_cast<Ip6::Prefix &>(mPrefix); }
 
+    /**
+     * This method sets the prefix.
+     *
+     * @param[in]  aPrefix  The prefix to set to.
+     *
+     */
+    void SetPrefix(const Ip6::Prefix &aPrefix) { mPrefix = aPrefix; }
+
 private:
     void SetFrom(Instance &           aInstance,
                  const PrefixTlv &    aPrefixTlv,
@@ -168,7 +180,7 @@
  * This type represents a Service configuration.
  *
  */
-class ServiceConfig : public otServiceConfig, public Clearable<ServiceConfig>
+class ServiceConfig : public otServiceConfig, public Clearable<ServiceConfig>, public Unequatable<ServiceConfig>
 {
     friend class NetworkData;
 
@@ -177,7 +189,7 @@
      * This class represents a Server configuration.
      *
      */
-    class ServerConfig : public otServerConfig
+    class ServerConfig : public otServerConfig, public Unequatable<ServerConfig>
     {
         friend class ServiceConfig;
 
@@ -193,17 +205,6 @@
          */
         bool operator==(const ServerConfig &aOther) const;
 
-        /**
-         * This method overloads operator `!=` to evaluate whether or not two `ServerConfig` instances are unequal.
-         *
-         * @param[in]  aOther  The other `ServerConfig` instance to compare with.
-         *
-         * @retval TRUE   If the two `ServerConfig` instances are unequal.
-         * @retval FALSE  If the two `ServerConfig` instances are not unequal.
-         *
-         */
-        bool operator!=(const ServerConfig &aOther) const { return !(*this == aOther); }
-
     private:
         void SetFrom(const ServerTlv &aServerTlv);
     };
@@ -235,17 +236,6 @@
      */
     bool operator==(const ServiceConfig &aOther) const;
 
-    /**
-     * This method overloads operator `!=` to evaluate whether or not two `ServiceConfig` instances are unequal.
-     *
-     * @param[in]  aOther  The other `ServiceConfig` instance to compare with.
-     *
-     * @retval TRUE   If the two `ServiceConfig` instances are unequal.
-     * @retval FALSE  If the two `ServiceConfig` instances are not unequal.
-     *
-     */
-    bool operator!=(const ServiceConfig &aOther) const { return !(*this == aOther); }
-
 private:
     void SetFrom(const ServiceTlv &aServiceTlv, const ServerTlv &aServerTlv);
 };
@@ -256,6 +246,8 @@
  */
 class NetworkData : public InstanceLocator
 {
+    friend class Service::Manager;
+
 public:
     enum
     {
@@ -295,11 +287,11 @@
      * @param[inout]  aDataLength  On entry, size of the data buffer pointed to by @p aData.
      *                             On exit, number of copied bytes.
      *
-     * @retval OT_ERROR_NONE       Successfully copied full Thread Network Data.
-     * @retval OT_ERROR_NO_BUFS    Not enough space to fully copy Thread Network Data.
+     * @retval kErrorNone       Successfully copied full Thread Network Data.
+     * @retval kErrorNoBufs     Not enough space to fully copy Thread Network Data.
      *
      */
-    otError GetNetworkData(bool aStable, uint8_t *aData, uint8_t &aDataLength) const;
+    Error GetNetworkData(bool aStable, uint8_t *aData, uint8_t &aDataLength) const;
 
     /**
      * This method provides the next On Mesh prefix in the Thread Network Data.
@@ -307,11 +299,11 @@
      * @param[inout]  aIterator  A reference to the Network Data iterator.
      * @param[out]    aConfig    A reference to a config variable where the On Mesh Prefix information will be placed.
      *
-     * @retval OT_ERROR_NONE       Successfully found the next On Mesh prefix.
-     * @retval OT_ERROR_NOT_FOUND  No subsequent On Mesh prefix exists in the Thread Network Data.
+     * @retval kErrorNone       Successfully found the next On Mesh prefix.
+     * @retval kErrorNotFound   No subsequent On Mesh prefix exists in the Thread Network Data.
      *
      */
-    otError GetNextOnMeshPrefix(Iterator &aIterator, OnMeshPrefixConfig &aConfig) const;
+    Error GetNextOnMeshPrefix(Iterator &aIterator, OnMeshPrefixConfig &aConfig) const;
 
     /**
      * This method provides the next On Mesh prefix in the Thread Network Data for a given RLOC16.
@@ -320,11 +312,11 @@
      * @param[in]     aRloc16    The RLOC16 value.
      * @param[out]    aConfig    A reference to a config variable where the On Mesh Prefix information will be placed.
      *
-     * @retval OT_ERROR_NONE       Successfully found the next On Mesh prefix.
-     * @retval OT_ERROR_NOT_FOUND  No subsequent On Mesh prefix exists in the Thread Network Data.
+     * @retval kErrorNone       Successfully found the next On Mesh prefix.
+     * @retval kErrorNotFound   No subsequent On Mesh prefix exists in the Thread Network Data.
      *
      */
-    otError GetNextOnMeshPrefix(Iterator &aIterator, uint16_t aRloc16, OnMeshPrefixConfig &aConfig) const;
+    Error GetNextOnMeshPrefix(Iterator &aIterator, uint16_t aRloc16, OnMeshPrefixConfig &aConfig) const;
 
     /**
      * This method provides the next external route in the Thread Network Data.
@@ -332,11 +324,11 @@
      * @param[inout]  aIterator  A reference to the Network Data iterator.
      * @param[out]    aConfig    A reference to a config variable where the external route information will be placed.
      *
-     * @retval OT_ERROR_NONE       Successfully found the next external route.
-     * @retval OT_ERROR_NOT_FOUND  No subsequent external route exists in the Thread Network Data.
+     * @retval kErrorNone       Successfully found the next external route.
+     * @retval kErrorNotFound   No subsequent external route exists in the Thread Network Data.
      *
      */
-    otError GetNextExternalRoute(Iterator &aIterator, ExternalRouteConfig &aConfig) const;
+    Error GetNextExternalRoute(Iterator &aIterator, ExternalRouteConfig &aConfig) const;
 
     /**
      * This method provides the next external route in the Thread Network Data for a given RLOC16.
@@ -345,11 +337,11 @@
      * @param[in]     aRloc16    The RLOC16 value.
      * @param[out]    aConfig    A reference to a config variable where the external route information will be placed.
      *
-     * @retval OT_ERROR_NONE       Successfully found the next external route.
-     * @retval OT_ERROR_NOT_FOUND  No subsequent external route exists in the Thread Network Data.
+     * @retval kErrorNone       Successfully found the next external route.
+     * @retval kErrorNotFound   No subsequent external route exists in the Thread Network Data.
      *
      */
-    otError GetNextExternalRoute(Iterator &aIterator, uint16_t aRloc16, ExternalRouteConfig &aConfig) const;
+    Error GetNextExternalRoute(Iterator &aIterator, uint16_t aRloc16, ExternalRouteConfig &aConfig) const;
 
     /**
      * This method provides the next service in the Thread Network Data.
@@ -357,11 +349,11 @@
      * @param[inout]  aIterator  A reference to the Network Data iterator.
      * @param[out]    aConfig    A reference to a config variable where the service information will be placed.
      *
-     * @retval OT_ERROR_NONE       Successfully found the next service.
-     * @retval OT_ERROR_NOT_FOUND  No subsequent service exists in the Thread Network Data.
+     * @retval kErrorNone       Successfully found the next service.
+     * @retval kErrorNotFound   No subsequent service exists in the Thread Network Data.
      *
      */
-    otError GetNextService(Iterator &aIterator, ServiceConfig &aConfig) const;
+    Error GetNextService(Iterator &aIterator, ServiceConfig &aConfig) const;
 
     /**
      * This method provides the next service in the Thread Network Data for a given RLOC16.
@@ -370,11 +362,11 @@
      * @param[in]     aRloc16    The RLOC16 value.
      * @param[out]    aConfig    A reference to a config variable where the service information will be placed.
      *
-     * @retval OT_ERROR_NONE       Successfully found the next service.
-     * @retval OT_ERROR_NOT_FOUND  No subsequent service exists in the Thread Network Data.
+     * @retval kErrorNone       Successfully found the next service.
+     * @retval kErrorNotFound   No subsequent service exists in the Thread Network Data.
      *
      */
-    otError GetNextService(Iterator &aIterator, uint16_t aRloc16, ServiceConfig &aConfig) const;
+    Error GetNextService(Iterator &aIterator, uint16_t aRloc16, ServiceConfig &aConfig) const;
 
     /**
      * This method provides the next Service ID in the Thread Network Data for a given RLOC16.
@@ -383,11 +375,11 @@
      * @param[in]     aRloc16    The RLOC16 value.
      * @param[out]    aServiceId A reference to variable where the Service ID will be placed.
      *
-     * @retval OT_ERROR_NONE       Successfully found the next service.
-     * @retval OT_ERROR_NOT_FOUND  No subsequent service exists in the Thread Network Data.
+     * @retval kErrorNone       Successfully found the next service.
+     * @retval kErrorNotFound   No subsequent service exists in the Thread Network Data.
      *
      */
-    otError GetNextServiceId(Iterator &aIterator, uint16_t aRloc16, uint8_t &aServiceId) const;
+    Error GetNextServiceId(Iterator &aIterator, uint16_t aRloc16, uint8_t &aServiceId) const;
 
     /**
      * This method indicates whether or not the Thread Network Data contains all of the on mesh prefix information
@@ -447,11 +439,11 @@
      * @param[inout]  aIterator  A reference to the Network Data iterator.
      * @param[out]    aRloc16    The RLOC16 value.
      *
-     * @retval OT_ERROR_NONE       Successfully found the next server.
-     * @retval OT_ERROR_NOT_FOUND  No subsequent server exists in the Thread Network Data.
+     * @retval kErrorNone       Successfully found the next server.
+     * @retval kErrorNotFound   No subsequent server exists in the Thread Network Data.
      *
      */
-    otError GetNextServer(Iterator &aIterator, uint16_t &aRloc16) const;
+    Error GetNextServer(Iterator &aIterator, uint16_t &aRloc16) const;
 
 protected:
     /**
@@ -825,11 +817,11 @@
      * @param[in]  aHandler  A function pointer that is called when the transaction ends.
      * @param[in]  aContext  A pointer to arbitrary context information.
      *
-     * @retval OT_ERROR_NONE     Successfully enqueued the notification message.
-     * @retval OT_ERROR_NO_BUFS  Insufficient message buffers to generate the notification message.
+     * @retval kErrorNone     Successfully enqueued the notification message.
+     * @retval kErrorNoBufs   Insufficient message buffers to generate the notification message.
      *
      */
-    otError SendServerDataNotification(uint16_t aRloc16, Coap::ResponseHandler aHandler, void *aContext);
+    Error SendServerDataNotification(uint16_t aRloc16, Coap::ResponseHandler aHandler, void *aContext);
 
     /**
      * This static method searches in a given sequence of TLVs to find the first TLV with a given TLV Type.
@@ -968,13 +960,6 @@
     uint8_t mLength;         ///< The number of valid bytes in @var mTlvs.
 
 private:
-    enum
-    {
-        kDataResubmitDelay  = 300000, ///< DATA_RESUBMIT_DELAY (milliseconds) if the device itself is the server.
-        kProxyResubmitDelay = 5000,   ///< Resubmit delay (milliseconds) if deregister as the child server proxy.
-
-    };
-
     class NetworkDataIterator
     {
     public:
@@ -1048,7 +1033,7 @@
         ServiceConfig *      mService;
     };
 
-    otError Iterate(Iterator &aIterator, uint16_t aRloc16, Config &aConfig) const;
+    Error Iterate(Iterator &aIterator, uint16_t aRloc16, Config &aConfig) const;
 
     static void RemoveTemporaryData(uint8_t *aData, uint8_t &aDataLength, PrefixTlv &aPrefix);
     static void RemoveTemporaryData(uint8_t *aData, uint8_t &aDataLength, ServiceTlv &aService);
diff --git a/src/core/thread/network_data_leader.cpp b/src/core/thread/network_data_leader.cpp
index 250343b..d4ad9bc 100644
--- a/src/core/thread/network_data_leader.cpp
+++ b/src/core/thread/network_data_leader.cpp
@@ -67,17 +67,17 @@
     Get<ot::Notifier>().Signal(kEventThreadNetdataChanged);
 }
 
-otError LeaderBase::GetServiceId(uint32_t       aEnterpriseNumber,
-                                 const uint8_t *aServiceData,
-                                 uint8_t        aServiceDataLength,
-                                 bool           aServerStable,
-                                 uint8_t &      aServiceId) const
+Error LeaderBase::GetServiceId(uint32_t       aEnterpriseNumber,
+                               const uint8_t *aServiceData,
+                               uint8_t        aServiceDataLength,
+                               bool           aServerStable,
+                               uint8_t &      aServiceId) const
 {
-    otError       error    = OT_ERROR_NOT_FOUND;
+    Error         error    = kErrorNotFound;
     Iterator      iterator = kIteratorInit;
     ServiceConfig serviceConfig;
 
-    while (GetNextService(iterator, serviceConfig) == OT_ERROR_NONE)
+    while (GetNextService(iterator, serviceConfig) == kErrorNone)
     {
         if (aEnterpriseNumber == serviceConfig.mEnterpriseNumber &&
             aServiceDataLength == serviceConfig.mServiceDataLength &&
@@ -85,7 +85,7 @@
             aServerStable == serviceConfig.mServerConfig.mStable)
         {
             aServiceId = serviceConfig.mServiceId;
-            ExitNow(error = OT_ERROR_NONE);
+            ExitNow(error = kErrorNone);
         }
     }
 
@@ -93,51 +93,6 @@
     return error;
 }
 
-#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
-otError LeaderBase::GetBackboneRouterPrimary(BackboneRouter::BackboneRouterConfig &aConfig) const
-{
-    otError                         error          = OT_ERROR_NOT_FOUND;
-    uint8_t                         serviceData    = ServiceTlv::kServiceDataBackboneRouter;
-    const ServerTlv *               rvalServerTlv  = nullptr;
-    const BackboneRouterServerData *rvalServerData = nullptr;
-    const ServiceTlv *              serviceTlv;
-    const ServerTlv *               serverTlv;
-
-    serviceTlv = Get<Leader>().FindService(ServiceTlv::kThreadEnterpriseNumber, &serviceData, sizeof(serviceData));
-
-    VerifyOrExit(serviceTlv != nullptr, aConfig.mServer16 = Mac::kShortAddrInvalid);
-
-    for (const NetworkDataTlv *start                                                      = serviceTlv->GetSubTlvs();
-         (serverTlv = FindTlv<ServerTlv>(start, serviceTlv->GetNext())) != nullptr; start = serverTlv->GetNext())
-    {
-        const BackboneRouterServerData *serverData =
-            reinterpret_cast<const BackboneRouterServerData *>(serverTlv->GetServerData());
-
-        if (rvalServerTlv == nullptr ||
-            (serverTlv->GetServer16() == Mle::Mle::Rloc16FromRouterId(Get<Mle::MleRouter>().GetLeaderId())) ||
-            serverData->GetSequenceNumber() > rvalServerData->GetSequenceNumber() ||
-            (serverData->GetSequenceNumber() == rvalServerData->GetSequenceNumber() &&
-             serverTlv->GetServer16() > rvalServerTlv->GetServer16()))
-        {
-            rvalServerTlv  = serverTlv;
-            rvalServerData = serverData;
-        }
-    }
-
-    VerifyOrExit(rvalServerTlv != nullptr);
-
-    aConfig.mServer16            = rvalServerTlv->GetServer16();
-    aConfig.mSequenceNumber      = rvalServerData->GetSequenceNumber();
-    aConfig.mReregistrationDelay = rvalServerData->GetReregistrationDelay();
-    aConfig.mMlrTimeout          = rvalServerData->GetMlrTimeout();
-
-    error = OT_ERROR_NONE;
-
-exit:
-    return error;
-}
-#endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
-
 const PrefixTlv *LeaderBase::FindNextMatchingPrefix(const Ip6::Address &aAddress, const PrefixTlv *aPrevTlv) const
 {
     const PrefixTlv *prefixTlv;
@@ -155,7 +110,7 @@
     return prefixTlv;
 }
 
-otError LeaderBase::GetContext(const Ip6::Address &aAddress, Lowpan::Context &aContext) const
+Error LeaderBase::GetContext(const Ip6::Address &aAddress, Lowpan::Context &aContext) const
 {
     const PrefixTlv * prefix = nullptr;
     const ContextTlv *contextTlv;
@@ -186,12 +141,12 @@
         }
     }
 
-    return (aContext.mPrefix.GetLength() > 0) ? OT_ERROR_NONE : OT_ERROR_NOT_FOUND;
+    return (aContext.mPrefix.GetLength() > 0) ? kErrorNone : kErrorNotFound;
 }
 
-otError LeaderBase::GetContext(uint8_t aContextId, Lowpan::Context &aContext) const
+Error LeaderBase::GetContext(uint8_t aContextId, Lowpan::Context &aContext) const
 {
-    otError          error = OT_ERROR_NOT_FOUND;
+    Error            error = kErrorNotFound;
     const PrefixTlv *prefix;
 
     if (aContextId == Mle::kMeshLocalPrefixContextId)
@@ -199,7 +154,7 @@
         aContext.mPrefix.Set(Get<Mle::MleRouter>().GetMeshLocalPrefix());
         aContext.mContextId    = Mle::kMeshLocalPrefixContextId;
         aContext.mCompressFlag = true;
-        ExitNow(error = OT_ERROR_NONE);
+        ExitNow(error = kErrorNone);
     }
 
     for (const NetworkDataTlv *start = GetTlvsStart(); (prefix = FindTlv<PrefixTlv>(start, GetTlvsEnd())) != nullptr;
@@ -215,29 +170,29 @@
         aContext.mPrefix.Set(prefix->GetPrefix(), prefix->GetPrefixLength());
         aContext.mContextId    = contextTlv->GetContextId();
         aContext.mCompressFlag = contextTlv->IsCompress();
-        ExitNow(error = OT_ERROR_NONE);
+        ExitNow(error = kErrorNone);
     }
 
 exit:
     return error;
 }
 
-otError LeaderBase::GetRlocByContextId(uint8_t aContextId, uint16_t &aRloc16) const
+Error LeaderBase::GetRlocByContextId(uint8_t aContextId, uint16_t &aRloc16) const
 {
-    otError         error = OT_ERROR_NOT_FOUND;
+    Error           error = kErrorNotFound;
     Lowpan::Context lowpanContext;
 
-    if ((GetContext(aContextId, lowpanContext)) == OT_ERROR_NONE)
+    if ((GetContext(aContextId, lowpanContext)) == kErrorNone)
     {
         Iterator           iterator = kIteratorInit;
         OnMeshPrefixConfig config;
 
-        while (GetNextOnMeshPrefix(iterator, config) == OT_ERROR_NONE)
+        while (GetNextOnMeshPrefix(iterator, config) == kErrorNone)
         {
             if (lowpanContext.mPrefix.ContainsPrefix(config.GetPrefix()))
             {
                 aRloc16 = config.mRloc16;
-                ExitNow(error = OT_ERROR_NONE);
+                ExitNow(error = kErrorNone);
             }
         }
     }
@@ -280,29 +235,29 @@
     return rval;
 }
 
-otError LeaderBase::RouteLookup(const Ip6::Address &aSource,
-                                const Ip6::Address &aDestination,
-                                uint8_t *           aPrefixMatchLength,
-                                uint16_t *          aRloc16) const
+Error LeaderBase::RouteLookup(const Ip6::Address &aSource,
+                              const Ip6::Address &aDestination,
+                              uint8_t *           aPrefixMatchLength,
+                              uint16_t *          aRloc16) const
 {
-    otError          error  = OT_ERROR_NO_ROUTE;
+    Error            error  = kErrorNoRoute;
     const PrefixTlv *prefix = nullptr;
 
     while ((prefix = FindNextMatchingPrefix(aSource, prefix)) != nullptr)
     {
-        if (ExternalRouteLookup(prefix->GetDomainId(), aDestination, aPrefixMatchLength, aRloc16) == OT_ERROR_NONE)
+        if (ExternalRouteLookup(prefix->GetDomainId(), aDestination, aPrefixMatchLength, aRloc16) == kErrorNone)
         {
-            ExitNow(error = OT_ERROR_NONE);
+            ExitNow(error = kErrorNone);
         }
 
-        if (DefaultRouteLookup(*prefix, aRloc16) == OT_ERROR_NONE)
+        if (DefaultRouteLookup(*prefix, aRloc16) == kErrorNone)
         {
             if (aPrefixMatchLength)
             {
                 *aPrefixMatchLength = 0;
             }
 
-            ExitNow(error = OT_ERROR_NONE);
+            ExitNow(error = kErrorNone);
         }
     }
 
@@ -310,12 +265,12 @@
     return error;
 }
 
-otError LeaderBase::ExternalRouteLookup(uint8_t             aDomainId,
-                                        const Ip6::Address &aDestination,
-                                        uint8_t *           aPrefixMatchLength,
-                                        uint16_t *          aRloc16) const
+Error LeaderBase::ExternalRouteLookup(uint8_t             aDomainId,
+                                      const Ip6::Address &aDestination,
+                                      uint8_t *           aPrefixMatchLength,
+                                      uint16_t *          aRloc16) const
 {
-    otError              error = OT_ERROR_NO_ROUTE;
+    Error                error = kErrorNoRoute;
     const PrefixTlv *    prefixTlv;
     const HasRouteEntry *bestRouteEntry  = nullptr;
     uint8_t              bestMatchLength = 0;
@@ -374,15 +329,15 @@
             *aPrefixMatchLength = bestMatchLength;
         }
 
-        error = OT_ERROR_NONE;
+        error = kErrorNone;
     }
 
     return error;
 }
 
-otError LeaderBase::DefaultRouteLookup(const PrefixTlv &aPrefix, uint16_t *aRloc16) const
+Error LeaderBase::DefaultRouteLookup(const PrefixTlv &aPrefix, uint16_t *aRloc16) const
 {
-    otError                  error = OT_ERROR_NO_ROUTE;
+    Error                    error = kErrorNoRoute;
     const BorderRouterTlv *  borderRouter;
     const BorderRouterEntry *route = nullptr;
 
@@ -416,26 +371,26 @@
             *aRloc16 = route->GetRloc();
         }
 
-        error = OT_ERROR_NONE;
+        error = kErrorNone;
     }
 
     return error;
 }
 
-otError LeaderBase::SetNetworkData(uint8_t        aVersion,
-                                   uint8_t        aStableVersion,
-                                   bool           aStableOnly,
-                                   const Message &aMessage,
-                                   uint16_t       aMessageOffset)
+Error LeaderBase::SetNetworkData(uint8_t        aVersion,
+                                 uint8_t        aStableVersion,
+                                 bool           aStableOnly,
+                                 const Message &aMessage,
+                                 uint16_t       aMessageOffset)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     Mle::Tlv tlv;
     uint16_t length;
 
     SuccessOrExit(error = aMessage.Read(aMessageOffset, tlv));
 
     length = aMessage.ReadBytes(aMessageOffset + sizeof(tlv), mTlvs, tlv.GetLength());
-    VerifyOrExit(length == tlv.GetLength(), error = OT_ERROR_PARSE);
+    VerifyOrExit(length == tlv.GetLength(), error = kErrorParse);
 
     mLength        = tlv.GetLength();
     mVersion       = aVersion;
@@ -462,19 +417,19 @@
     return error;
 }
 
-otError LeaderBase::SetCommissioningData(const uint8_t *aValue, uint8_t aValueLength)
+Error LeaderBase::SetCommissioningData(const uint8_t *aValue, uint8_t aValueLength)
 {
-    otError               error = OT_ERROR_NONE;
+    Error                 error = kErrorNone;
     CommissioningDataTlv *commissioningDataTlv;
 
     RemoveCommissioningData();
 
     if (aValueLength > 0)
     {
-        VerifyOrExit(aValueLength <= kMaxSize - sizeof(CommissioningDataTlv), error = OT_ERROR_NO_BUFS);
+        VerifyOrExit(aValueLength <= kMaxSize - sizeof(CommissioningDataTlv), error = kErrorNoBufs);
         commissioningDataTlv =
             static_cast<CommissioningDataTlv *>(AppendTlv(sizeof(CommissioningDataTlv) + aValueLength));
-        VerifyOrExit(commissioningDataTlv != nullptr, error = OT_ERROR_NO_BUFS);
+        VerifyOrExit(commissioningDataTlv != nullptr, error = kErrorNoBufs);
 
         commissioningDataTlv->Init();
         commissioningDataTlv->SetLength(aValueLength);
@@ -540,24 +495,24 @@
     return;
 }
 
-otError LeaderBase::SteeringDataCheck(const FilterIndexes &aFilterIndexes) const
+Error LeaderBase::SteeringDataCheck(const FilterIndexes &aFilterIndexes) const
 {
-    otError               error = OT_ERROR_NONE;
+    Error                 error = kErrorNone;
     const MeshCoP::Tlv *  steeringDataTlv;
     MeshCoP::SteeringData steeringData;
 
     steeringDataTlv = GetCommissioningDataSubTlv(MeshCoP::Tlv::kSteeringData);
-    VerifyOrExit(steeringDataTlv != nullptr, error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(steeringDataTlv != nullptr, error = kErrorInvalidState);
 
     static_cast<const MeshCoP::SteeringDataTlv *>(steeringDataTlv)->CopyTo(steeringData);
 
-    VerifyOrExit(steeringData.Contains(aFilterIndexes), error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(steeringData.Contains(aFilterIndexes), error = kErrorNotFound);
 
 exit:
     return error;
 }
 
-otError LeaderBase::SteeringDataCheckJoiner(const Mac::ExtAddress &aEui64) const
+Error LeaderBase::SteeringDataCheckJoiner(const Mac::ExtAddress &aEui64) const
 {
     FilterIndexes   filterIndexes;
     Mac::ExtAddress joinerId;
@@ -568,7 +523,7 @@
     return SteeringDataCheck(filterIndexes);
 }
 
-otError LeaderBase::SteeringDataCheckJoiner(const MeshCoP::JoinerDiscerner &aDiscerner) const
+Error LeaderBase::SteeringDataCheckJoiner(const MeshCoP::JoinerDiscerner &aDiscerner) const
 {
     FilterIndexes filterIndexes;
 
diff --git a/src/core/thread/network_data_leader.hpp b/src/core/thread/network_data_leader.hpp
index f869125..baf93d6 100644
--- a/src/core/thread/network_data_leader.hpp
+++ b/src/core/thread/network_data_leader.hpp
@@ -38,10 +38,6 @@
 
 #include <stdint.h>
 
-#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
-#include "backbone_router/bbr_leader.hpp"
-#endif
-
 #include "coap/coap.hpp"
 #include "common/timer.hpp"
 #include "net/ip6_address.hpp"
@@ -105,11 +101,11 @@
      * @param[in]   aAddress  A reference to an IPv6 address.
      * @param[out]  aContext  A reference to 6LoWPAN Context information.
      *
-     * @retval OT_ERROR_NONE       Successfully retrieved 6LoWPAN Context information.
-     * @retval OT_ERROR_NOT_FOUND  Could not find the 6LoWPAN Context information.
+     * @retval kErrorNone       Successfully retrieved 6LoWPAN Context information.
+     * @retval kErrorNotFound   Could not find the 6LoWPAN Context information.
      *
      */
-    otError GetContext(const Ip6::Address &aAddress, Lowpan::Context &aContext) const;
+    Error GetContext(const Ip6::Address &aAddress, Lowpan::Context &aContext) const;
 
     /**
      * This method retrieves the 6LoWPAN Context information based on a given Context ID.
@@ -117,11 +113,11 @@
      * @param[in]   aContextId  The Context ID value.
      * @param[out]  aContext    A reference to the 6LoWPAN Context information.
      *
-     * @retval OT_ERROR_NONE       Successfully retrieved 6LoWPAN Context information.
-     * @retval OT_ERROR_NOT_FOUND  Could not find the 6LoWPAN Context information.
+     * @retval kErrorNone       Successfully retrieved 6LoWPAN Context information.
+     * @retval kErrorNotFound   Could not find the 6LoWPAN Context information.
      *
      */
-    otError GetContext(uint8_t aContextId, Lowpan::Context &aContext) const;
+    Error GetContext(uint8_t aContextId, Lowpan::Context &aContext) const;
 
     /**
      * This method indicates whether or not the given IPv6 address is on-mesh.
@@ -142,14 +138,14 @@
      * @param[out]  aPrefixMatchLength  A pointer to output the longest prefix match length in bits.
      * @param[out]  aRloc16             A pointer to the RLOC16 for the selected route.
      *
-     * @retval OT_ERROR_NONE      Successfully found a route.
-     * @retval OT_ERROR_NO_ROUTE  No valid route was found.
+     * @retval kErrorNone      Successfully found a route.
+     * @retval kErrorNoRoute   No valid route was found.
      *
      */
-    otError RouteLookup(const Ip6::Address &aSource,
-                        const Ip6::Address &aDestination,
-                        uint8_t *           aPrefixMatchLength,
-                        uint16_t *          aRloc16) const;
+    Error RouteLookup(const Ip6::Address &aSource,
+                      const Ip6::Address &aDestination,
+                      uint8_t *           aPrefixMatchLength,
+                      uint16_t *          aRloc16) const;
 
     /**
      * This method is used by non-Leader devices to set newly received Network Data from the Leader.
@@ -160,15 +156,15 @@
      * @param[in]  aMessage        A reference to the MLE message.
      * @param[in]  aMessageOffset  The offset in @p aMessage for the Network Data TLV.
      *
-     * @retval OT_ERROR_NONE   Successfully set the network data.
-     * @retval OT_ERROR_PARSE  Network Data TLV in @p aMessage is not valid.
+     * @retval kErrorNone   Successfully set the network data.
+     * @retval kErrorParse  Network Data TLV in @p aMessage is not valid.
      *
      */
-    otError SetNetworkData(uint8_t        aVersion,
-                           uint8_t        aStableVersion,
-                           bool           aStableOnly,
-                           const Message &aMessage,
-                           uint16_t       aMessageOffset);
+    Error SetNetworkData(uint8_t        aVersion,
+                         uint8_t        aStableVersion,
+                         bool           aStableOnly,
+                         const Message &aMessage,
+                         uint16_t       aMessageOffset);
 
     /**
      * This method returns a pointer to the Commissioning Data.
@@ -228,35 +224,35 @@
      * @param[in]  aValue        A pointer to the Commissioning Data value.
      * @param[in]  aValueLength  The length of @p aValue.
      *
-     * @retval OT_ERROR_NONE     Successfully added the Commissioning Data.
-     * @retval OT_ERROR_NO_BUFS  Insufficient space to add the Commissioning Data.
+     * @retval kErrorNone     Successfully added the Commissioning Data.
+     * @retval kErrorNoBufs   Insufficient space to add the Commissioning Data.
      *
      */
-    otError SetCommissioningData(const uint8_t *aValue, uint8_t aValueLength);
+    Error SetCommissioningData(const uint8_t *aValue, uint8_t aValueLength);
 
     /**
      * This method checks if the steering data includes a Joiner.
      *
      * @param[in]  aEui64             A reference to the Joiner's IEEE EUI-64.
      *
-     * @retval OT_ERROR_NONE          @p aEui64 is in the bloom filter.
-     * @retval OT_ERROR_INVALID_STATE No steering data present.
-     * @retval OT_ERROR_NOT_FOUND     @p aEui64 is not in the bloom filter.
+     * @retval kErrorNone          @p aEui64 is in the bloom filter.
+     * @retval kErrorInvalidState  No steering data present.
+     * @retval kErrorNotFound      @p aEui64 is not in the bloom filter.
      *
      */
-    otError SteeringDataCheckJoiner(const Mac::ExtAddress &aEui64) const;
+    Error SteeringDataCheckJoiner(const Mac::ExtAddress &aEui64) const;
 
     /**
      * This method checks if the steering data includes a Joiner with a given discerner value.
      *
      * @param[in]  aDiscerner         A reference to the Joiner Discerner.
      *
-     * @retval OT_ERROR_NONE          @p aDiscerner is in the bloom filter.
-     * @retval OT_ERROR_INVALID_STATE No steering data present.
-     * @retval OT_ERROR_NOT_FOUND     @p aDiscerner is not in the bloom filter.
+     * @retval kErrorNone          @p aDiscerner is in the bloom filter.
+     * @retval kErrorInvalidState  No steering data present.
+     * @retval kErrorNotFound      @p aDiscerner is not in the bloom filter.
      *
      */
-    otError SteeringDataCheckJoiner(const MeshCoP::JoinerDiscerner &aDiscerner) const;
+    Error SteeringDataCheckJoiner(const MeshCoP::JoinerDiscerner &aDiscerner) const;
 
     /**
      * This method gets the Rloc of Dhcp Agent of specified contextId.
@@ -264,11 +260,11 @@
      * @param[in]  aContextId      A pointer to the Commissioning Data value.
      * @param[out] aRloc16         The reference of which for output the Rloc16.
      *
-     * @retval OT_ERROR_NONE       Successfully get the Rloc of Dhcp Agent.
-     * @retval OT_ERROR_NOT_FOUND  The specified @p aContextId could not be found.
+     * @retval kErrorNone       Successfully get the Rloc of Dhcp Agent.
+     * @retval kErrorNotFound   The specified @p aContextId could not be found.
      *
      */
-    otError GetRlocByContextId(uint8_t aContextId, uint16_t &aRloc16) const;
+    Error GetRlocByContextId(uint8_t aContextId, uint16_t &aRloc16) const;
 
     /**
      * This method gets the Service ID for the specified service.
@@ -279,28 +275,15 @@
      * @param[in]  aServerStable      The Stable flag value for Server TLV
      * @param[out] aServiceId         A reference where to put the Service ID.
      *
-     * @retval OT_ERROR_NONE       Successfully got the Service ID.
-     * @retval OT_ERROR_NOT_FOUND  The specified service was not found.
+     * @retval kErrorNone       Successfully got the Service ID.
+     * @retval kErrorNotFound   The specified service was not found.
      *
      */
-    otError GetServiceId(uint32_t       aEnterpriseNumber,
-                         const uint8_t *aServiceData,
-                         uint8_t        aServiceDataLength,
-                         bool           aServerStable,
-                         uint8_t &      aServiceId) const;
-
-#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
-    /**
-     * This method gets the Primary Backbone Router (PBBR) in the Thread Network.
-     *
-     * @param[out]  aConfig      The Primary Backbone Router configuration.
-     *
-     * @retval OT_ERROR_NONE       Successfully got the Primary Backbone Router configuration.
-     * @retval OT_ERROR_NOT_FOUND  No Backbone Router Service in the Thread Network.
-     *
-     */
-    otError GetBackboneRouterPrimary(BackboneRouter::BackboneRouterConfig &aConfig) const;
-#endif
+    Error GetServiceId(uint32_t       aEnterpriseNumber,
+                       const uint8_t *aServiceData,
+                       uint8_t        aServiceDataLength,
+                       bool           aServerStable,
+                       uint8_t &      aServiceId) const;
 
 protected:
     uint8_t mStableVersion;
@@ -313,12 +296,12 @@
 
     void RemoveCommissioningData(void);
 
-    otError ExternalRouteLookup(uint8_t             aDomainId,
-                                const Ip6::Address &aDestination,
-                                uint8_t *           aPrefixMatchLength,
-                                uint16_t *          aRloc16) const;
-    otError DefaultRouteLookup(const PrefixTlv &aPrefix, uint16_t *aRloc16) const;
-    otError SteeringDataCheck(const FilterIndexes &aFilterIndexes) const;
+    Error ExternalRouteLookup(uint8_t             aDomainId,
+                              const Ip6::Address &aDestination,
+                              uint8_t *           aPrefixMatchLength,
+                              uint16_t *          aRloc16) const;
+    Error DefaultRouteLookup(const PrefixTlv &aPrefix, uint16_t *aRloc16) const;
+    Error SteeringDataCheck(const FilterIndexes &aFilterIndexes) const;
 };
 
 /**
diff --git a/src/core/thread/network_data_leader_ftd.cpp b/src/core/thread/network_data_leader_ftd.cpp
index c07db93..f3fc965 100644
--- a/src/core/thread/network_data_leader_ftd.cpp
+++ b/src/core/thread/network_data_leader_ftd.cpp
@@ -57,7 +57,7 @@
 
 Leader::Leader(Instance &aInstance)
     : LeaderBase(aInstance)
-    , mTimer(aInstance, Leader::HandleTimer, this)
+    , mTimer(aInstance, Leader::HandleTimer)
     , mServerData(UriPath::kServerData, &Leader::HandleServerData, this)
     , mCommissioningDataGet(UriPath::kCommissionerGet, &Leader::HandleCommissioningGet, this)
     , mCommissioningDataSet(UriPath::kCommissionerSet, &Leader::HandleCommissioningSet, this)
@@ -148,16 +148,16 @@
 
     switch (Tlv::Find<ThreadRloc16Tlv>(aMessage, rloc16))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         RemoveBorderRouter(rloc16, kMatchModeRloc16);
         break;
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         break;
     default:
         ExitNow();
     }
 
-    if (Tlv::FindTlv(aMessage, networkData) == OT_ERROR_NONE)
+    if (Tlv::FindTlv(aMessage, networkData) == kErrorNone)
     {
         VerifyOrExit(networkData.IsValid());
         RegisterNetworkData(aMessageInfo.GetPeerAddr().GetIid().GetLocator(), networkData.GetTlvs(),
@@ -298,13 +298,13 @@
                                           uint16_t                aLength,
                                           const Ip6::MessageInfo &aMessageInfo)
 {
-    otError               error = OT_ERROR_NONE;
+    Error                 error = kErrorNone;
     Coap::Message *       message;
     CommissioningDataTlv *commDataTlv;
     uint8_t *             data   = nullptr;
     uint8_t               length = 0;
 
-    VerifyOrExit((message = MeshCoP::NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = MeshCoP::NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->SetDefaultResponseHeader(aRequest));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -317,7 +317,7 @@
         length = commDataTlv->GetLength();
     }
 
-    VerifyOrExit(data && length, error = OT_ERROR_DROP);
+    VerifyOrExit(data && length, error = kErrorDrop);
 
     if (aLength == 0)
     {
@@ -361,10 +361,10 @@
                                           const Ip6::MessageInfo & aMessageInfo,
                                           MeshCoP::StateTlv::State aState)
 {
-    otError        error = OT_ERROR_NONE;
+    Error          error = kErrorNone;
     Coap::Message *message;
 
-    VerifyOrExit((message = MeshCoP::NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = MeshCoP::NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->SetDefaultResponseHeader(aRequest));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -397,20 +397,20 @@
     return matched;
 }
 
-otError Leader::Validate(const uint8_t *aTlvs, uint8_t aTlvsLength, uint16_t aRloc16)
+Error Leader::Validate(const uint8_t *aTlvs, uint8_t aTlvsLength, uint16_t aRloc16)
 {
     // Validate that the `aTlvs` contains well-formed TLVs, sub-TLVs,
     // and entries all matching `aRloc16` (no other entry for other
     // RLOCs and no duplicates TLVs).
 
-    otError               error = OT_ERROR_NONE;
+    Error                 error = kErrorNone;
     const NetworkDataTlv *end   = reinterpret_cast<const NetworkDataTlv *>(aTlvs + aTlvsLength);
 
     for (const NetworkDataTlv *cur = reinterpret_cast<const NetworkDataTlv *>(aTlvs); cur < end; cur = cur->GetNext())
     {
         uint8_t offset;
 
-        VerifyOrExit((cur + 1) <= end && cur->GetNext() <= end, error = OT_ERROR_PARSE);
+        VerifyOrExit((cur + 1) <= end && cur->GetNext() <= end, error = kErrorParse);
 
         offset = static_cast<uint8_t>(reinterpret_cast<const uint8_t *>(cur) - aTlvs);
 
@@ -420,11 +420,11 @@
         {
             const PrefixTlv *prefix = static_cast<const PrefixTlv *>(cur);
 
-            VerifyOrExit(prefix->IsValid(), error = OT_ERROR_PARSE);
+            VerifyOrExit(prefix->IsValid(), error = kErrorParse);
 
             // Ensure there is no duplicate Prefix TLVs with same prefix.
             VerifyOrExit(FindPrefix(prefix->GetPrefix(), prefix->GetPrefixLength(), aTlvs, offset) == nullptr,
-                         error = OT_ERROR_PARSE);
+                         error = kErrorParse);
 
             SuccessOrExit(error = ValidatePrefix(*prefix, aRloc16));
             break;
@@ -434,13 +434,13 @@
         {
             const ServiceTlv *service = static_cast<const ServiceTlv *>(cur);
 
-            VerifyOrExit(service->IsValid(), error = OT_ERROR_PARSE);
+            VerifyOrExit(service->IsValid(), error = kErrorParse);
 
             // Ensure there is no duplicate Service TLV with same
             // Enterprise Number and Service Data.
             VerifyOrExit(FindService(service->GetEnterpriseNumber(), service->GetServiceData(),
                                      service->GetServiceDataLength(), aTlvs, offset) == nullptr,
-                         error = OT_ERROR_PARSE);
+                         error = kErrorParse);
 
             SuccessOrExit(error = ValidateService(*service, aRloc16));
             break;
@@ -455,13 +455,13 @@
     return error;
 }
 
-otError Leader::ValidatePrefix(const PrefixTlv &aPrefix, uint16_t aRloc16)
+Error Leader::ValidatePrefix(const PrefixTlv &aPrefix, uint16_t aRloc16)
 {
     // Validate that `aPrefix` TLV contains well-formed sub-TLVs and
     // and entries all matching `aRloc16` (no other entry for other
     // RLOCs).
 
-    otError               error                   = OT_ERROR_PARSE;
+    Error                 error                   = kErrorParse;
     const NetworkDataTlv *subEnd                  = aPrefix.GetNext();
     bool                  foundTempHasRoute       = false;
     bool                  foundStableHasRoute     = false;
@@ -529,19 +529,19 @@
 
     if (foundStableBorderRouter || foundTempBorderRouter || foundStableHasRoute || foundTempHasRoute)
     {
-        error = OT_ERROR_NONE;
+        error = kErrorNone;
     }
 
 exit:
     return error;
 }
 
-otError Leader::ValidateService(const ServiceTlv &aService, uint16_t aRloc16)
+Error Leader::ValidateService(const ServiceTlv &aService, uint16_t aRloc16)
 {
     // Validate that `aService` TLV contains a single well-formed
     // Server sub-TLV associated with `aRloc16`.
 
-    otError               error       = OT_ERROR_PARSE;
+    Error                 error       = kErrorParse;
     const NetworkDataTlv *subEnd      = aService.GetNext();
     bool                  foundServer = false;
 
@@ -569,7 +569,7 @@
 
     if (foundServer)
     {
-        error = OT_ERROR_NONE;
+        error = kErrorNone;
     }
 
 exit:
@@ -699,11 +699,11 @@
 
 void Leader::RegisterNetworkData(uint16_t aRloc16, const uint8_t *aTlvs, uint8_t aTlvsLength)
 {
-    otError               error = OT_ERROR_NONE;
+    Error                 error = kErrorNone;
     const NetworkDataTlv *end   = reinterpret_cast<const NetworkDataTlv *>(aTlvs + aTlvsLength);
     ChangedFlags          flags;
 
-    VerifyOrExit(Get<RouterTable>().IsAllocated(Mle::Mle::RouterIdFromRloc16(aRloc16)), error = OT_ERROR_NO_ROUTE);
+    VerifyOrExit(Get<RouterTable>().IsAllocated(Mle::Mle::RouterIdFromRloc16(aRloc16)), error = kErrorNoRoute);
 
     // Validate that the `aTlvs` contains well-formed TLVs, sub-TLVs,
     // and entries all matching `aRloc16` (no other RLOCs).
@@ -737,21 +737,21 @@
 
 exit:
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogNoteNetData("Failed to register network data: %s", otThreadErrorToString(error));
+        otLogNoteNetData("Failed to register network data: %s", ErrorToString(error));
     }
 }
 
-otError Leader::AddPrefix(const PrefixTlv &aPrefix, ChangedFlags &aChangedFlags)
+Error Leader::AddPrefix(const PrefixTlv &aPrefix, ChangedFlags &aChangedFlags)
 {
-    otError    error     = OT_ERROR_NONE;
+    Error      error     = kErrorNone;
     PrefixTlv *dstPrefix = FindPrefix(aPrefix.GetPrefix(), aPrefix.GetPrefixLength());
 
     if (dstPrefix == nullptr)
     {
         dstPrefix = static_cast<PrefixTlv *>(AppendTlv(PrefixTlv::CalculateSize(aPrefix.GetPrefixLength())));
-        VerifyOrExit(dstPrefix != nullptr, error = OT_ERROR_NO_BUFS);
+        VerifyOrExit(dstPrefix != nullptr, error = kErrorNoBufs);
 
         dstPrefix->Init(aPrefix.GetDomainId(), aPrefix.GetPrefixLength(), aPrefix.GetPrefix());
     }
@@ -789,9 +789,9 @@
     return error;
 }
 
-otError Leader::AddService(const ServiceTlv &aService, ChangedFlags &aChangedFlags)
+Error Leader::AddService(const ServiceTlv &aService, ChangedFlags &aChangedFlags)
 {
-    otError     error = OT_ERROR_NONE;
+    Error       error = kErrorNone;
     ServiceTlv *dstService =
         FindService(aService.GetEnterpriseNumber(), aService.GetServiceData(), aService.GetServiceDataLength());
     const ServerTlv *server;
@@ -804,7 +804,7 @@
 
         dstService = static_cast<ServiceTlv *>(
             AppendTlv(ServiceTlv::CalculateSize(aService.GetEnterpriseNumber(), aService.GetServiceDataLength())));
-        VerifyOrExit(dstService != nullptr, error = OT_ERROR_NO_BUFS);
+        VerifyOrExit(dstService != nullptr, error = kErrorNoBufs);
 
         dstService->Init(serviceId, aService.GetEnterpriseNumber(), aService.GetServiceData(),
                          aService.GetServiceDataLength());
@@ -830,16 +830,16 @@
     return error;
 }
 
-otError Leader::AddHasRoute(const HasRouteTlv &aHasRoute, PrefixTlv &aDstPrefix, ChangedFlags &aChangedFlags)
+Error Leader::AddHasRoute(const HasRouteTlv &aHasRoute, PrefixTlv &aDstPrefix, ChangedFlags &aChangedFlags)
 {
-    otError              error       = OT_ERROR_NONE;
+    Error                error       = kErrorNone;
     HasRouteTlv *        dstHasRoute = FindHasRoute(aDstPrefix, aHasRoute.IsStable());
     const HasRouteEntry *entry       = aHasRoute.GetFirstEntry();
 
     if (dstHasRoute == nullptr)
     {
         // Ensure there is space for `HasRouteTlv` and a single entry.
-        VerifyOrExit(CanInsert(sizeof(HasRouteTlv) + sizeof(HasRouteEntry)), error = OT_ERROR_NO_BUFS);
+        VerifyOrExit(CanInsert(sizeof(HasRouteTlv) + sizeof(HasRouteEntry)), error = kErrorNoBufs);
 
         dstHasRoute = static_cast<HasRouteTlv *>(aDstPrefix.GetNext());
         Insert(dstHasRoute, sizeof(HasRouteTlv));
@@ -854,7 +854,7 @@
 
     VerifyOrExit(!ContainsMatchingEntry(dstHasRoute, *entry));
 
-    VerifyOrExit(CanInsert(sizeof(HasRouteEntry)), error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(CanInsert(sizeof(HasRouteEntry)), error = kErrorNoBufs);
 
     Insert(dstHasRoute->GetNext(), sizeof(HasRouteEntry));
     dstHasRoute->IncreaseLength(sizeof(HasRouteEntry));
@@ -867,11 +867,9 @@
     return error;
 }
 
-otError Leader::AddBorderRouter(const BorderRouterTlv &aBorderRouter,
-                                PrefixTlv &            aDstPrefix,
-                                ChangedFlags &         aChangedFlags)
+Error Leader::AddBorderRouter(const BorderRouterTlv &aBorderRouter, PrefixTlv &aDstPrefix, ChangedFlags &aChangedFlags)
 {
-    otError                  error           = OT_ERROR_NONE;
+    Error                    error           = kErrorNone;
     BorderRouterTlv *        dstBorderRouter = FindBorderRouter(aDstPrefix, aBorderRouter.IsStable());
     ContextTlv *             dstContext      = FindContext(aDstPrefix);
     uint8_t                  contextId       = 0;
@@ -891,7 +889,7 @@
         // and a `ContextTlv` (if not already present).
         VerifyOrExit(CanInsert(sizeof(BorderRouterTlv) + sizeof(BorderRouterEntry) +
                                ((dstContext == nullptr) ? sizeof(ContextTlv) : 0)),
-                     error = OT_ERROR_NO_BUFS);
+                     error = kErrorNoBufs);
 
         dstBorderRouter = static_cast<BorderRouterTlv *>(aDstPrefix.GetNext());
         Insert(dstBorderRouter, sizeof(BorderRouterTlv));
@@ -907,7 +905,7 @@
     if (dstContext == nullptr)
     {
         // Ensure there is space for a `ContextTlv` and a single entry.
-        VerifyOrExit(CanInsert(sizeof(BorderRouterEntry) + sizeof(ContextTlv)), error = OT_ERROR_NO_BUFS);
+        VerifyOrExit(CanInsert(sizeof(BorderRouterEntry) + sizeof(ContextTlv)), error = kErrorNoBufs);
 
         dstContext = static_cast<ContextTlv *>(aDstPrefix.GetNext());
         Insert(dstContext, sizeof(ContextTlv));
@@ -925,7 +923,7 @@
 
     VerifyOrExit(!ContainsMatchingEntry(dstBorderRouter, *entry));
 
-    VerifyOrExit(CanInsert(sizeof(BorderRouterEntry)), error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(CanInsert(sizeof(BorderRouterEntry)), error = kErrorNoBufs);
 
     Insert(dstBorderRouter->GetNext(), sizeof(BorderRouterEntry));
     dstBorderRouter->IncreaseLength(sizeof(BorderRouterEntry));
@@ -937,15 +935,15 @@
     return error;
 }
 
-otError Leader::AddServer(const ServerTlv &aServer, ServiceTlv &aDstService, ChangedFlags &aChangedFlags)
+Error Leader::AddServer(const ServerTlv &aServer, ServiceTlv &aDstService, ChangedFlags &aChangedFlags)
 {
-    otError    error = OT_ERROR_NONE;
+    Error      error = kErrorNone;
     ServerTlv *dstServer;
     uint8_t    tlvSize = aServer.GetSize();
 
     VerifyOrExit(!ContainsMatchingServer(&aDstService, aServer));
 
-    VerifyOrExit(CanInsert(tlvSize), error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(CanInsert(tlvSize), error = kErrorNoBufs);
 
     dstServer = static_cast<ServerTlv *>(aDstService.GetNext());
     Insert(dstServer, tlvSize);
@@ -963,9 +961,9 @@
     return error;
 }
 
-otError Leader::AllocateServiceId(uint8_t &aServiceId) const
+Error Leader::AllocateServiceId(uint8_t &aServiceId) const
 {
-    otError error = OT_ERROR_NOT_FOUND;
+    Error   error = kErrorNotFound;
     uint8_t serviceId;
 
     for (serviceId = Mle::kServiceMinId; serviceId <= Mle::kServiceMaxId; serviceId++)
@@ -973,7 +971,7 @@
         if (FindServiceById(serviceId) == nullptr)
         {
             aServiceId = serviceId;
-            error      = OT_ERROR_NONE;
+            error      = kErrorNone;
             otLogInfoNetData("Allocated Service ID = %d", serviceId);
             break;
         }
@@ -1001,9 +999,9 @@
     return service;
 }
 
-otError Leader::AllocateContextId(uint8_t &aContextId)
+Error Leader::AllocateContextId(uint8_t &aContextId)
 {
-    otError error = OT_ERROR_NOT_FOUND;
+    Error error = kErrorNotFound;
 
     for (uint8_t contextId = kMinContextId; contextId < kMinContextId + kNumContextIds; contextId++)
     {
@@ -1011,7 +1009,7 @@
         {
             mContextUsed |= (1 << contextId);
             aContextId = contextId;
-            error      = OT_ERROR_NONE;
+            error      = kErrorNone;
             otLogInfoNetData("Allocated Context ID = %d", contextId);
             break;
         }
@@ -1331,7 +1329,7 @@
 
 void Leader::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<Leader>().HandleTimer();
+    aTimer.Get<Leader>().HandleTimer();
 }
 
 void Leader::HandleTimer(void)
@@ -1361,15 +1359,15 @@
     }
 }
 
-otError Leader::RemoveStaleChildEntries(Coap::ResponseHandler aHandler, void *aContext)
+Error Leader::RemoveStaleChildEntries(Coap::ResponseHandler aHandler, void *aContext)
 {
-    otError  error    = OT_ERROR_NOT_FOUND;
+    Error    error    = kErrorNotFound;
     Iterator iterator = kIteratorInit;
     uint16_t rloc16;
 
     VerifyOrExit(Get<Mle::MleRouter>().IsRouterOrLeader());
 
-    while (GetNextServer(iterator, rloc16) == OT_ERROR_NONE)
+    while (GetNextServer(iterator, rloc16) == kErrorNone)
     {
         if (!Mle::Mle::IsActiveRouter(rloc16) && Mle::Mle::RouterIdMatch(Get<Mle::MleRouter>().GetRloc16(), rloc16) &&
             Get<ChildTable>().FindChild(rloc16, Child::kInStateValid) == nullptr)
diff --git a/src/core/thread/network_data_leader_ftd.hpp b/src/core/thread/network_data_leader_ftd.hpp
index fe5657f..0643947 100644
--- a/src/core/thread/network_data_leader_ftd.hpp
+++ b/src/core/thread/network_data_leader_ftd.hpp
@@ -164,12 +164,12 @@
      * @param[in]  aHandler  A function pointer that is called when the transaction ends.
      * @param[in]  aContext  A pointer to arbitrary context information.
      *
-     * @retval OT_ERROR_NONE       A stale child entry was found and successfully enqueued a SVR_DATA.ntf message.
-     * @retval OT_ERROR_NO_BUFS    A stale child entry was found, but insufficient message buffers were available.
-     * @retval OT_ERROR_NOT_FOUND  No stale child entries were found.
+     * @retval kErrorNone      A stale child entry was found and successfully enqueued a SVR_DATA.ntf message.
+     * @retval kErrorNoBufs    A stale child entry was found, but insufficient message buffers were available.
+     * @retval kErrorNotFound  No stale child entries were found.
      *
      */
-    otError RemoveStaleChildEntries(Coap::ResponseHandler aHandler, void *aContext);
+    Error RemoveStaleChildEntries(Coap::ResponseHandler aHandler, void *aContext);
 
 private:
     class ChangedFlags
@@ -209,18 +209,18 @@
 
     void RegisterNetworkData(uint16_t aRloc16, const uint8_t *aTlvs, uint8_t aTlvsLength);
 
-    otError AddPrefix(const PrefixTlv &aPrefix, ChangedFlags &aChangedFlags);
-    otError AddHasRoute(const HasRouteTlv &aHasRoute, PrefixTlv &aDstPrefix, ChangedFlags &aChangedFlags);
-    otError AddBorderRouter(const BorderRouterTlv &aBorderRouter, PrefixTlv &aDstPrefix, ChangedFlags &aChangedFlags);
-    otError AddService(const ServiceTlv &aService, ChangedFlags &aChangedFlags);
-    otError AddServer(const ServerTlv &aServer, ServiceTlv &aDstService, ChangedFlags &aChangedFlags);
+    Error AddPrefix(const PrefixTlv &aPrefix, ChangedFlags &aChangedFlags);
+    Error AddHasRoute(const HasRouteTlv &aHasRoute, PrefixTlv &aDstPrefix, ChangedFlags &aChangedFlags);
+    Error AddBorderRouter(const BorderRouterTlv &aBorderRouter, PrefixTlv &aDstPrefix, ChangedFlags &aChangedFlags);
+    Error AddService(const ServiceTlv &aService, ChangedFlags &aChangedFlags);
+    Error AddServer(const ServerTlv &aServer, ServiceTlv &aDstService, ChangedFlags &aChangedFlags);
 
-    otError AllocateServiceId(uint8_t &aServiceId) const;
+    Error AllocateServiceId(uint8_t &aServiceId) const;
 
-    otError AllocateContextId(uint8_t &aContextId);
-    void    FreeContextId(uint8_t aContextId);
-    void    StartContextReuseTimer(uint8_t aContextId);
-    void    StopContextReuseTimer(uint8_t aContextId);
+    Error AllocateContextId(uint8_t &aContextId);
+    void  FreeContextId(uint8_t aContextId);
+    void  StartContextReuseTimer(uint8_t aContextId);
+    void  StopContextReuseTimer(uint8_t aContextId);
 
     void RemoveContext(uint8_t aContextId);
     void RemoveContext(PrefixTlv &aPrefix, uint8_t aContextId);
@@ -258,9 +258,9 @@
 
     static bool RlocMatch(uint16_t aFirstRloc16, uint16_t aSecondRloc16, MatchMode aMatchMode);
 
-    static otError Validate(const uint8_t *aTlvs, uint8_t aTlvsLength, uint16_t aRloc16);
-    static otError ValidatePrefix(const PrefixTlv &aPrefix, uint16_t aRloc16);
-    static otError ValidateService(const ServiceTlv &aService, uint16_t aRloc16);
+    static Error Validate(const uint8_t *aTlvs, uint8_t aTlvsLength, uint16_t aRloc16);
+    static Error ValidatePrefix(const PrefixTlv &aPrefix, uint16_t aRloc16);
+    static Error ValidateService(const ServiceTlv &aService, uint16_t aRloc16);
 
     static bool ContainsMatchingEntry(const PrefixTlv *aPrefix, bool aStable, const HasRouteEntry &aEntry);
     static bool ContainsMatchingEntry(const HasRouteTlv *aHasRoute, const HasRouteEntry &aEntry);
diff --git a/src/core/thread/network_data_local.cpp b/src/core/thread/network_data_local.cpp
index 04e20dd..bef1b26 100644
--- a/src/core/thread/network_data_local.cpp
+++ b/src/core/thread/network_data_local.cpp
@@ -54,20 +54,20 @@
 }
 
 #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
-otError Local::AddOnMeshPrefix(const OnMeshPrefixConfig &aConfig)
+Error Local::AddOnMeshPrefix(const OnMeshPrefixConfig &aConfig)
 {
-    otError  error;
+    Error    error;
     uint16_t flags = 0;
 
     // Add Prefix validation check:
     // Thread 1.1 Specification 5.13.2 says
     // "A valid prefix MUST NOT allow both DHCPv6 and SLAAC for address configuration"
-    VerifyOrExit(!aConfig.mDhcp || !aConfig.mSlaac, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(!aConfig.mDhcp || !aConfig.mSlaac, error = kErrorInvalidArgs);
 
     // RFC 4944 Section 6 says:
     // An IPv6 address prefix used for stateless autoconfiguration [RFC4862]
     // of an IEEE 802.15.4 interface MUST have a length of 64 bits.
-    VerifyOrExit(!aConfig.mSlaac || aConfig.mPrefix.mLength == OT_IP6_PREFIX_BITSIZE, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(!aConfig.mSlaac || aConfig.mPrefix.mLength == OT_IP6_PREFIX_BITSIZE, error = kErrorInvalidArgs);
 
     if (aConfig.mPreferred)
     {
@@ -118,33 +118,33 @@
     return error;
 }
 
-otError Local::RemoveOnMeshPrefix(const Ip6::Prefix &aPrefix)
+Error Local::RemoveOnMeshPrefix(const Ip6::Prefix &aPrefix)
 {
     return RemovePrefix(aPrefix, NetworkDataTlv::kTypeBorderRouter);
 }
 
-otError Local::AddHasRoutePrefix(const ExternalRouteConfig &aConfig)
+Error Local::AddHasRoutePrefix(const ExternalRouteConfig &aConfig)
 {
     return AddPrefix(aConfig.GetPrefix(), NetworkDataTlv::kTypeHasRoute, aConfig.mPreference, /* aFlags */ 0,
                      aConfig.mStable);
 }
 
-otError Local::RemoveHasRoutePrefix(const Ip6::Prefix &aPrefix)
+Error Local::RemoveHasRoutePrefix(const Ip6::Prefix &aPrefix)
 {
     return RemovePrefix(aPrefix, NetworkDataTlv::kTypeHasRoute);
 }
 
-otError Local::AddPrefix(const Ip6::Prefix &  aPrefix,
-                         NetworkDataTlv::Type aSubTlvType,
-                         int8_t               aPrf,
-                         uint16_t             aFlags,
-                         bool                 aStable)
+Error Local::AddPrefix(const Ip6::Prefix &  aPrefix,
+                       NetworkDataTlv::Type aSubTlvType,
+                       int8_t               aPrf,
+                       uint16_t             aFlags,
+                       bool                 aStable)
 {
-    otError    error = OT_ERROR_NONE;
+    Error      error = kErrorNone;
     uint8_t    subTlvLength;
     PrefixTlv *prefixTlv;
 
-    VerifyOrExit((aPrefix.GetLength() > 0) && aPrefix.IsValid(), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit((aPrefix.GetLength() > 0) && aPrefix.IsValid(), error = kErrorInvalidArgs);
 
     switch (aPrf)
     {
@@ -153,10 +153,10 @@
     case OT_ROUTE_PREFERENCE_HIGH:
         break;
     default:
-        ExitNow(error = OT_ERROR_INVALID_ARGS);
+        ExitNow(error = kErrorInvalidArgs);
     }
 
-    VerifyOrExit(!aPrefix.ContainsPrefix(Get<Mle::MleRouter>().GetMeshLocalPrefix()), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(!aPrefix.ContainsPrefix(Get<Mle::MleRouter>().GetMeshLocalPrefix()), error = kErrorInvalidArgs);
 
     IgnoreError(RemovePrefix(aPrefix, aSubTlvType));
 
@@ -165,7 +165,7 @@
                        : sizeof(HasRouteTlv) + sizeof(HasRouteEntry);
 
     prefixTlv = static_cast<PrefixTlv *>(AppendTlv(sizeof(PrefixTlv) + aPrefix.GetBytesSize() + subTlvLength));
-    VerifyOrExit(prefixTlv != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(prefixTlv != nullptr, error = kErrorNoBufs);
 
     prefixTlv->Init(0, aPrefix);
     prefixTlv->SetSubTlvsLength(subTlvLength);
@@ -200,13 +200,13 @@
     return error;
 }
 
-otError Local::RemovePrefix(const Ip6::Prefix &aPrefix, NetworkDataTlv::Type aSubTlvType)
+Error Local::RemovePrefix(const Ip6::Prefix &aPrefix, NetworkDataTlv::Type aSubTlvType)
 {
-    otError    error = OT_ERROR_NONE;
+    Error      error = kErrorNone;
     PrefixTlv *tlv;
 
-    VerifyOrExit((tlv = FindPrefix(aPrefix)) != nullptr, error = OT_ERROR_NOT_FOUND);
-    VerifyOrExit(FindTlv(tlv->GetSubTlvs(), tlv->GetNext(), aSubTlvType) != nullptr, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit((tlv = FindPrefix(aPrefix)) != nullptr, error = kErrorNotFound);
+    VerifyOrExit(FindTlv(tlv->GetSubTlvs(), tlv->GetNext(), aSubTlvType) != nullptr, error = kErrorNotFound);
     RemoveTlv(tlv);
 
 exit:
@@ -252,14 +252,14 @@
 #endif // OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
 
 #if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
-otError Local::AddService(uint32_t       aEnterpriseNumber,
-                          const uint8_t *aServiceData,
-                          uint8_t        aServiceDataLength,
-                          bool           aServerStable,
-                          const uint8_t *aServerData,
-                          uint8_t        aServerDataLength)
+Error Local::AddService(uint32_t       aEnterpriseNumber,
+                        const uint8_t *aServiceData,
+                        uint8_t        aServiceDataLength,
+                        bool           aServerStable,
+                        const uint8_t *aServerData,
+                        uint8_t        aServerDataLength)
 {
-    otError     error = OT_ERROR_NONE;
+    Error       error = kErrorNone;
     ServiceTlv *serviceTlv;
     ServerTlv * serverTlv;
     uint16_t    serviceTlvSize =
@@ -267,10 +267,10 @@
 
     IgnoreError(RemoveService(aEnterpriseNumber, aServiceData, aServiceDataLength));
 
-    VerifyOrExit(serviceTlvSize <= kMaxSize, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(serviceTlvSize <= kMaxSize, error = kErrorNoBufs);
 
     serviceTlv = static_cast<ServiceTlv *>(AppendTlv(serviceTlvSize));
-    VerifyOrExit(serviceTlv != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit(serviceTlv != nullptr, error = kErrorNoBufs);
 
     serviceTlv->Init(/* aServiceId */ 0, aEnterpriseNumber, aServiceData, aServiceDataLength);
     serviceTlv->SetSubTlvsLength(sizeof(ServerTlv) + aServerDataLength);
@@ -294,13 +294,13 @@
     return error;
 }
 
-otError Local::RemoveService(uint32_t aEnterpriseNumber, const uint8_t *aServiceData, uint8_t aServiceDataLength)
+Error Local::RemoveService(uint32_t aEnterpriseNumber, const uint8_t *aServiceData, uint8_t aServiceDataLength)
 {
-    otError     error = OT_ERROR_NONE;
+    Error       error = kErrorNone;
     ServiceTlv *tlv;
 
     VerifyOrExit((tlv = FindService(aEnterpriseNumber, aServiceData, aServiceDataLength)) != nullptr,
-                 error = OT_ERROR_NOT_FOUND);
+                 error = kErrorNotFound);
     RemoveTlv(tlv);
 
 exit:
@@ -361,9 +361,9 @@
     }
 }
 
-otError Local::UpdateInconsistentServerData(Coap::ResponseHandler aHandler, void *aContext)
+Error Local::UpdateInconsistentServerData(Coap::ResponseHandler aHandler, void *aContext)
 {
-    otError  error        = OT_ERROR_NONE;
+    Error    error        = kErrorNone;
     uint16_t rloc         = Get<Mle::MleRouter>().GetRloc16();
     bool     isConsistent = true;
 
@@ -372,7 +372,7 @@
     // Don't send this Server Data Notification if the device is going to upgrade to Router
     if (Get<Mle::MleRouter>().IsExpectedToBecomeRouter())
     {
-        ExitNow(error = OT_ERROR_INVALID_STATE);
+        ExitNow(error = kErrorInvalidState);
     }
 
 #endif
@@ -386,7 +386,7 @@
     isConsistent = isConsistent && IsServiceConsistent();
 #endif
 
-    VerifyOrExit(!isConsistent, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(!isConsistent, error = kErrorNotFound);
 
     if (mOldRloc == rloc)
     {
diff --git a/src/core/thread/network_data_local.hpp b/src/core/thread/network_data_local.hpp
index 8eae4c9..71facfb 100644
--- a/src/core/thread/network_data_local.hpp
+++ b/src/core/thread/network_data_local.hpp
@@ -76,45 +76,45 @@
      *
      * @param[in]  aConfig  A reference to the on mesh perfix configuration.
      *
-     * @retval OT_ERROR_NONE         Successfully added the Border Router entry.
-     * @retval OT_ERROR_NO_BUFS      Insufficient space to add the Border Router entry.
-     * @retval OT_ERROR_INVALID_ARGS The prefix is mesh local prefix.
+     * @retval kErrorNone         Successfully added the Border Router entry.
+     * @retval kErrorNoBufs       Insufficient space to add the Border Router entry.
+     * @retval kErrorInvalidArgs  The prefix is mesh local prefix.
      *
      */
-    otError AddOnMeshPrefix(const OnMeshPrefixConfig &aConfig);
+    Error AddOnMeshPrefix(const OnMeshPrefixConfig &aConfig);
 
     /**
      * This method removes a Border Router entry from the Thread Network Data.
      *
      * @param[in]  aPrefix        The Prefix to remove.
      *
-     * @retval OT_ERROR_NONE       Successfully removed the Border Router entry.
-     * @retval OT_ERROR_NOT_FOUND  Could not find the Border Router entry.
+     * @retval kErrorNone       Successfully removed the Border Router entry.
+     * @retval kErrorNotFound   Could not find the Border Router entry.
      *
      */
-    otError RemoveOnMeshPrefix(const Ip6::Prefix &aPrefix);
+    Error RemoveOnMeshPrefix(const Ip6::Prefix &aPrefix);
 
     /**
      * This method adds a Has Route entry to the Thread Network data.
      *
      * @param[in]  aConfig       A reference to the external route configuration.
      *
-     * @retval OT_ERROR_NONE     Successfully added the Has Route entry.
-     * @retval OT_ERROR_NO_BUFS  Insufficient space to add the Has Route entry.
+     * @retval kErrorNone     Successfully added the Has Route entry.
+     * @retval kErrorNoBufs   Insufficient space to add the Has Route entry.
      *
      */
-    otError AddHasRoutePrefix(const ExternalRouteConfig &aConfig);
+    Error AddHasRoutePrefix(const ExternalRouteConfig &aConfig);
 
     /**
      * This method removes a Border Router entry from the Thread Network Data.
      *
      * @param[in]  aPrefix        The Prefix to remove.
      *
-     * @retval OT_ERROR_NONE       Successfully removed the Border Router entry.
-     * @retval OT_ERROR_NOT_FOUND  Could not find the Border Router entry.
+     * @retval kErrorNone       Successfully removed the Border Router entry.
+     * @retval kErrorNotFound   Could not find the Border Router entry.
      *
      */
-    otError RemoveHasRoutePrefix(const Ip6::Prefix &aPrefix);
+    Error RemoveHasRoutePrefix(const Ip6::Prefix &aPrefix);
 #endif // OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
 
 #if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
@@ -128,16 +128,16 @@
      * @param[in]  aServerData        A pointer to the Server Data
      * @param[in]  aServerDataLength  The length of @p aServerData in bytes.
      *
-     * @retval OT_ERROR_NONE     Successfully added the Service entry.
-     * @retval OT_ERROR_NO_BUFS  Insufficient space to add the Service entry.
+     * @retval kErrorNone     Successfully added the Service entry.
+     * @retval kErrorNoBufs   Insufficient space to add the Service entry.
      *
      */
-    otError AddService(uint32_t       aEnterpriseNumber,
-                       const uint8_t *aServiceData,
-                       uint8_t        aServiceDataLength,
-                       bool           aServerStable,
-                       const uint8_t *aServerData,
-                       uint8_t        aServerDataLength);
+    Error AddService(uint32_t       aEnterpriseNumber,
+                     const uint8_t *aServiceData,
+                     uint8_t        aServiceDataLength,
+                     bool           aServerStable,
+                     const uint8_t *aServerData,
+                     uint8_t        aServerDataLength);
 
     /**
      * This method removes a Service entry from the Thread Network local data.
@@ -146,12 +146,12 @@
      * @param[in]  aServiceData        A pointer to the service data.
      * @param[in]  aServiceDataLength  The length of @p aServiceData in bytes.
      *
-     * @retval OT_ERROR_NONE       Successfully removed the Border Router entry.
-     * @retval OT_ERROR_NOT_FOUND  Could not find the Border Router entry.
+     * @retval kErrorNone       Successfully removed the Service entry.
+     * @retval kErrorNotFound   Could not find the Service entry.
      *
      */
-    otError RemoveService(uint32_t aEnterpriseNumber, const uint8_t *aServiceData, uint8_t aServiceDataLength);
-#endif
+    Error RemoveService(uint32_t aEnterpriseNumber, const uint8_t *aServiceData, uint8_t aServiceDataLength);
+#endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
 
     /**
      * This method sends a Server Data Notification message to the Leader.
@@ -159,26 +159,26 @@
      * @param[in]  aHandler  A function pointer that is called when the transaction ends.
      * @param[in]  aContext  A pointer to arbitrary context information.
      *
-     * @retval OT_ERROR_NONE           Successfully enqueued the notification message.
-     * @retval OT_ERROR_NO_BUFS        Insufficient message buffers to generate the notification message.
-     * @retval OT_ERROR_INVALID_STATE  Device is a REED and is in the process of becoming a Router.
-     * @retval OT_ERROR_NOT_FOUND      Server Data is already consistent with network data.
+     * @retval kErrorNone          Successfully enqueued the notification message.
+     * @retval kErrorNoBufs        Insufficient message buffers to generate the notification message.
+     * @retval kErrorInvalidState  Device is a REED and is in the process of becoming a Router.
+     * @retval kErrorNotFound      Server Data is already consistent with network data.
      *
      */
-    otError UpdateInconsistentServerData(Coap::ResponseHandler aHandler, void *aContext);
+    Error UpdateInconsistentServerData(Coap::ResponseHandler aHandler, void *aContext);
 
 private:
     void UpdateRloc(void);
 #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
-    otError AddPrefix(const Ip6::Prefix &  aPrefix,
-                      NetworkDataTlv::Type aSubTlvType,
-                      int8_t               aPrf,
-                      uint16_t             aFlags,
-                      bool                 aStable);
-    otError RemovePrefix(const Ip6::Prefix &aPrefix, NetworkDataTlv::Type aSubTlvType);
-    void    UpdateRloc(PrefixTlv &aPrefixTlv);
-    bool    IsOnMeshPrefixConsistent(void) const;
-    bool    IsExternalRouteConsistent(void) const;
+    Error AddPrefix(const Ip6::Prefix &  aPrefix,
+                    NetworkDataTlv::Type aSubTlvType,
+                    int8_t               aPrf,
+                    uint16_t             aFlags,
+                    bool                 aStable);
+    Error RemovePrefix(const Ip6::Prefix &aPrefix, NetworkDataTlv::Type aSubTlvType);
+    void  UpdateRloc(PrefixTlv &aPrefixTlv);
+    bool  IsOnMeshPrefixConsistent(void) const;
+    bool  IsExternalRouteConsistent(void) const;
 #endif
 
 #if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
diff --git a/src/core/thread/network_data_notifier.cpp b/src/core/thread/network_data_notifier.cpp
index eed8087..7ea123a 100644
--- a/src/core/thread/network_data_notifier.cpp
+++ b/src/core/thread/network_data_notifier.cpp
@@ -46,7 +46,7 @@
 
 Notifier::Notifier(Instance &aInstance)
     : InstanceLocator(aInstance)
-    , mTimer(aInstance, Notifier::HandleTimer, this)
+    , mTimer(aInstance, Notifier::HandleTimer)
     , mNextDelay(0)
     , mWaitingForResponse(false)
 {
@@ -60,7 +60,7 @@
 
 void Notifier::SynchronizeServerData(void)
 {
-    otError error = OT_ERROR_NOT_FOUND;
+    Error error = kErrorNotFound;
 
     VerifyOrExit(Get<Mle::MleRouter>().IsAttached() && !mWaitingForResponse);
 
@@ -69,30 +69,30 @@
 #if OPENTHREAD_FTD
     mNextDelay = kDelayRemoveStaleChildren;
     error      = Get<Leader>().RemoveStaleChildEntries(&Notifier::HandleCoapResponse, this);
-    VerifyOrExit(error == OT_ERROR_NOT_FOUND);
+    VerifyOrExit(error == kErrorNotFound);
 #endif
 
 #if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
     mNextDelay = kDelaySynchronizeServerData;
     error      = Get<Local>().UpdateInconsistentServerData(&Notifier::HandleCoapResponse, this);
-    VerifyOrExit(error == OT_ERROR_NOT_FOUND);
+    VerifyOrExit(error == kErrorNotFound);
 #endif
 
 exit:
     switch (error)
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         mWaitingForResponse = true;
         break;
-    case OT_ERROR_NO_BUFS:
+    case kErrorNoBufs:
         mTimer.Start(kDelayNoBufs);
         break;
 #if OPENTHREAD_FTD
-    case OT_ERROR_INVALID_STATE:
+    case kErrorInvalidState:
         mTimer.Start(Time::SecToMsec(Get<Mle::MleRouter>().GetRouterSelectionJitterTimeout() + 1));
         break;
 #endif
-    case OT_ERROR_NOT_FOUND:
+    case kErrorNotFound:
         break;
     default:
         OT_ASSERT(false);
@@ -115,7 +115,7 @@
 
 void Notifier::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<Notifier>().HandleTimer();
+    aTimer.Get<Notifier>().HandleTimer();
 }
 
 void Notifier::HandleTimer(void)
@@ -123,10 +123,7 @@
     SynchronizeServerData();
 }
 
-void Notifier::HandleCoapResponse(void *               aContext,
-                                  otMessage *          aMessage,
-                                  const otMessageInfo *aMessageInfo,
-                                  otError              aResult)
+void Notifier::HandleCoapResponse(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo, Error aResult)
 {
     OT_UNUSED_VARIABLE(aMessage);
     OT_UNUSED_VARIABLE(aMessageInfo);
@@ -134,18 +131,18 @@
     static_cast<Notifier *>(aContext)->HandleCoapResponse(aResult);
 }
 
-void Notifier::HandleCoapResponse(otError aResult)
+void Notifier::HandleCoapResponse(Error aResult)
 {
     mWaitingForResponse = false;
 
     switch (aResult)
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         mTimer.Start(mNextDelay + 1);
         break;
 
-    case OT_ERROR_RESPONSE_TIMEOUT:
-    case OT_ERROR_ABORT:
+    case kErrorResponseTimeout:
+    case kErrorAbort:
         SynchronizeServerData();
         break;
 
diff --git a/src/core/thread/network_data_notifier.hpp b/src/core/thread/network_data_notifier.hpp
index dcc3195..433072c 100644
--- a/src/core/thread/network_data_notifier.hpp
+++ b/src/core/thread/network_data_notifier.hpp
@@ -85,8 +85,8 @@
     static void HandleCoapResponse(void *               aContext,
                                    otMessage *          aMessage,
                                    const otMessageInfo *aMessageInfo,
-                                   otError              aResult);
-    void        HandleCoapResponse(otError aResult);
+                                   Error                aResult);
+    void        HandleCoapResponse(Error aResult);
 
     void SynchronizeServerData(void);
 
diff --git a/src/core/thread/network_data_service.cpp b/src/core/thread/network_data_service.cpp
new file mode 100644
index 0000000..ed18e3f
--- /dev/null
+++ b/src/core/thread/network_data_service.cpp
@@ -0,0 +1,186 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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
+ *   This file implements function for managing Thread Network Data service/server entries.
+ *
+ */
+
+#include "network_data_service.hpp"
+
+#include "common/code_utils.hpp"
+#include "common/instance.hpp"
+#include "common/locator-getters.hpp"
+#include "thread/network_data_local.hpp"
+
+namespace ot {
+namespace NetworkData {
+namespace Service {
+
+#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
+
+Error Manager::AddService(uint8_t     aServiceNumber,
+                          bool        aServerStable,
+                          const void *aServerData,
+                          uint8_t     aServerDataLength)
+{
+    Error error;
+
+    SuccessOrExit(error = Get<Local>().AddService(kThreadEnterpriseNumber, &aServiceNumber, sizeof(aServiceNumber),
+                                                  aServerStable, reinterpret_cast<const uint8_t *>(aServerData),
+                                                  aServerDataLength));
+
+    Get<Notifier>().HandleServerDataUpdated();
+
+exit:
+    return error;
+}
+
+Error Manager::RemoveService(uint8_t aServiceNumber)
+{
+    Error error;
+
+    SuccessOrExit(error = Get<Local>().RemoveService(kThreadEnterpriseNumber, &aServiceNumber, sizeof(aServiceNumber)));
+    Get<Notifier>().HandleServerDataUpdated();
+
+exit:
+    return error;
+}
+
+#endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
+
+Error Manager::GetServiceId(uint8_t aServiceNumber, bool aServerStable, uint8_t &aServiceId) const
+{
+    return Get<Leader>().GetServiceId(kThreadEnterpriseNumber, &aServiceNumber, sizeof(aServiceNumber), aServerStable,
+                                      aServiceId);
+}
+
+#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+
+void Manager::GetBackboneRouterPrimary(ot::BackboneRouter::BackboneRouterConfig &aConfig) const
+{
+    const uint8_t                     serviceData    = BackboneRouter::kServiceNumber;
+    const ServerTlv *                 rvalServerTlv  = nullptr;
+    const BackboneRouter::ServerData *rvalServerData = nullptr;
+    Iterator                          iterator;
+
+    aConfig.mServer16 = Mac::kShortAddrInvalid;
+
+    iterator.mServiceTlv = Get<Leader>().FindService(kThreadEnterpriseNumber, &serviceData, sizeof(serviceData));
+
+    VerifyOrExit(iterator.mServiceTlv != nullptr);
+
+    while (IterateToNextServer(iterator) == kErrorNone)
+    {
+        const BackboneRouter::ServerData *serverData;
+
+        if (iterator.mServerSubTlv->GetServerDataLength() < sizeof(BackboneRouter::ServerData))
+        {
+            continue;
+        }
+
+        serverData = reinterpret_cast<const BackboneRouter::ServerData *>(iterator.mServerSubTlv->GetServerData());
+
+        if (rvalServerTlv == nullptr ||
+            (iterator.mServerSubTlv->GetServer16() ==
+             Mle::Mle::Rloc16FromRouterId(Get<Mle::MleRouter>().GetLeaderId())) ||
+            serverData->GetSequenceNumber() > rvalServerData->GetSequenceNumber() ||
+            (serverData->GetSequenceNumber() == rvalServerData->GetSequenceNumber() &&
+             iterator.mServerSubTlv->GetServer16() > rvalServerTlv->GetServer16()))
+        {
+            rvalServerTlv  = iterator.mServerSubTlv;
+            rvalServerData = serverData;
+        }
+    }
+
+    VerifyOrExit(rvalServerTlv != nullptr);
+
+    aConfig.mServer16            = rvalServerTlv->GetServer16();
+    aConfig.mSequenceNumber      = rvalServerData->GetSequenceNumber();
+    aConfig.mReregistrationDelay = rvalServerData->GetReregistrationDelay();
+    aConfig.mMlrTimeout          = rvalServerData->GetMlrTimeout();
+
+exit:
+    return;
+}
+
+#endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+
+Error Manager::GetNextSrpServerInfo(Iterator &aIterator, SrpServer::Info &aInfo) const
+{
+    Error error = kErrorNotFound;
+
+    if (aIterator.mServiceTlv == nullptr)
+    {
+        const uint8_t serviceData = SrpServer::kServiceNumber;
+
+        aIterator.mServiceTlv = Get<Leader>().FindService(kThreadEnterpriseNumber, &serviceData, sizeof(serviceData));
+        VerifyOrExit(aIterator.mServiceTlv != nullptr);
+    }
+    else
+    {
+        VerifyOrExit(aIterator.mServerSubTlv != nullptr);
+    }
+
+    while ((error = IterateToNextServer(aIterator)) == kErrorNone)
+    {
+        const SrpServer::ServerData *serverData;
+
+        if (aIterator.mServerSubTlv->GetServerDataLength() < sizeof(SrpServer::ServerData))
+        {
+            continue;
+        }
+
+        serverData = reinterpret_cast<const SrpServer::ServerData *>(aIterator.mServerSubTlv->GetServerData());
+
+        aInfo.mRloc16 = aIterator.mServerSubTlv->GetServer16();
+        aInfo.mSockAddr.GetAddress().SetToRoutingLocator(Get<Mle::Mle>().GetMeshLocalPrefix(), aInfo.mRloc16);
+        aInfo.mSockAddr.SetPort(serverData->GetPort());
+
+        break;
+    }
+
+exit:
+    return error;
+}
+
+Error Manager::IterateToNextServer(Iterator &aIterator) const
+{
+    const NetworkDataTlv *start;
+
+    start =
+        (aIterator.mServerSubTlv != nullptr) ? aIterator.mServerSubTlv->GetNext() : aIterator.mServiceTlv->GetSubTlvs();
+    aIterator.mServerSubTlv = NetworkData::FindTlv<ServerTlv>(start, aIterator.mServiceTlv->GetNext());
+
+    return (aIterator.mServerSubTlv != nullptr) ? kErrorNone : kErrorNotFound;
+}
+
+} // namespace Service
+} // namespace NetworkData
+} // namespace ot
diff --git a/src/core/thread/network_data_service.hpp b/src/core/thread/network_data_service.hpp
new file mode 100644
index 0000000..313b2a8
--- /dev/null
+++ b/src/core/thread/network_data_service.hpp
@@ -0,0 +1,359 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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
+ *   This file includes definitions related to Thread Network Data service/server entries.
+ */
+
+#ifndef NETWORK_DATA_SERVICE_HPP_
+#define NETWORK_DATA_SERVICE_HPP_
+
+#include "openthread-core-config.h"
+
+#include <openthread/netdata.h>
+
+#include "backbone_router/bbr_leader.hpp"
+#include "common/encoding.hpp"
+#include "common/locator.hpp"
+#include "common/non_copyable.hpp"
+#include "net/socket.hpp"
+#include "thread/network_data_tlvs.hpp"
+
+namespace ot {
+namespace NetworkData {
+namespace Service {
+
+using ot::Encoding::BigEndian::HostSwap16;
+using ot::Encoding::BigEndian::HostSwap32;
+
+enum : uint32_t
+{
+    kThreadEnterpriseNumber = ServiceTlv::kThreadEnterpriseNumber, ///< Thread enterprise number.
+};
+
+#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+
+/**
+ * This type implements Thread Network Data "Backbone Router Service" server data generation and parsing.
+ *
+ */
+class BackboneRouter
+{
+public:
+    enum : uint8_t
+    {
+        kServiceNumber = 0x01, ///< Backbone Router service data number (THREAD_SERVICE_DATA_BBR).
+    };
+
+    /**
+     * This class implements the generation and parsing of "Backbone Router Service" server data.
+     *
+     */
+    OT_TOOL_PACKED_BEGIN
+    class ServerData
+    {
+    public:
+        /**
+         * This method returns the length (in bytes) of server data.
+         *
+         * @returns The server data length in bytes.
+         *
+         */
+        uint8_t GetLength(void) const { return sizeof(ServerData); }
+
+        /**
+         * This method returns the sequence number of Backbone Router.
+         *
+         * @returns  The sequence number of the Backbone Router.
+         *
+         */
+        uint8_t GetSequenceNumber(void) const { return mSequenceNumber; }
+
+        /**
+         * This method sets the sequence number of Backbone Router.
+         *
+         * @param[in]  aSequenceNumber  The sequence number of Backbone Router.
+         *
+         */
+        void SetSequenceNumber(uint8_t aSequenceNumber) { mSequenceNumber = aSequenceNumber; }
+
+        /**
+         * This method returns the Registration Delay (in seconds) of Backbone Router.
+         *
+         * @returns The BBR Registration Delay (in seconds) of Backbone Router.
+         *
+         */
+        uint16_t GetReregistrationDelay(void) const { return HostSwap16(mReregistrationDelay); }
+
+        /**
+         * This method sets the Registration Delay (in seconds) of Backbone Router.
+         *
+         * @param[in]  aReregistrationDelay  The Registration Delay (in seconds) of Backbone Router.
+         *
+         */
+        void SetReregistrationDelay(uint16_t aReregistrationDelay)
+        {
+            mReregistrationDelay = HostSwap16(aReregistrationDelay);
+        }
+
+        /**
+         * This method returns the multicast listener report timeout (in seconds) of Backbone Router.
+         *
+         * @returns The multicast listener report timeout (in seconds) of Backbone Router.
+         *
+         */
+        uint32_t GetMlrTimeout(void) const { return HostSwap32(mMlrTimeout); }
+
+        /**
+         * This method sets multicast listener report timeout (in seconds) of Backbone Router.
+         *
+         * @param[in]  aMlrTimeout  The multicast listener report timeout (in seconds) of Backbone Router.
+         *
+         */
+        void SetMlrTimeout(uint32_t aMlrTimeout) { mMlrTimeout = HostSwap32(aMlrTimeout); }
+
+    private:
+        uint8_t  mSequenceNumber;
+        uint16_t mReregistrationDelay;
+        uint32_t mMlrTimeout;
+    } OT_TOOL_PACKED_END;
+
+    BackboneRouter(void) = delete;
+};
+
+#endif // #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+
+/**
+ * This type implements Thread Network Data "SRP Server Service" server data generation and parsing.
+ *
+ */
+class SrpServer
+{
+public:
+    enum : uint8_t
+    {
+        kServiceNumber = OPENTHREAD_CONFIG_SRP_SERVER_SERVICE_NUMBER, ///< SRP Sever Service number
+    };
+
+    /**
+     * This structure represents information about an SRP server (from "SRP Server Service" Server entries).
+     *
+     */
+    struct Info
+    {
+        Ip6::SockAddr mSockAddr; ///< The SRP server address (IPv6 address and port number).
+        uint16_t      mRloc16;   ///< The RLOC16 of SRP server.
+    };
+
+    /**
+     * This class implements generation and parsing of "SRP Server Service" server data.
+     *
+     */
+    OT_TOOL_PACKED_BEGIN
+    class ServerData
+    {
+    public:
+        /** This method returns the length (in bytes) of server data.
+         *
+         * @returns The server data length in bytes.
+         *
+         */
+        uint8_t GetLength(void) const { return sizeof(ServerData); }
+
+        /**
+         * This method returns the port number being used by the SRP server.
+         *
+         * @return The port number of SPR server.
+         *
+         */
+        uint16_t GetPort(void) const { return HostSwap16(mPort); }
+
+        /**
+         * This method sets the SRP port number in `ServerData`.
+         *
+         * @param[in] aPort   The port number of SRP server.
+         *
+         */
+        void SetPort(uint16_t aPort) { mPort = HostSwap16(aPort); }
+
+    private:
+        uint16_t mPort;
+    } OT_TOOL_PACKED_END;
+
+    SrpServer(void) = delete;
+};
+
+/**
+ * This class manages the Thread Service entries in Thread Network Data.
+ *
+ */
+class Manager : public InstanceLocator, private NonCopyable
+{
+public:
+    /**
+     * This class represents an iterator used to iterate through Network Data Service entries.
+     *
+     */
+    class Iterator : public Clearable<Iterator>
+    {
+        friend class Manager;
+
+    public:
+        /**
+         * This constructor initializes the iterator (as empty/clear).
+         *
+         */
+        Iterator(void)
+            : mServiceTlv(nullptr)
+            , mServerSubTlv(nullptr)
+        {
+        }
+
+    private:
+        const ServiceTlv *mServiceTlv;
+        const ServerTlv * mServerSubTlv;
+    };
+
+    /**
+     * This constructor initializes the `Manager` object.
+     *
+     * @param[in]  aInstance     A reference to the OpenThread instance.
+     *
+     */
+    explicit Manager(Instance &aInstance)
+        : InstanceLocator(aInstance)
+    {
+    }
+
+#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
+    /**
+     * This method adds a Thread Service entry to the local Thread Network Data.
+     *
+     * When successfully added, this method also invokes `Notifier::HandleServerDataUpdated()` to register the changes
+     * in local Network Data with leader.
+     *
+     * The template type `ServiceType` has the following requirements:
+     *   - It MUST have a constant `ServiceType::kServiceNumber` specifying the service number.
+     *   - It MUST define nested type `ServiceType::ServerData` representing the server data (and its format).
+     *   - The `ServiceType::ServerData` MUST provide `GetLength()` method returning the length of server data.
+     *
+     * @tparam    ServiceType    The service type to be added.
+     *
+     * @param[in] aServerData    The server data.
+     * @param[in] aServerStable  The Stable flag value for Server TLV.
+     *
+     * @retval kErrorNone     Successfully added the Service entry.
+     * @retval kErrorNoBufs   Insufficient space to add the Service entry.
+     *
+     */
+    template <typename ServiceType>
+    Error Add(const typename ServiceType::ServerData &aServerData, bool aServerStable = true)
+    {
+        return AddService(ServiceType::kServiceNumber, aServerStable, &aServerData, aServerData.GetLength());
+    }
+
+    /**
+     * This method removed a Thread Service entry to the local Thread Network Data.
+     *
+     * When successfully removed, this method also invokes `Notifier::HandleServerDataUpdated()` to register the
+     * changes in local Network Data with leader.
+     *
+     * The template type `ServiceType` has the following requirements:
+     *   - It MUST have a constant `ServiceType::kServiceNumber` specifying the service number.
+     *
+     * @tparam   ServiceType       The service type to be removed.
+     *
+     * @retval kErrorNone       Successfully removed the Service entry.
+     * @retval kErrorNotFound   Could not find the Service entry.
+     *
+     */
+    template <typename ServiceType> Error Remove(void) { return RemoveService(ServiceType::kServiceNumber); }
+
+#endif
+
+    /**
+     * This method gets the Service ID for the specified service from Thread Network Data.
+     *
+     * The template type `ServiceType` has the following requirements:
+     *   - It MUST have a constant `ServiceType::kServiceNumber` specifying the service number.
+     *
+     * @tparam     ServiceType     The service type to be added.
+     *
+     * @param[in]  aServerStable   The Stable flag value for Server TLV
+     * @param[out] aServiceId      A reference where to put the Service ID.
+     *
+     * @retval kErrorNone       Successfully got the Service ID.
+     * @retval kErrorNotFound   The specified service was not found.
+     *
+     */
+    template <typename ServiceType> Error GetServiceId(bool aServerStable, uint8_t &aServiceId) const
+    {
+        return GetServiceId(ServiceType::kServiceNumber, aServerStable, aServiceId);
+    }
+
+#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+    /**
+     * This method gets the Primary Backbone Router (PBBR) in the Thread Network Data.
+     *
+     * @param[out]  aConfig      The Primary Backbone Router configuration.
+     *
+     */
+    void GetBackboneRouterPrimary(ot::BackboneRouter::BackboneRouterConfig &aConfig) const;
+#endif
+
+    /**
+     * This method gets the next SRP server info from the Thread Network Data "SRP Server Service" entries.
+     *
+     * This method allows caller to iterate through all server entries for Network Data "SRP Server Service". To get
+     * the first entry @p aIterator should be cleared (e.g., a new instance of `Iterator` or calling `Clear()` method).
+     *
+     * @param[inout] aIterator     A reference to an iterator.
+     * @param[out]   aInfo         A reference to `SrpServer::Info` to return the next SRP server info.
+     *
+     * @retval kErrorNone       Successfully got the next SRP server info. @p aInfo and @p aIterator are updated.
+     * @retval kErrorNotFound   No more SRP server entries in Network Data.
+     *
+     */
+    Error GetNextSrpServerInfo(Iterator &aIterator, SrpServer::Info &aInfo) const;
+
+private:
+#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
+    Error AddService(uint8_t aServiceNumber, bool aServerStable, const void *aServerData, uint8_t aServerDataLength);
+    Error RemoveService(uint8_t aServiceNumber);
+#endif
+
+    Error GetServiceId(uint8_t aServiceNumber, bool aServerStable, uint8_t &aServiceId) const;
+    Error IterateToNextServer(Iterator &aIterator) const;
+};
+
+} // namespace Service
+} // namespace NetworkData
+} // namespace ot
+
+#endif // NETWORK_DATA_SERVICE_HPP_
diff --git a/src/core/thread/network_data_tlvs.hpp b/src/core/thread/network_data_tlvs.hpp
index ed01d00..ceac708 100644
--- a/src/core/thread/network_data_tlvs.hpp
+++ b/src/core/thread/network_data_tlvs.hpp
@@ -990,9 +990,12 @@
 public:
     enum
     {
-        kType                      = kTypeService, ///< The TLV Type.
-        kThreadEnterpriseNumber    = 44970,        ///< Thread enterprise number.
-        kServiceDataBackboneRouter = 0x01,         ///< const THREAD_SERVICE_DATA_BBR
+        kType = kTypeService, ///< The TLV Type.
+    };
+
+    enum : uint32_t
+    {
+        kThreadEnterpriseNumber = 44970, ///< Thread enterprise number.
     };
 
     /**
@@ -1287,67 +1290,6 @@
     uint16_t mServer16;
 } OT_TOOL_PACKED_END;
 
-OT_TOOL_PACKED_BEGIN
-class BackboneRouterServerData
-{
-public:
-    /**
-     * This method returns the sequence number of Backbone Router.
-     *
-     * @returns  The sequence number of the Backbone Router.
-     *
-     */
-    uint8_t GetSequenceNumber(void) const { return mSequenceNumber; }
-
-    /**
-     * This method sets the sequence number of Backbone Router.
-     *
-     * @param[in]  aSequenceNumber  The sequence number of Backbone Router.
-     *
-     */
-    void SetSequenceNumber(uint8_t aSequenceNumber) { mSequenceNumber = aSequenceNumber; }
-
-    /**
-     * This method returns the Registration Delay (in seconds) of Backbone Router.
-     *
-     * @returns The BBR Registration Delay (in seconds) of Backbone Router.
-     *
-     */
-    uint16_t GetReregistrationDelay(void) const { return HostSwap16(mReregistrationDelay); }
-
-    /**
-     * This method sets the Registration Delay (in seconds) of Backbone Router.
-     *
-     * @param[in]  aReregistrationDelay  The Registration Delay (in seconds) of Backbone Router.
-     *
-     */
-    void SetReregistrationDelay(uint16_t aReregistrationDelay)
-    {
-        mReregistrationDelay = HostSwap16(aReregistrationDelay);
-    }
-
-    /**
-     * This method returns the multicast listener report timeout (in seconds) of Backbone Router.
-     *
-     * @returns The multicast listener report timeout (in seconds) of Backbone Router.
-     *
-     */
-    uint32_t GetMlrTimeout(void) const { return HostSwap32(mMlrTimeout); }
-
-    /**
-     * This method sets multicast listener report timeout (in seconds) of Backbone Router.
-     *
-     * @param[in]  aMlrTimeout  The multicast listener report timeout (in seconds) of Backbone Router.
-     *
-     */
-    void SetMlrTimeout(uint32_t aMlrTimeout) { mMlrTimeout = HostSwap32(aMlrTimeout); }
-
-private:
-    uint8_t  mSequenceNumber;
-    uint16_t mReregistrationDelay;
-    uint32_t mMlrTimeout;
-} OT_TOOL_PACKED_END;
-
 /**
  * @}
  *
diff --git a/src/core/thread/network_diagnostic.cpp b/src/core/thread/network_diagnostic.cpp
index a26525b..4941840 100644
--- a/src/core/thread/network_diagnostic.cpp
+++ b/src/core/thread/network_diagnostic.cpp
@@ -69,23 +69,18 @@
     Get<Tmf::TmfAgent>().AddResource(mDiagnosticReset);
 }
 
-void NetworkDiagnostic::SetReceiveDiagnosticGetCallback(otReceiveDiagnosticGetCallback aCallback,
-                                                        void *                         aCallbackContext)
+Error NetworkDiagnostic::SendDiagnosticGet(const Ip6::Address &           aDestination,
+                                           const uint8_t                  aTlvTypes[],
+                                           uint8_t                        aCount,
+                                           otReceiveDiagnosticGetCallback aCallback,
+                                           void *                         aCallbackContext)
 {
-    mReceiveDiagnosticGetCallback        = aCallback;
-    mReceiveDiagnosticGetCallbackContext = aCallbackContext;
-}
-
-otError NetworkDiagnostic::SendDiagnosticGet(const Ip6::Address &aDestination,
-                                             const uint8_t       aTlvTypes[],
-                                             uint8_t             aCount)
-{
-    otError               error;
+    Error                 error;
     Coap::Message *       message = nullptr;
     Ip6::MessageInfo      messageInfo;
     otCoapResponseHandler handler = nullptr;
 
-    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = kErrorNoBufs);
 
     if (aDestination.IsMulticast())
     {
@@ -122,6 +117,9 @@
 
     SuccessOrExit(error = Get<Tmf::TmfAgent>().SendMessage(*message, messageInfo, handler, this));
 
+    mReceiveDiagnosticGetCallback        = aCallback;
+    mReceiveDiagnosticGetCallbackContext = aCallbackContext;
+
     otLogInfoNetDiag("Sent diagnostic get");
 
 exit:
@@ -132,7 +130,7 @@
 void NetworkDiagnostic::HandleDiagnosticGetResponse(void *               aContext,
                                                     otMessage *          aMessage,
                                                     const otMessageInfo *aMessageInfo,
-                                                    otError              aResult)
+                                                    Error                aResult)
 {
     static_cast<NetworkDiagnostic *>(aContext)->HandleDiagnosticGetResponse(
         static_cast<Coap::Message *>(aMessage), static_cast<const Ip6::MessageInfo *>(aMessageInfo), aResult);
@@ -140,10 +138,10 @@
 
 void NetworkDiagnostic::HandleDiagnosticGetResponse(Coap::Message *         aMessage,
                                                     const Ip6::MessageInfo *aMessageInfo,
-                                                    otError                 aResult)
+                                                    Error                   aResult)
 {
     SuccessOrExit(aResult);
-    VerifyOrExit(aMessage->GetCode() == Coap::kCodeChanged, aResult = OT_ERROR_FAILED);
+    VerifyOrExit(aMessage->GetCode() == Coap::kCodeChanged, aResult = kErrorFailed);
 
 exit:
     if (mReceiveDiagnosticGetCallback)
@@ -152,7 +150,7 @@
     }
     else
     {
-        otLogDebgNetDiag("Received diagnostic get response, error = %s", otThreadErrorToString(aResult));
+        otLogDebgNetDiag("Received diagnostic get response, error = %s", ErrorToString(aResult));
     }
     return;
 }
@@ -173,7 +171,7 @@
 
     if (mReceiveDiagnosticGetCallback)
     {
-        mReceiveDiagnosticGetCallback(OT_ERROR_NONE, &aMessage, &aMessageInfo, mReceiveDiagnosticGetCallbackContext);
+        mReceiveDiagnosticGetCallback(kErrorNone, &aMessage, &aMessageInfo, mReceiveDiagnosticGetCallbackContext);
     }
 
     SuccessOrExit(Get<Tmf::TmfAgent>().SendEmptyAck(aMessage, aMessageInfo));
@@ -184,9 +182,9 @@
     return;
 }
 
-otError NetworkDiagnostic::AppendIp6AddressList(Message &aMessage)
+Error NetworkDiagnostic::AppendIp6AddressList(Message &aMessage)
 {
-    otError           error = OT_ERROR_NONE;
+    Error             error = kErrorNone;
     Ip6AddressListTlv tlv;
     uint8_t           count = 0;
 
@@ -211,9 +209,9 @@
 }
 
 #if OPENTHREAD_FTD
-otError NetworkDiagnostic::AppendChildTable(Message &aMessage)
+Error NetworkDiagnostic::AppendChildTable(Message &aMessage)
 {
-    otError         error   = OT_ERROR_NONE;
+    Error           error   = kErrorNone;
     uint16_t        count   = 0;
     uint8_t         timeout = 0;
     ChildTableTlv   tlv;
@@ -280,11 +278,11 @@
     aMacCountersTlv.SetIfOutDiscards(macCounters.mTxErrBusyChannel);
 }
 
-otError NetworkDiagnostic::FillRequestedTlvs(const Message &       aRequest,
-                                             Message &             aResponse,
-                                             NetworkDiagnosticTlv &aNetworkDiagnosticTlv)
+Error NetworkDiagnostic::FillRequestedTlvs(const Message &       aRequest,
+                                           Message &             aResponse,
+                                           NetworkDiagnosticTlv &aNetworkDiagnosticTlv)
 {
-    otError  error  = OT_ERROR_NONE;
+    Error    error  = kErrorNone;
     uint16_t offset = 0;
     uint8_t  type;
 
@@ -434,7 +432,7 @@
         {
             uint32_t maxTimeout;
 
-            if (Get<Mle::MleRouter>().GetMaxChildTimeout(maxTimeout) == OT_ERROR_NONE)
+            if (Get<Mle::MleRouter>().GetMaxChildTimeout(maxTimeout) == kErrorNone)
             {
                 SuccessOrExit(error = Tlv::Append<MaxChildTimeoutTlv>(aResponse, maxTimeout));
             }
@@ -463,29 +461,29 @@
 
 void NetworkDiagnostic::HandleDiagnosticGetQuery(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError              error   = OT_ERROR_NONE;
+    Error                error   = kErrorNone;
     Coap::Message *      message = nullptr;
     NetworkDiagnosticTlv networkDiagnosticTlv;
     Ip6::MessageInfo     messageInfo;
 
-    VerifyOrExit(aMessage.IsPostRequest(), error = OT_ERROR_DROP);
+    VerifyOrExit(aMessage.IsPostRequest(), error = kErrorDrop);
 
     otLogInfoNetDiag("Received diagnostic get query");
 
     SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), networkDiagnosticTlv));
 
-    VerifyOrExit(networkDiagnosticTlv.GetType() == NetworkDiagnosticTlv::kTypeList, error = OT_ERROR_PARSE);
+    VerifyOrExit(networkDiagnosticTlv.GetType() == NetworkDiagnosticTlv::kTypeList, error = kErrorParse);
 
     // DIAG_GET.qry may be sent as a confirmable message.
     if (aMessage.IsConfirmable())
     {
-        if (Get<Tmf::TmfAgent>().SendEmptyAck(aMessage, aMessageInfo) == OT_ERROR_NONE)
+        if (Get<Tmf::TmfAgent>().SendEmptyAck(aMessage, aMessageInfo) == kErrorNone)
         {
             otLogInfoNetDiag("Sent diagnostic get query acknowledgment");
         }
     }
 
-    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsConfirmablePost(UriPath::kDiagnosticGetAnswer));
 
@@ -532,20 +530,20 @@
 
 void NetworkDiagnostic::HandleDiagnosticGetRequest(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    otError              error   = OT_ERROR_NONE;
+    Error                error   = kErrorNone;
     Coap::Message *      message = nullptr;
     NetworkDiagnosticTlv networkDiagnosticTlv;
     Ip6::MessageInfo     messageInfo(aMessageInfo);
 
-    VerifyOrExit(aMessage.IsConfirmablePostRequest(), error = OT_ERROR_DROP);
+    VerifyOrExit(aMessage.IsConfirmablePostRequest(), error = kErrorDrop);
 
     otLogInfoNetDiag("Received diagnostic get request");
 
     SuccessOrExit(error = aMessage.Read(aMessage.GetOffset(), networkDiagnosticTlv));
 
-    VerifyOrExit(networkDiagnosticTlv.GetType() == NetworkDiagnosticTlv::kTypeList, error = OT_ERROR_PARSE);
+    VerifyOrExit(networkDiagnosticTlv.GetType() == NetworkDiagnosticTlv::kTypeList, error = kErrorParse);
 
-    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->SetDefaultResponseHeader(aMessage));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -566,15 +564,15 @@
     FreeMessageOnError(message, error);
 }
 
-otError NetworkDiagnostic::SendDiagnosticReset(const Ip6::Address &aDestination,
-                                               const uint8_t       aTlvTypes[],
-                                               uint8_t             aCount)
+Error NetworkDiagnostic::SendDiagnosticReset(const Ip6::Address &aDestination,
+                                             const uint8_t       aTlvTypes[],
+                                             uint8_t             aCount)
 {
-    otError          error;
+    Error            error;
     Coap::Message *  message = nullptr;
     Ip6::MessageInfo messageInfo;
 
-    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = Get<Tmf::TmfAgent>().NewMessage()) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsConfirmablePost(UriPath::kDiagnosticReset));
 
@@ -726,11 +724,11 @@
     ParseMode(aChildTableTlvEntry.GetMode(), aChildEntry.mMode);
 }
 
-otError NetworkDiagnostic::GetNextDiagTlv(const Coap::Message &aMessage,
-                                          Iterator &           aIterator,
-                                          otNetworkDiagTlv &   aNetworkDiagTlv)
+Error NetworkDiagnostic::GetNextDiagTlv(const Coap::Message &aMessage,
+                                        Iterator &           aIterator,
+                                        otNetworkDiagTlv &   aNetworkDiagTlv)
 {
-    otError              error  = OT_ERROR_NONE;
+    Error                error  = kErrorNone;
     uint16_t             offset = aMessage.GetOffset() + aIterator;
     NetworkDiagnosticTlv tlv;
 
@@ -738,7 +736,7 @@
     {
         uint16_t tlvTotalLength;
 
-        VerifyOrExit(aMessage.Read(offset, tlv) == OT_ERROR_NONE, error = OT_ERROR_NOT_FOUND);
+        VerifyOrExit(aMessage.Read(offset, tlv) == kErrorNone, error = kErrorNotFound);
 
         switch (tlv.GetType())
         {
@@ -769,7 +767,7 @@
             ConnectivityTlv connectivity;
 
             SuccessOrExit(error = aMessage.Read(offset, connectivity));
-            VerifyOrExit(connectivity.IsValid(), error = OT_ERROR_PARSE);
+            VerifyOrExit(connectivity.IsValid(), error = kErrorParse);
 
             ParseConnectivity(connectivity, aNetworkDiagTlv.mData.mConnectivity);
             break;
@@ -780,9 +778,9 @@
             RouteTlv route;
 
             tlvTotalLength = sizeof(tlv) + tlv.GetLength();
-            VerifyOrExit(tlvTotalLength <= sizeof(route), error = OT_ERROR_PARSE);
+            VerifyOrExit(tlvTotalLength <= sizeof(route), error = kErrorParse);
             SuccessOrExit(error = aMessage.Read(offset, &route, tlvTotalLength));
-            VerifyOrExit(route.IsValid(), error = OT_ERROR_PARSE);
+            VerifyOrExit(route.IsValid(), error = kErrorParse);
 
             ParseRoute(route, aNetworkDiagTlv.mData.mRoute);
             break;
@@ -793,7 +791,7 @@
             LeaderDataTlv leaderData;
 
             SuccessOrExit(error = aMessage.Read(offset, leaderData));
-            VerifyOrExit(leaderData.IsValid(), error = OT_ERROR_PARSE);
+            VerifyOrExit(leaderData.IsValid(), error = kErrorParse);
 
             ParseLeaderData(leaderData, aNetworkDiagTlv.mData.mLeaderData);
             break;
@@ -804,11 +802,10 @@
             NetworkDataTlv networkData;
 
             tlvTotalLength = sizeof(tlv) + tlv.GetLength();
-            VerifyOrExit(tlvTotalLength <= sizeof(networkData), error = OT_ERROR_PARSE);
+            VerifyOrExit(tlvTotalLength <= sizeof(networkData), error = kErrorParse);
             SuccessOrExit(error = aMessage.Read(offset, &networkData, tlvTotalLength));
-            VerifyOrExit(networkData.IsValid(), error = OT_ERROR_PARSE);
-            VerifyOrExit(sizeof(aNetworkDiagTlv.mData.mNetworkData.m8) >= networkData.GetLength(),
-                         error = OT_ERROR_PARSE);
+            VerifyOrExit(networkData.IsValid(), error = kErrorParse);
+            VerifyOrExit(sizeof(aNetworkDiagTlv.mData.mNetworkData.m8) >= networkData.GetLength(), error = kErrorParse);
 
             memcpy(aNetworkDiagTlv.mData.mNetworkData.m8, networkData.GetNetworkData(), networkData.GetLength());
             aNetworkDiagTlv.mData.mNetworkData.mCount = networkData.GetLength();
@@ -819,9 +816,9 @@
         {
             Ip6AddressListTlv &ip6AddrList = static_cast<Ip6AddressListTlv &>(tlv);
 
-            VerifyOrExit(ip6AddrList.IsValid(), error = OT_ERROR_PARSE);
+            VerifyOrExit(ip6AddrList.IsValid(), error = kErrorParse);
             VerifyOrExit(sizeof(aNetworkDiagTlv.mData.mIp6AddrList.mList) >= ip6AddrList.GetLength(),
-                         error = OT_ERROR_PARSE);
+                         error = kErrorParse);
             SuccessOrExit(error = aMessage.Read(offset + sizeof(ip6AddrList), aNetworkDiagTlv.mData.mIp6AddrList.mList,
                                                 ip6AddrList.GetLength()));
             aNetworkDiagTlv.mData.mIp6AddrList.mCount = ip6AddrList.GetLength() / OT_IP6_ADDRESS_SIZE;
@@ -833,7 +830,7 @@
             MacCountersTlv macCounters;
 
             SuccessOrExit(error = aMessage.Read(offset, macCounters));
-            VerifyOrExit(macCounters.IsValid(), error = OT_ERROR_PARSE);
+            VerifyOrExit(macCounters.IsValid(), error = kErrorParse);
 
             ParseMacCounters(macCounters, aNetworkDiagTlv.mData.mMacCounters);
             break;
@@ -851,15 +848,14 @@
         {
             ChildTableTlv &childTable = static_cast<ChildTableTlv &>(tlv);
 
-            VerifyOrExit(childTable.IsValid(), error = OT_ERROR_PARSE);
+            VerifyOrExit(childTable.IsValid(), error = kErrorParse);
             VerifyOrExit(childTable.GetNumEntries() <= OT_ARRAY_LENGTH(aNetworkDiagTlv.mData.mChildTable.mTable),
-                         error = OT_ERROR_PARSE);
+                         error = kErrorParse);
 
             for (uint8_t i = 0; i < childTable.GetNumEntries(); ++i)
             {
                 ChildTableEntry childEntry;
-                VerifyOrExit(childTable.ReadEntry(childEntry, aMessage, offset, i) == OT_ERROR_NONE,
-                             error = OT_ERROR_PARSE);
+                VerifyOrExit(childTable.ReadEntry(childEntry, aMessage, offset, i) == kErrorNone, error = kErrorParse);
                 ParseChildEntry(childEntry, aNetworkDiagTlv.mData.mChildTable.mTable[i]);
             }
             aNetworkDiagTlv.mData.mChildTable.mCount = childTable.GetNumEntries();
@@ -868,7 +864,7 @@
 
         case NetworkDiagnosticTlv::kChannelPages:
         {
-            VerifyOrExit(sizeof(aNetworkDiagTlv.mData.mChannelPages.m8) >= tlv.GetLength(), error = OT_ERROR_PARSE);
+            VerifyOrExit(sizeof(aNetworkDiagTlv.mData.mChannelPages.m8) >= tlv.GetLength(), error = kErrorParse);
             SuccessOrExit(
                 error = aMessage.Read(offset + sizeof(tlv), aNetworkDiagTlv.mData.mChannelPages.m8, tlv.GetLength()));
             aNetworkDiagTlv.mData.mChannelPages.mCount = tlv.GetLength();
diff --git a/src/core/thread/network_diagnostic.hpp b/src/core/thread/network_diagnostic.hpp
index 510a9ea..e5a2b68 100644
--- a/src/core/thread/network_diagnostic.hpp
+++ b/src/core/thread/network_diagnostic.hpp
@@ -82,25 +82,22 @@
     explicit NetworkDiagnostic(Instance &aInstance);
 
     /**
-     * This method registers a callback to provide received raw DIAG_GET.rsp or an DIAG_GET.ans payload.
-     *
-     * @param[in]  aCallback         A pointer to a function that is called when an DIAG_GET.rsp or an DIAG_GET.ans
-     *                               is received or nullptr to disable the callback.
-     * @param[in]  aCallbackContext  A pointer to application-specific context.
-     *
-     */
-    void SetReceiveDiagnosticGetCallback(otReceiveDiagnosticGetCallback aCallback, void *aCallbackContext);
-
-    /**
      * This method sends Diagnostic Get request. If the @p aDestination is of multicast type, the DIAG_GET.qry
      * message is sent or the DIAG_GET.req otherwise.
      *
-     * @param[in] aDestination  A reference to the destination address.
-     * @param[in] aTlvTypes     An array of Network Diagnostic TLV types.
-     * @param[in] aCount        Number of types in aTlvTypes
+     * @param[in]  aDestination      A reference to the destination address.
+     * @param[in]  aTlvTypes         An array of Network Diagnostic TLV types.
+     * @param[in]  aCount            Number of types in aTlvTypes.
+     * @param[in]  aCallback         A pointer to a function that is called when Network Diagnostic Get response
+     *                               is received or NULL to disable the callback.
+     * @param[in]  aCallbackContext  A pointer to application-specific context.
      *
      */
-    otError SendDiagnosticGet(const Ip6::Address &aDestination, const uint8_t aTlvTypes[], uint8_t aCount);
+    Error SendDiagnosticGet(const Ip6::Address &           aDestination,
+                            const uint8_t                  aTlvTypes[],
+                            uint8_t                        aCount,
+                            otReceiveDiagnosticGetCallback aCallback,
+                            void *                         aCallbackContext);
 
     /**
      * This method sends Diagnostic Reset request.
@@ -110,7 +107,7 @@
      * @param[in] aCount        Number of types in aTlvTypes
      *
      */
-    otError SendDiagnosticReset(const Ip6::Address &aDestination, const uint8_t aTlvTypes[], uint8_t aCount);
+    Error SendDiagnosticReset(const Ip6::Address &aDestination, const uint8_t aTlvTypes[], uint8_t aCount);
 
     /**
      * This static method gets the next Network Diagnostic TLV in a given message.
@@ -119,20 +116,18 @@
      * @param[inout]  aIterator        The Network Diagnostic iterator. To get the first TLV set it to `kIteratorInit`.
      * @param[out]    aNetworkDiagTlv  A reference to a Network Diagnostic TLV to output the next TLV.
      *
-     * @retval OT_ERROR_NONE       Successfully found the next Network Diagnostic TLV.
-     * @retval OT_ERROR_NOT_FOUND  No subsequent Network Diagnostic TLV exists in the message.
-     * @retval OT_ERROR_PARSE      Parsing the next Network Diagnostic failed.
+     * @retval kErrorNone       Successfully found the next Network Diagnostic TLV.
+     * @retval kErrorNotFound   No subsequent Network Diagnostic TLV exists in the message.
+     * @retval kErrorParse      Parsing the next Network Diagnostic failed.
      *
      */
-    static otError GetNextDiagTlv(const Coap::Message &aMessage,
-                                  Iterator &           aIterator,
-                                  otNetworkDiagTlv &   aNetworkDiagTlv);
+    static Error GetNextDiagTlv(const Coap::Message &aMessage, Iterator &aIterator, otNetworkDiagTlv &aNetworkDiagTlv);
 
 private:
-    otError AppendIp6AddressList(Message &aMessage);
-    otError AppendChildTable(Message &aMessage);
-    void    FillMacCountersTlv(MacCountersTlv &aMacCountersTlv);
-    otError FillRequestedTlvs(const Message &aRequest, Message &aResponse, NetworkDiagnosticTlv &aNetworkDiagnosticTlv);
+    Error AppendIp6AddressList(Message &aMessage);
+    Error AppendChildTable(Message &aMessage);
+    void  FillMacCountersTlv(MacCountersTlv &aMacCountersTlv);
+    Error FillRequestedTlvs(const Message &aRequest, Message &aResponse, NetworkDiagnosticTlv &aNetworkDiagnosticTlv);
 
     static void HandleDiagnosticGetRequest(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
     void        HandleDiagnosticGetRequest(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
@@ -143,8 +138,8 @@
     static void HandleDiagnosticGetResponse(void *               aContext,
                                             otMessage *          aMessage,
                                             const otMessageInfo *aMessageInfo,
-                                            otError              aResult);
-    void HandleDiagnosticGetResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, otError aResult);
+                                            Error                aResult);
+    void HandleDiagnosticGetResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, Error aResult);
 
     static void HandleDiagnosticGetAnswer(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo);
     void        HandleDiagnosticGetAnswer(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
diff --git a/src/core/thread/network_diagnostic_tlvs.hpp b/src/core/thread/network_diagnostic_tlvs.hpp
index 1bb44c3..cd56d08 100644
--- a/src/core/thread/network_diagnostic_tlvs.hpp
+++ b/src/core/thread/network_diagnostic_tlvs.hpp
@@ -1098,6 +1098,7 @@
      * @param[in]  aIndex  The index into the Child Table list.
      *
      * @returns  A reference to the Child Table entry.
+     *
      */
     ChildTableEntry &GetEntry(uint16_t aIndex)
     {
@@ -1112,16 +1113,17 @@
      * @param[in]   aOffset     The offset of the ChildTableTLV in aMessage.
      * @param[in]   aIndex      The index into the Child Table list.
      *
-     * @retval  OT_ERROR_NOT_FOUND  No such entry is found.
-     * @retval  OT_ERROR_NONE       Successfully read the entry.
+     * @retval  kErrorNotFound   No such entry is found.
+     * @retval  kErrorNone       Successfully read the entry.
+     *
      */
-    otError ReadEntry(ChildTableEntry &aEntry, const Message &aMessage, uint16_t aOffset, uint8_t aIndex) const
+    Error ReadEntry(ChildTableEntry &aEntry, const Message &aMessage, uint16_t aOffset, uint8_t aIndex) const
     {
         return ((aIndex < GetNumEntries()) &&
                 (aMessage.Read(aOffset + sizeof(ChildTableTlv) + (aIndex * sizeof(ChildTableEntry)), aEntry) ==
-                 OT_ERROR_NONE))
-                   ? OT_ERROR_NONE
-                   : OT_ERROR_INVALID_ARGS;
+                 kErrorNone))
+                   ? kErrorNone
+                   : kErrorInvalidArgs;
     }
 
 } OT_TOOL_PACKED_END;
diff --git a/src/core/thread/panid_query_server.cpp b/src/core/thread/panid_query_server.cpp
index 8d4b082..77a5e1f 100644
--- a/src/core/thread/panid_query_server.cpp
+++ b/src/core/thread/panid_query_server.cpp
@@ -50,7 +50,7 @@
     : InstanceLocator(aInstance)
     , mChannelMask(0)
     , mPanId(Mac::kPanIdBroadcast)
-    , mTimer(aInstance, PanIdQueryServer::HandleTimer, this)
+    , mTimer(aInstance, PanIdQueryServer::HandleTimer)
     , mPanIdQuery(UriPath::kPanIdQuery, &PanIdQueryServer::HandleQuery, this)
 {
     Get<Tmf::TmfAgent>().AddResource(mPanIdQuery);
@@ -110,12 +110,12 @@
 
 void PanIdQueryServer::SendConflict(void)
 {
-    otError                 error = OT_ERROR_NONE;
+    Error                   error = kErrorNone;
     MeshCoP::ChannelMaskTlv channelMask;
     Ip6::MessageInfo        messageInfo;
     Coap::Message *         message;
 
-    VerifyOrExit((message = MeshCoP::NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = OT_ERROR_NO_BUFS);
+    VerifyOrExit((message = MeshCoP::NewMeshCoPMessage(Get<Tmf::TmfAgent>())) != nullptr, error = kErrorNoBufs);
 
     SuccessOrExit(error = message->InitAsConfirmablePost(UriPath::kPanIdConflict));
     SuccessOrExit(error = message->SetPayloadMarker());
@@ -140,7 +140,7 @@
 
 void PanIdQueryServer::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<PanIdQueryServer>().HandleTimer();
+    aTimer.Get<PanIdQueryServer>().HandleTimer();
 }
 
 void PanIdQueryServer::HandleTimer(void)
diff --git a/src/core/thread/radio_selector.cpp b/src/core/thread/radio_selector.cpp
index f630d89..e7d7c0a 100644
--- a/src/core/thread/radio_selector.cpp
+++ b/src/core/thread/radio_selector.cpp
@@ -127,7 +127,7 @@
     }
 }
 
-void RadioSelector::UpdateOnSendDone(Mac::TxFrame &aFrame, otError aTxError)
+void RadioSelector::UpdateOnSendDone(Mac::TxFrame &aFrame, Error aTxError)
 {
     otLogLevel     logLevel  = OT_LOG_LEVEL_INFO;
     Mac::RadioType radioType = aFrame.GetRadioType();
@@ -140,7 +140,7 @@
         // TREL radio link uses deferred ack model. We ignore
         // `SendDone` event from `Mac` layer with success status and
         // wait for deferred ack callback.
-        VerifyOrExit(aTxError != OT_ERROR_NONE);
+        VerifyOrExit(aTxError != kErrorNone);
     }
 #endif
 
@@ -152,15 +152,14 @@
 
     if (neighbor->GetSupportedRadioTypes().Contains(radioType))
     {
-        logLevel =
-            UpdatePreference(*neighbor, radioType,
-                             (aTxError == OT_ERROR_NONE) ? kPreferenceChangeOnTxSuccess : kPreferenceChangeOnTxError);
+        logLevel = UpdatePreference(
+            *neighbor, radioType, (aTxError == kErrorNone) ? kPreferenceChangeOnTxSuccess : kPreferenceChangeOnTxError);
 
-        Log(logLevel, (aTxError == OT_ERROR_NONE) ? "UpdateOnTxSucc" : "UpdateOnTxErr", radioType, *neighbor);
+        Log(logLevel, (aTxError == kErrorNone) ? "UpdateOnTxSucc" : "UpdateOnTxErr", radioType, *neighbor);
     }
     else
     {
-        VerifyOrExit(aTxError == OT_ERROR_NONE);
+        VerifyOrExit(aTxError == kErrorNone);
         neighbor->AddSupportedRadioType(radioType);
         neighbor->SetRadioPreference(radioType, kInitPreference);
 
@@ -172,7 +171,7 @@
 }
 
 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
-void RadioSelector::UpdateOnDeferredAck(Neighbor &aNeighbor, otError aTxError, bool &aAllowNeighborRemove)
+void RadioSelector::UpdateOnDeferredAck(Neighbor &aNeighbor, Error aTxError, bool &aAllowNeighborRemove)
 {
     otLogLevel logLevel = OT_LOG_LEVEL_INFO;
 
@@ -181,10 +180,10 @@
     if (aNeighbor.GetSupportedRadioTypes().Contains(Mac::kRadioTypeTrel))
     {
         logLevel = UpdatePreference(aNeighbor, Mac::kRadioTypeTrel,
-                                    (aTxError == OT_ERROR_NONE) ? kPreferenceChangeOnDeferredAckSuccess
-                                                                : kPreferenceChangeOnDeferredAckTimeout);
+                                    (aTxError == kErrorNone) ? kPreferenceChangeOnDeferredAckSuccess
+                                                             : kPreferenceChangeOnDeferredAckTimeout);
 
-        Log(logLevel, (aTxError == OT_ERROR_NONE) ? "UpdateOnDefAckSucc" : "UpdateOnDefAckFail", Mac::kRadioTypeTrel,
+        Log(logLevel, (aTxError == kErrorNone) ? "UpdateOnDefAckSucc" : "UpdateOnDefAckFail", Mac::kRadioTypeTrel,
             aNeighbor);
 
         // In case of deferred ack timeout, we check if the neighbor
@@ -192,7 +191,7 @@
         // tx. If it it does, we set `aAllowNeighborRemove` to `false`
         // to ensure neighbor is not removed yet.
 
-        VerifyOrExit(aTxError != OT_ERROR_NONE);
+        VerifyOrExit(aTxError != kErrorNone);
 
         for (Mac::RadioType radio : sRadioSelectionOrder)
         {
@@ -206,7 +205,7 @@
     }
     else
     {
-        VerifyOrExit(aTxError == OT_ERROR_NONE);
+        VerifyOrExit(aTxError == kErrorNone);
         aNeighbor.AddSupportedRadioType(Mac::kRadioTypeTrel);
         aNeighbor.SetRadioPreference(Mac::kRadioTypeTrel, kInitPreference);
 
diff --git a/src/core/thread/radio_selector.hpp b/src/core/thread/radio_selector.hpp
index 369f15b..30471ab 100644
--- a/src/core/thread/radio_selector.hpp
+++ b/src/core/thread/radio_selector.hpp
@@ -140,7 +140,7 @@
      * @param[in] aTxError   The transmission error.
      *
      */
-    void UpdateOnSendDone(Mac::TxFrame &aFrame, otError aTxError);
+    void UpdateOnSendDone(Mac::TxFrame &aFrame, Error aTxError);
 
 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
     /**
@@ -149,11 +149,11 @@
      * The deferred ack model is used by TREL radio link.
      *
      * @param[in]  aNeighbor              The neighbor from which ack was expected
-     * @param[in]  aError                 The deferred ack status (`OT_ERROR_NONE` indicates ack was received).
+     * @param[in]  aError                 The deferred ack status (`kErrorNone` indicates ack was received).
      * @param[out] aAllowNeighborRemove   Boolean variable to output whether the neighbor is allowed to be removed.
      *
      */
-    void UpdateOnDeferredAck(Neighbor &aNeighbor, otError aTxError, bool &aAllowNeighborRemove);
+    void UpdateOnDeferredAck(Neighbor &aNeighbor, Error aTxError, bool &aAllowNeighborRemove);
 #endif
 
     /**
diff --git a/src/core/thread/router_table.cpp b/src/core/thread/router_table.cpp
index ea2efec..e727189 100644
--- a/src/core/thread/router_table.cpp
+++ b/src/core/thread/router_table.cpp
@@ -44,13 +44,13 @@
 
 RouterTable::Iterator::Iterator(Instance &aInstance)
     : InstanceLocator(aInstance)
-    , mRouter(Get<RouterTable>().GetFirstEntry())
+    , ItemPtrIterator(Get<RouterTable>().GetFirstEntry())
 {
 }
 
 void RouterTable::Iterator::Advance(void)
 {
-    mRouter = Get<RouterTable>().GetNextEntry(mRouter);
+    mItem = Get<RouterTable>().GetNextEntry(mItem);
 }
 
 RouterTable::RouterTable(Instance &aInstance)
@@ -261,15 +261,15 @@
     return rval;
 }
 
-otError RouterTable::Release(uint8_t aRouterId)
+Error RouterTable::Release(uint8_t aRouterId)
 {
-    otError  error  = OT_ERROR_NONE;
+    Error    error  = kErrorNone;
     uint16_t rloc16 = Mle::Mle::Rloc16FromRouterId(aRouterId);
 
     OT_ASSERT(aRouterId <= Mle::kMaxRouterId);
 
-    VerifyOrExit(Get<Mle::MleRouter>().IsLeader(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(IsAllocated(aRouterId), error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(Get<Mle::MleRouter>().IsLeader(), error = kErrorInvalidState);
+    VerifyOrExit(IsAllocated(aRouterId), error = kErrorNotFound);
 
     mAllocatedRouterIds.Remove(aRouterId);
     UpdateAllocation();
@@ -397,9 +397,9 @@
     return FindRouter(Router::AddressMatcher(aExtAddress, Router::kInStateAny));
 }
 
-otError RouterTable::GetRouterInfo(uint16_t aRouterId, Router::Info &aRouterInfo)
+Error RouterTable::GetRouterInfo(uint16_t aRouterId, Router::Info &aRouterInfo)
 {
-    otError error = OT_ERROR_NONE;
+    Error   error = kErrorNone;
     Router *router;
     uint8_t routerId;
 
@@ -409,13 +409,13 @@
     }
     else
     {
-        VerifyOrExit(Mle::Mle::IsActiveRouter(aRouterId), error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit(Mle::Mle::IsActiveRouter(aRouterId), error = kErrorInvalidArgs);
         routerId = Mle::Mle::RouterIdFromRloc16(aRouterId);
-        VerifyOrExit(routerId <= Mle::kMaxRouterId, error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit(routerId <= Mle::kMaxRouterId, error = kErrorInvalidArgs);
     }
 
     router = GetRouter(routerId);
-    VerifyOrExit(router != nullptr, error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(router != nullptr, error = kErrorNotFound);
 
     aRouterInfo.SetFrom(*router);
 
diff --git a/src/core/thread/router_table.hpp b/src/core/thread/router_table.hpp
index c798a4f..4942063 100644
--- a/src/core/thread/router_table.hpp
+++ b/src/core/thread/router_table.hpp
@@ -34,6 +34,7 @@
 #if OPENTHREAD_FTD
 
 #include "common/encoding.hpp"
+#include "common/iterator_utils.hpp"
 #include "common/locator.hpp"
 #include "common/non_copyable.hpp"
 #include "mac/mac_types.hpp"
@@ -53,8 +54,9 @@
      * This class represents an iterator for iterating through entries in the router table.
      *
      */
-    class Iterator : public InstanceLocator
+    class Iterator : public InstanceLocator, public ItemPtrIterator<Router, Iterator>
     {
+        friend class ItemPtrIterator<Router, Iterator>;
         friend class IteratorBuilder;
 
     public:
@@ -66,78 +68,6 @@
          */
         explicit Iterator(Instance &aInstance);
 
-        /**
-         * This method indicates if the iterator has reached the end of the list, i.e., iterator is empty.
-         *
-         * @retval TRUE   The iterator has reached the end of the list.
-         * @retval FALSE  The iterator currently points to a valid entry.
-         *
-         */
-        bool IsDone(void) const { return (mRouter == nullptr); }
-
-        /**
-         * This method overloads `++` operator (pre-increment) to advance the iterator.
-         *
-         * The iterator is moved to point to the next entry.  If there are no more entries matching the iterator
-         * becomes empty (i.e., `IsDone()` returns `true`).
-         *
-         */
-        void operator++(void) { Advance(); }
-
-        /**
-         * This method overloads `++` operator (post-increment) to advance the iterator.
-         *
-         * The iterator is moved to point to the next entry.  If there are no more entries matching the iterator
-         * becomes empty (i.e., `IsDone()` returns `true`).
-         *
-         */
-        void operator++(int) { Advance(); }
-
-        /**
-         * This method overloads the `*` dereference operator and gets a reference to `Router` entry to which the
-         * iterator is currently pointing.
-         *
-         * This method MUST be used when the iterator is not empty/finished (i.e., `IsDone()` returns `false`).
-         *
-         * @returns A reference to the `Router` entry currently pointed by the iterator.
-         *
-         */
-        Router &operator*(void) { return *mRouter; }
-
-        /**
-         * This method overloads the `->` dereference operator and gets a pointer to `Router` entry to which the
-         * iterator is currently pointing.
-         *
-         * @returns A pointer to the `Router` entry associated with the iterator, or `nullptr` if iterator is
-         * empty/done.
-         *
-         */
-        Router *operator->(void) { return mRouter; }
-
-        /**
-         * This method overloads operator `==` to evaluate whether or not two `Iterator` instances point to the same
-         * router entry.
-         *
-         * @param[in]  aOther  The other `Iterator` to compare with.
-         *
-         * @retval TRUE   If the two `Iterator` objects point to the same router entry or both are done.
-         * @retval FALSE  If the two `Iterator` objects do not point to the same router entry.
-         *
-         */
-        bool operator==(const Iterator &aOther) { return mRouter == aOther.mRouter; }
-
-        /**
-         * This method overloads operator `!=` to evaluate whether or not two `Iterator` instances point to the same
-         * router entry.
-         *
-         * @param[in]  aOther  The other `Iterator` to compare with.
-         *
-         * @retval TRUE   If the two `Iterator` objects do not point to the same router entry.
-         * @retval FALSE  If the two `Iterator` objects point to the same router entry or both are done.
-         *
-         */
-        bool operator!=(const Iterator &aOther) { return mRouter != aOther.mRouter; }
-
     private:
         enum IteratorType
         {
@@ -146,13 +76,10 @@
 
         Iterator(Instance &aInstance, IteratorType)
             : InstanceLocator(aInstance)
-            , mRouter(nullptr)
         {
         }
 
         void Advance(void);
-
-        Router *mRouter;
     };
 
     /**
@@ -196,12 +123,12 @@
      *
      * @param[in]  aRouterId  The router id.
      *
-     * @retval OT_ERROR_NONE           Successfully released the router id.
-     * @retval OT_ERROR_INVALID_STATE  The device is not currently operating as a leader.
-     * @retval OT_ERROR_NOT_FOUND      The router id is not currently allocated.
+     * @retval kErrorNone          Successfully released the router id.
+     * @retval kErrorInvalidState  The device is not currently operating as a leader.
+     * @retval kErrorNotFound      The router id is not currently allocated.
      *
      */
-    otError Release(uint8_t aRouterId);
+    Error Release(uint8_t aRouterId);
 
     /**
      * This method removes a router link.
@@ -338,12 +265,12 @@
      * @param[in]   aRouterId    The router ID or RLOC16 for a given router.
      * @param[out]  aRouterInfo  The router information.
      *
-     * @retval OT_ERROR_NONE          Successfully retrieved the router info for given id.
-     * @retval OT_ERROR_INVALID_ARGS  @p aRouterId is not a valid value for a router.
-     * @retval OT_ERROR_NOT_FOUND     No router entry with the given id.
+     * @retval kErrorNone          Successfully retrieved the router info for given id.
+     * @retval kErrorInvalidArgs   @p aRouterId is not a valid value for a router.
+     * @retval kErrorNotFound      No router entry with the given id.
      *
      */
-    otError GetRouterInfo(uint16_t aRouterId, Router::Info &aRouterInfo);
+    Error GetRouterInfo(uint16_t aRouterId, Router::Info &aRouterInfo);
 
     /**
      * This method returns the Router ID Sequence.
diff --git a/src/core/thread/src_match_controller.cpp b/src/core/thread/src_match_controller.cpp
index aeb501e..d16df33 100644
--- a/src/core/thread/src_match_controller.cpp
+++ b/src/core/thread/src_match_controller.cpp
@@ -134,7 +134,7 @@
     }
     else
     {
-        VerifyOrExit(AddAddress(aChild) == OT_ERROR_NONE, Enable(false));
+        VerifyOrExit(AddAddress(aChild) == kErrorNone, Enable(false));
         aChild.SetIndirectSourceMatchPending(false);
     }
 
@@ -142,16 +142,16 @@
     return;
 }
 
-otError SourceMatchController::AddAddress(const Child &aChild)
+Error SourceMatchController::AddAddress(const Child &aChild)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     if (aChild.IsIndirectSourceMatchShort())
     {
         error = Get<Radio>().AddSrcMatchShortEntry(aChild.GetRloc16());
 
-        otLogDebgMac("SrcAddrMatch - Adding short addr: 0x%04x -- %s (%d)", aChild.GetRloc16(),
-                     otThreadErrorToString(error), error);
+        otLogDebgMac("SrcAddrMatch - Adding short addr: 0x%04x -- %s (%d)", aChild.GetRloc16(), ErrorToString(error),
+                     error);
     }
     else
     {
@@ -161,7 +161,7 @@
         error = Get<Radio>().AddSrcMatchExtEntry(address);
 
         otLogDebgMac("SrcAddrMatch - Adding addr: %s -- %s (%d)", aChild.GetExtAddress().ToString().AsCString(),
-                     otThreadErrorToString(error), error);
+                     ErrorToString(error), error);
     }
 
     return error;
@@ -169,7 +169,7 @@
 
 void SourceMatchController::ClearEntry(Child &aChild)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     if (aChild.IsIndirectSourceMatchPending())
     {
@@ -182,8 +182,8 @@
     {
         error = Get<Radio>().ClearSrcMatchShortEntry(aChild.GetRloc16());
 
-        otLogDebgMac("SrcAddrMatch - Clearing short addr: 0x%04x -- %s (%d)", aChild.GetRloc16(),
-                     otThreadErrorToString(error), error);
+        otLogDebgMac("SrcAddrMatch - Clearing short addr: 0x%04x -- %s (%d)", aChild.GetRloc16(), ErrorToString(error),
+                     error);
     }
     else
     {
@@ -193,7 +193,7 @@
         error = Get<Radio>().ClearSrcMatchExtEntry(address);
 
         otLogDebgMac("SrcAddrMatch - Clearing addr: %s -- %s (%d)", aChild.GetExtAddress().ToString().AsCString(),
-                     otThreadErrorToString(error), error);
+                     ErrorToString(error), error);
     }
 
     SuccessOrExit(error);
@@ -208,9 +208,9 @@
     return;
 }
 
-otError SourceMatchController::AddPendingEntries(void)
+Error SourceMatchController::AddPendingEntries(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     for (Child &child : Get<ChildTable>().Iterate(Child::kInStateValidOrRestoring))
     {
diff --git a/src/core/thread/src_match_controller.hpp b/src/core/thread/src_match_controller.hpp
index 83abe9d..f41d1e8 100644
--- a/src/core/thread/src_match_controller.hpp
+++ b/src/core/thread/src_match_controller.hpp
@@ -36,7 +36,7 @@
 
 #include "openthread-core-config.h"
 
-#include <openthread/error.h>
+#include "common/error.hpp"
 #include "common/locator.hpp"
 #include "common/non_copyable.hpp"
 
@@ -169,20 +169,20 @@
      *
      * @param[in] aChild            A reference to the child
      *
-     * @retval OT_ERROR_NONE     Child's address was added successfully to the source match table.
-     * @retval OT_ERROR_NO_BUFS  No available space in the source match table.
+     * @retval kErrorNone     Child's address was added successfully to the source match table.
+     * @retval kErrorNoBufs   No available space in the source match table.
      *
      */
-    otError AddAddress(const Child &aChild);
+    Error AddAddress(const Child &aChild);
 
     /**
      * This method adds all pending entries to the source match table.
      *
-     * @retval OT_ERROR_NONE     All pending entries were successfully added.
-     * @retval OT_ERROR_NO_BUFS  No available space in the source match table.
+     * @retval kErrorNone     All pending entries were successfully added.
+     * @retval kErrorNoBufs   No available space in the source match table.
      *
      */
-    otError AddPendingEntries(void);
+    Error AddPendingEntries(void);
 
     bool mEnabled;
 };
diff --git a/src/core/thread/thread_netif.cpp b/src/core/thread/thread_netif.cpp
index faf611b..3824c1d 100644
--- a/src/core/thread/thread_netif.cpp
+++ b/src/core/thread/thread_netif.cpp
@@ -62,6 +62,12 @@
 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
     , mDnsClient(aInstance)
 #endif
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+    , mSrpClient(aInstance)
+#endif
+#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
+    , mDnssdServer(aInstance)
+#endif
 #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
     , mSntpClient(aInstance)
 #endif
@@ -83,6 +89,7 @@
 #if OPENTHREAD_FTD || OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
     , mNetworkDataNotifier(aInstance)
 #endif
+    , mNetworkDataServiceManager(aInstance)
 #if OPENTHREAD_FTD || OPENTHREAD_CONFIG_TMF_NETWORK_DIAG_MTD_ENABLE
     , mNetworkDiagnostic(aInstance)
 #endif
@@ -121,6 +128,10 @@
 #if OPENTHREAD_CONFIG_DUA_ENABLE || (OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE)
     , mDuaManager(aInstance)
 #endif
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+    , mSrpServer(aInstance)
+#endif
+
     , mChildSupervisor(aInstance)
     , mSupervisionListener(aInstance)
     , mAnnounceBegin(aInstance)
@@ -151,6 +162,9 @@
     SubscribeAllNodesMulticast();
     IgnoreError(Get<Mle::MleRouter>().Enable());
     IgnoreError(Get<Tmf::TmfAgent>().Start());
+#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
+    IgnoreError(Get<Dns::ServiceDiscovery::Server>().Start());
+#endif
 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
     IgnoreError(Get<Dns::Client>().Start());
 #endif
@@ -168,11 +182,14 @@
     VerifyOrExit(mIsUp);
 
 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
-    IgnoreError(Get<Dns::Client>().Stop());
+    Get<Dns::Client>().Stop();
 #endif
 #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
     IgnoreError(Get<Sntp::Client>().Stop());
 #endif
+#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
+    Get<Dns::ServiceDiscovery::Server>().Stop();
+#endif
 #if OPENTHREAD_CONFIG_DTLS_ENABLE
     Get<Coap::CoapSecure>().Stop();
 #endif
@@ -194,16 +211,16 @@
     return;
 }
 
-otError ThreadNetif::RouteLookup(const Ip6::Address &aSource, const Ip6::Address &aDestination, uint8_t *aPrefixMatch)
+Error ThreadNetif::RouteLookup(const Ip6::Address &aSource, const Ip6::Address &aDestination, uint8_t *aPrefixMatch)
 {
-    otError  error;
+    Error    error;
     uint16_t rloc;
 
     SuccessOrExit(error = Get<NetworkData::Leader>().RouteLookup(aSource, aDestination, aPrefixMatch, &rloc));
 
     if (rloc == Get<Mle::MleRouter>().GetRloc16())
     {
-        error = OT_ERROR_NO_ROUTE;
+        error = kErrorNoRoute;
     }
 
 exit:
diff --git a/src/core/thread/thread_netif.hpp b/src/core/thread/thread_netif.hpp
index 7a61830..3fd86cf 100644
--- a/src/core/thread/thread_netif.hpp
+++ b/src/core/thread/thread_netif.hpp
@@ -64,6 +64,10 @@
 #include "thread/dua_manager.hpp"
 #endif
 
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+#include "net/srp_server.hpp"
+#endif
+
 #include "meshcop/dataset_manager.hpp"
 
 #if OPENTHREAD_CONFIG_JOINER_ENABLE
@@ -76,9 +80,11 @@
 #include "net/dhcp6_client.hpp"
 #include "net/dhcp6_server.hpp"
 #include "net/dns_client.hpp"
+#include "net/dnssd_server.hpp"
 #include "net/ip6_filter.hpp"
 #include "net/netif.hpp"
 #include "net/sntp_client.hpp"
+#include "net/srp_client.hpp"
 #include "thread/address_resolver.hpp"
 #include "thread/announce_begin_server.hpp"
 #include "thread/discover_scanner.hpp"
@@ -90,6 +96,7 @@
 #include "thread/mle_router.hpp"
 #include "thread/network_data_local.hpp"
 #include "thread/network_data_notifier.hpp"
+#include "thread/network_data_service.hpp"
 #include "thread/network_diagnostic.hpp"
 #include "thread/panid_query_server.hpp"
 #include "thread/radio_selector.hpp"
@@ -154,10 +161,10 @@
      *
      * @param[in]  aMessage  A reference to the message.
      *
-     * @retval OT_ERROR_NONE  Successfully submitted the message to the interface.
+     * @retval kErrorNone  Successfully submitted the message to the interface.
      *
      */
-    otError SendMessage(Message &aMessage) { return mMeshForwarder.SendMessage(aMessage); }
+    Error SendMessage(Message &aMessage) { return mMeshForwarder.SendMessage(aMessage); }
 
     /**
      * This method performs a route lookup.
@@ -166,11 +173,11 @@
      * @param[in]   aDestination  A reference to the IPv6 destination address.
      * @param[out]  aPrefixMatch  A pointer where the number of prefix match bits for the chosen route is stored.
      *
-     * @retval OT_ERROR_NONE      Successfully found a route.
-     * @retval OT_ERROR_NO_ROUTE  Could not find a valid route.
+     * @retval kErrorNone      Successfully found a route.
+     * @retval kErrorNoRoute   Could not find a valid route.
      *
      */
-    otError RouteLookup(const Ip6::Address &aSource, const Ip6::Address &aDestination, uint8_t *aPrefixMatch);
+    Error RouteLookup(const Ip6::Address &aSource, const Ip6::Address &aDestination, uint8_t *aPrefixMatch);
 
     /**
      * This method indicates whether @p aAddress matches an on-mesh prefix.
@@ -196,10 +203,16 @@
 #endif
 #if OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
     Dns::Client mDnsClient;
-#endif // OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE
+#endif
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+    Srp::Client mSrpClient;
+#endif
+#if OPENTHREAD_CONFIG_DNSSD_SERVER_ENABLE
+    Dns::ServiceDiscovery::Server mDnssdServer;
+#endif
 #if OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
     Sntp::Client mSntpClient;
-#endif // OPENTHREAD_CONFIG_SNTP_CLIENT_ENABLE
+#endif
     MeshCoP::ActiveDataset  mActiveDataset;
     MeshCoP::PendingDataset mPendingDataset;
     Ip6::Filter             mIp6Filter;
@@ -219,6 +232,7 @@
 #if OPENTHREAD_FTD || OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE || OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
     NetworkData::Notifier mNetworkDataNotifier;
 #endif
+    NetworkData::Service::Manager mNetworkDataServiceManager;
 #if OPENTHREAD_FTD || OPENTHREAD_CONFIG_TMF_NETWORK_DIAG_MTD_ENABLE
     NetworkDiagnostic::NetworkDiagnostic mNetworkDiagnostic;
 #endif // OPENTHREAD_FTD || OPENTHREAD_CONFIG_TMF_NETWORK_DIAG_MTD_ENABLE
@@ -262,6 +276,10 @@
 #if OPENTHREAD_CONFIG_DUA_ENABLE || (OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_DUA_ENABLE)
     DuaManager mDuaManager;
 #endif
+#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+    Srp::Server mSrpServer;
+#endif
+
     Utils::ChildSupervisor     mChildSupervisor;
     Utils::SupervisionListener mSupervisionListener;
     AnnounceBeginServer        mAnnounceBegin;
diff --git a/src/core/thread/thread_tlvs.hpp b/src/core/thread/thread_tlvs.hpp
index fd112a0..f4c349b 100644
--- a/src/core/thread/thread_tlvs.hpp
+++ b/src/core/thread/thread_tlvs.hpp
@@ -51,8 +51,8 @@
 enum
 {
     // Thread 1.2.0 5.19.13 limits the number of IPv6 addresses should be [1, 15].
-    kIPv6AddressesNumMin = 1,
-    kIPv6AddressesNumMax = 15,
+    kIp6AddressesNumMin = 1,
+    kIp6AddressesNumMax = 15,
 };
 
 /**
@@ -76,12 +76,12 @@
         kStatus                = 4,  ///< Status TLV
         kLastTransactionTime   = 6,  ///< Time Since Last Transaction TLV
         kRouterMask            = 7,  ///< Router Mask TLV
-        kNDOption              = 8,  ///< ND Option TLV
-        kNDData                = 9,  ///< ND Data TLV
+        kNdOption              = 8,  ///< ND Option TLV
+        kNdData                = 9,  ///< ND Data TLV
         kThreadNetworkData     = 10, ///< Thread Network Data TLV
         kTimeout               = 11, ///< Timeout TLV
         kNetworkName           = 12, ///< Network Name TLV
-        kIPv6Addresses         = 14, ///< IPv6 Addresses TLV
+        kIp6Addresses          = 14, ///< IPv6 Addresses TLV
         kCommissionerSessionId = 15, ///< Commissioner Session ID TLV
     };
 
@@ -316,14 +316,14 @@
  *
  */
 OT_TOOL_PACKED_BEGIN
-class IPv6AddressesTlv : public ThreadTlv, public TlvInfo<ThreadTlv::kIPv6Addresses>
+class Ip6AddressesTlv : public ThreadTlv, public TlvInfo<ThreadTlv::kIp6Addresses>
 {
 public:
     /**
      * This method initializes the TLV.
      *
      */
-    void Init(void) { SetType(kIPv6Addresses); }
+    void Init(void) { SetType(kIp6Addresses); }
 
     /**
      * This method indicates whether or not the TLV appears to be well-formed.
@@ -334,8 +334,8 @@
      */
     bool IsValid(void) const
     {
-        return GetLength() >= sizeof(Ip6::Address) * kIPv6AddressesNumMin &&
-               GetLength() <= sizeof(Ip6::Address) * kIPv6AddressesNumMax && (GetLength() % sizeof(Ip6::Address)) == 0;
+        return GetLength() >= sizeof(Ip6::Address) * kIp6AddressesNumMin &&
+               GetLength() <= sizeof(Ip6::Address) * kIp6AddressesNumMax && (GetLength() % sizeof(Ip6::Address)) == 0;
     }
 
     /**
diff --git a/src/core/thread/time_sync_service.cpp b/src/core/thread/time_sync_service.cpp
index eeee380..31abebb 100644
--- a/src/core/thread/time_sync_service.cpp
+++ b/src/core/thread/time_sync_service.cpp
@@ -62,7 +62,7 @@
     , mNetworkTimeOffset(0)
     , mTimeSyncCallback(nullptr)
     , mTimeSyncCallbackContext(nullptr)
-    , mTimer(aInstance, HandleTimeout, this)
+    , mTimer(aInstance, HandleTimeout)
     , mCurrentStatus(OT_NETWORK_TIME_UNSYNCHRONIZED)
 {
     CheckAndHandleChanges(false);
@@ -161,7 +161,7 @@
 
     if (mTimeSyncRequired)
     {
-        VerifyOrExit(Get<Mle::MleRouter>().SendTimeSync() == OT_ERROR_NONE);
+        VerifyOrExit(Get<Mle::MleRouter>().SendTimeSync() == kErrorNone);
 
         mLastTimeSyncSent = TimerMilli::GetNow();
         mTimeSyncRequired = false;
@@ -210,7 +210,7 @@
 
 void TimeSync::HandleTimeout(Timer &aTimer)
 {
-    aTimer.GetOwner<TimeSync>().HandleTimeout();
+    aTimer.Get<TimeSync>().HandleTimeout();
 }
 
 void TimeSync::CheckAndHandleChanges(bool aTimeUpdated)
diff --git a/src/core/thread/tmf.cpp b/src/core/thread/tmf.cpp
index 0d6977f..6ae4c68 100644
--- a/src/core/thread/tmf.cpp
+++ b/src/core/thread/tmf.cpp
@@ -38,16 +38,16 @@
 namespace ot {
 namespace Tmf {
 
-otError TmfAgent::Start(void)
+Error TmfAgent::Start(void)
 {
     return Coap::Start(kUdpPort, OT_NETIF_THREAD);
 }
 
-otError TmfAgent::Filter(const ot::Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo, void *aContext)
+Error TmfAgent::Filter(const ot::Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo, void *aContext)
 {
     OT_UNUSED_VARIABLE(aMessage);
 
-    return static_cast<TmfAgent *>(aContext)->IsTmfMessage(aMessageInfo) ? OT_ERROR_NONE : OT_ERROR_NOT_TMF;
+    return static_cast<TmfAgent *>(aContext)->IsTmfMessage(aMessageInfo) ? kErrorNone : kErrorNotTmf;
 }
 
 bool TmfAgent::IsTmfMessage(const Ip6::MessageInfo &aMessageInfo) const
diff --git a/src/core/thread/tmf.hpp b/src/core/thread/tmf.hpp
index c3a0787..6de0173 100644
--- a/src/core/thread/tmf.hpp
+++ b/src/core/thread/tmf.hpp
@@ -66,11 +66,11 @@
     /**
      * This method starts the TMF agent.
      *
-     * @retval OT_ERROR_NONE    Successfully started the CoAP service.
-     * @retval OT_ERROR_FAILED  Failed to start the TMF agent.
+     * @retval kErrorNone    Successfully started the CoAP service.
+     * @retval kErrorFailed  Failed to start the TMF agent.
      *
      */
-    otError Start(void);
+    Error Start(void);
 
     /**
      * This method returns whether Thread Management Framework Addressing Rules are met.
@@ -82,7 +82,7 @@
     bool IsTmfMessage(const Ip6::MessageInfo &aMessageInfo) const;
 
 private:
-    static otError Filter(const ot::Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo, void *aContext);
+    static Error Filter(const ot::Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo, void *aContext);
 };
 
 } // namespace Tmf
diff --git a/src/core/thread/topology.cpp b/src/core/thread/topology.cpp
index 3c94c34..f169ee4 100644
--- a/src/core/thread/topology.cpp
+++ b/src/core/thread/topology.cpp
@@ -234,7 +234,7 @@
     mFullThreadDevice   = aChild.IsFullThreadDevice();
     mFullNetworkData    = aChild.IsFullNetworkData();
     mIsStateRestoring   = aChild.IsStateRestoring();
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     mIsCslSynced = aChild.IsCslSynchronized();
 #else
     mIsCslSynced = false;
@@ -255,13 +255,13 @@
 {
     const Ip6::Address *address;
 
+    if ((mIndex == 0) && (mChild.GetMeshLocalIp6Address(mMeshLocalAddress) != kErrorNone))
+    {
+        mIndex++;
+    }
+
     while (true)
     {
-        if ((mIndex == 0) && (mChild.GetMeshLocalIp6Address(mMeshLocalAddress) != OT_ERROR_NONE))
-        {
-            mIndex++;
-        }
-
         address = GetAddress();
 
         VerifyOrExit((address != nullptr) && !address->IsUnspecified(), mIndex = kMaxIndex);
@@ -292,11 +292,11 @@
 #endif
 }
 
-otError Child::GetMeshLocalIp6Address(Ip6::Address &aAddress) const
+Error Child::GetMeshLocalIp6Address(Ip6::Address &aAddress) const
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(!mMeshLocalIid.IsUnspecified(), error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(!mMeshLocalIid.IsUnspecified(), error = kErrorNotFound);
 
     aAddress.SetPrefix(Get<Mle::MleRouter>().GetMeshLocalPrefix());
     aAddress.SetIid(mMeshLocalIid);
@@ -305,15 +305,15 @@
     return error;
 }
 
-otError Child::AddIp6Address(const Ip6::Address &aAddress)
+Error Child::AddIp6Address(const Ip6::Address &aAddress)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(!aAddress.IsUnspecified(), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(!aAddress.IsUnspecified(), error = kErrorInvalidArgs);
 
     if (Get<Mle::MleRouter>().IsMeshLocalAddress(aAddress))
     {
-        VerifyOrExit(mMeshLocalIid.IsUnspecified(), error = OT_ERROR_ALREADY);
+        VerifyOrExit(mMeshLocalIid.IsUnspecified(), error = kErrorAlready);
         mMeshLocalIid = aAddress.GetIid();
         ExitNow();
     }
@@ -326,28 +326,28 @@
             ExitNow();
         }
 
-        VerifyOrExit(ip6Address != aAddress, error = OT_ERROR_ALREADY);
+        VerifyOrExit(ip6Address != aAddress, error = kErrorAlready);
     }
 
-    error = OT_ERROR_NO_BUFS;
+    error = kErrorNoBufs;
 
 exit:
     return error;
 }
 
-otError Child::RemoveIp6Address(const Ip6::Address &aAddress)
+Error Child::RemoveIp6Address(const Ip6::Address &aAddress)
 {
-    otError  error = OT_ERROR_NOT_FOUND;
+    Error    error = kErrorNotFound;
     uint16_t index;
 
-    VerifyOrExit(!aAddress.IsUnspecified(), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(!aAddress.IsUnspecified(), error = kErrorInvalidArgs);
 
     if (Get<Mle::MleRouter>().IsMeshLocalAddress(aAddress))
     {
         if (aAddress.GetIid() == mMeshLocalIid)
         {
             mMeshLocalIid.Clear();
-            error = OT_ERROR_NONE;
+            error = kErrorNone;
         }
 
         ExitNow();
@@ -359,7 +359,7 @@
 
         if (mIp6Address[index] == aAddress)
         {
-            error = OT_ERROR_NONE;
+            error = kErrorNone;
             break;
         }
     }
diff --git a/src/core/thread/topology.hpp b/src/core/thread/topology.hpp
index dd066e5..657df3a 100644
--- a/src/core/thread/topology.hpp
+++ b/src/core/thread/topology.hpp
@@ -39,6 +39,7 @@
 #include <openthread/thread_ftd.h>
 
 #include "common/clearable.hpp"
+#include "common/equatable.hpp"
 #include "common/linked_list.hpp"
 #include "common/locator.hpp"
 #include "common/message.hpp"
@@ -821,7 +822,7 @@
 class Child : public Neighbor,
               public IndirectSender::ChildInfo,
               public DataPollHandler::ChildInfo
-#if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
     ,
               public CslTxScheduler::ChildInfo
 #endif
@@ -854,7 +855,7 @@
      * This class defines an iterator used to go through IPv6 address entries of a child.
      *
      */
-    class AddressIterator
+    class AddressIterator : public Unequatable<AddressIterator>
     {
         friend class AddressIteratorBuilder;
 
@@ -969,19 +970,6 @@
          */
         bool operator==(const AddressIterator &aOther) const { return (mIndex == aOther.mIndex); }
 
-        /**
-         * This method overloads operator `!=` to evaluate whether or not two `Iterator` instances are unequal.
-         *
-         * This method MUST be used when the two iterators are associated with the same `Child` entry.
-         *
-         * @param[in]  aOther  The other `Iterator` to compare with.
-         *
-         * @retval TRUE   If the two `Iterator` objects are unequal.
-         * @retval FALSE  If the two `Iterator` objects are not unequal.
-         *
-         */
-        bool operator!=(const AddressIterator &aOther) const { return !(*this == aOther); }
-
     private:
         enum IteratorType : uint8_t
         {
@@ -1032,11 +1020,11 @@
      *
      * @param[out]   aAddress            A reference to an IPv6 address to provide address (if any).
      *
-     * @retval       OT_ERROR_NONE       Successfully found the mesh-local address and updated @p aAddress.
-     * @retval       OT_ERROR_NOT_FOUND  No mesh-local IPv6 address in the IPv6 address list.
+     * @retval kErrorNone      Successfully found the mesh-local address and updated @p aAddress.
+     * @retval kErrorNotFound  No mesh-local IPv6 address in the IPv6 address list.
      *
      */
-    otError GetMeshLocalIp6Address(Ip6::Address &aAddress) const;
+    Error GetMeshLocalIp6Address(Ip6::Address &aAddress) const;
 
     /**
      * This method returns the Mesh Local Interface Identifier.
@@ -1073,25 +1061,25 @@
      *
      * @param[in]  aAddress           A reference to IPv6 address to be added.
      *
-     * @retval OT_ERROR_NONE          Successfully added the new address.
-     * @retval OT_ERROR_ALREADY       Address is already in the list.
-     * @retval OT_ERROR_NO_BUFS       Already at maximum number of addresses. No entry available to add the new address.
-     * @retval OT_ERROR_INVALID_ARGS  Address is invalid (it is the Unspecified Address).
+     * @retval kErrorNone          Successfully added the new address.
+     * @retval kErrorAlready       Address is already in the list.
+     * @retval kErrorNoBufs        Already at maximum number of addresses. No entry available to add the new address.
+     * @retval kErrorInvalidArgs   Address is invalid (it is the Unspecified Address).
      *
      */
-    otError AddIp6Address(const Ip6::Address &aAddress);
+    Error AddIp6Address(const Ip6::Address &aAddress);
 
     /**
      * This method removes an IPv6 address from the list.
      *
      * @param[in]  aAddress               A reference to IPv6 address to be removed.
      *
-     * @retval OT_ERROR_NONE              Successfully removed the address.
-     * @retval OT_ERROR_NOT_FOUND         Address was not found in the list.
-     * @retval OT_ERROR_INVALID_ARGS      Address is invalid (it is the Unspecified Address).
+     * @retval kErrorNone             Successfully removed the address.
+     * @retval kErrorNotFound         Address was not found in the list.
+     * @retval kErrorInvalidArgs      Address is invalid (it is the Unspecified Address).
      *
      */
-    otError RemoveIp6Address(const Ip6::Address &aAddress);
+    Error RemoveIp6Address(const Ip6::Address &aAddress);
 
     /**
      * This method indicates whether an IPv6 address is in the list of IPv6 addresses of the child.
diff --git a/src/core/utils/channel_manager.cpp b/src/core/utils/channel_manager.cpp
index 8886682..70b05cb 100644
--- a/src/core/utils/channel_manager.cpp
+++ b/src/core/utils/channel_manager.cpp
@@ -39,8 +39,8 @@
 #include "common/locator-getters.hpp"
 #include "common/logging.hpp"
 #include "common/random.hpp"
+#include "meshcop/dataset_updater.hpp"
 #include "radio/radio.hpp"
-#include "utils/dataset_updater.hpp"
 
 #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD
 
@@ -54,7 +54,7 @@
     , mDelay(kMinimumDelay)
     , mChannel(0)
     , mState(kStateIdle)
-    , mTimer(aInstance, ChannelManager::HandleTimer, this)
+    , mTimer(aInstance, ChannelManager::HandleTimer)
     , mAutoSelectInterval(kDefaultAutoSelectInterval)
     , mAutoSelectEnabled(false)
 {
@@ -73,7 +73,6 @@
     if (mState == kStateChangeInProgress)
     {
         VerifyOrExit(mChannel != aChannel);
-        Get<DatasetUpdater>().CancelUpdate();
     }
 
     mState   = kStateChangeRequested;
@@ -87,11 +86,11 @@
     return;
 }
 
-otError ChannelManager::SetDelay(uint16_t aDelay)
+Error ChannelManager::SetDelay(uint16_t aDelay)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(aDelay >= kMinimumDelay, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aDelay >= kMinimumDelay, error = kErrorInvalidArgs);
     mDelay = aDelay;
 
 exit:
@@ -106,22 +105,22 @@
     dataset.SetChannel(mChannel);
     dataset.SetDelay(Time::SecToMsec(mDelay));
 
-    switch (Get<DatasetUpdater>().RequestUpdate(dataset, HandleDatasetUpdateDone, this, kChangeCheckWaitInterval))
+    switch (Get<MeshCoP::DatasetUpdater>().RequestUpdate(dataset, HandleDatasetUpdateDone, this))
     {
-    case OT_ERROR_NONE:
+    case kErrorNone:
         mState = kStateChangeInProgress;
         // Wait for the `HandleDatasetUpdateDone()` callback.
         break;
 
-    case OT_ERROR_BUSY:
-    case OT_ERROR_NO_BUFS:
+    case kErrorBusy:
+    case kErrorNoBufs:
         mTimer.Start(kPendingDatasetTxRetryInterval);
         break;
 
-    case OT_ERROR_INVALID_STATE:
+    case kErrorInvalidState:
         otLogInfoUtil("ChannelManager: Request to change to channel %d failed. Device is disabled", mChannel);
 
-        // Fall through
+        OT_FALL_THROUGH;
 
     default:
         mState = kStateIdle;
@@ -130,21 +129,21 @@
     }
 }
 
-void ChannelManager::HandleDatasetUpdateDone(otError aError, void *aContext)
+void ChannelManager::HandleDatasetUpdateDone(Error aError, void *aContext)
 {
     static_cast<ChannelManager *>(aContext)->HandleDatasetUpdateDone(aError);
 }
 
-void ChannelManager::HandleDatasetUpdateDone(otError aError)
+void ChannelManager::HandleDatasetUpdateDone(Error aError)
 {
-    if (aError == OT_ERROR_NONE)
+    if (aError == kErrorNone)
     {
         otLogInfoUtil("ChannelManager: Channel changed to %d", mChannel);
     }
     else
     {
         otLogInfoUtil("ChannelManager: Canceling channel change to %d%s", mChannel,
-                      (aError == OT_ERROR_ALREADY) ? " since current ActiveDataset is more recent" : "");
+                      (aError == kErrorAlready) ? " since current ActiveDataset is more recent" : "");
     }
 
     mState = kStateIdle;
@@ -153,7 +152,7 @@
 
 void ChannelManager::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<ChannelManager>().HandleTimer();
+    aTimer.Get<ChannelManager>().HandleTimer();
 }
 
 void ChannelManager::HandleTimer(void)
@@ -177,9 +176,9 @@
 
 #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
 
-otError ChannelManager::FindBetterChannel(uint8_t &aNewChannel, uint16_t &aOccupancy)
+Error ChannelManager::FindBetterChannel(uint8_t &aNewChannel, uint16_t &aOccupancy)
 {
-    otError          error = OT_ERROR_NONE;
+    Error            error = kErrorNone;
     Mac::ChannelMask favoredAndSupported;
     Mac::ChannelMask favoredBest;
     Mac::ChannelMask supportedBest;
@@ -190,7 +189,7 @@
     {
         otLogInfoUtil("ChannelManager: Too few samples (%d <= %d) to select channel",
                       Get<ChannelMonitor>().GetSampleCount(), kMinChannelMonitorSampleCount);
-        ExitNow(error = OT_ERROR_INVALID_STATE);
+        ExitNow(error = kErrorInvalidState);
     }
 
     favoredAndSupported = mFavoredChannelMask;
@@ -220,7 +219,7 @@
         favoredOccupancy = supportedOccupancy;
     }
 
-    VerifyOrExit(!favoredBest.IsEmpty(), error = OT_ERROR_NOT_FOUND);
+    VerifyOrExit(!favoredBest.IsEmpty(), error = kErrorNotFound);
 
     aNewChannel = favoredBest.ChooseRandomChannel();
     aOccupancy  = favoredOccupancy;
@@ -240,16 +239,16 @@
     return shouldAttempt;
 }
 
-otError ChannelManager::RequestChannelSelect(bool aSkipQualityCheck)
+Error ChannelManager::RequestChannelSelect(bool aSkipQualityCheck)
 {
-    otError  error = OT_ERROR_NONE;
+    Error    error = kErrorNone;
     uint8_t  curChannel, newChannel;
     uint16_t curOccupancy, newOccupancy;
 
     otLogInfoUtil("ChannelManager: Request to select channel (skip quality check: %s)",
                   aSkipQualityCheck ? "yes" : "no");
 
-    VerifyOrExit(!Get<Mle::Mle>().IsDisabled(), error = OT_ERROR_INVALID_STATE);
+    VerifyOrExit(!Get<Mle::Mle>().IsDisabled(), error = kErrorInvalidState);
 
     VerifyOrExit(aSkipQualityCheck || ShouldAttemptChannelChange());
 
@@ -281,10 +280,9 @@
 
 exit:
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogInfoUtil("ChannelManager: Request to select better channel failed, error: %s",
-                      otThreadErrorToString(error));
+        otLogInfoUtil("ChannelManager: Request to select better channel failed, error: %s", ErrorToString(error));
     }
 
     return error;
@@ -318,12 +316,12 @@
     }
 }
 
-otError ChannelManager::SetAutoChannelSelectionInterval(uint32_t aInterval)
+Error ChannelManager::SetAutoChannelSelectionInterval(uint32_t aInterval)
 {
-    otError  error        = OT_ERROR_NONE;
+    Error    error        = kErrorNone;
     uint32_t prevInterval = mAutoSelectInterval;
 
-    VerifyOrExit((aInterval != 0) && (aInterval <= Time::MsecToSec(Timer::kMaxDelay)), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit((aInterval != 0) && (aInterval <= Time::MsecToSec(Timer::kMaxDelay)), error = kErrorInvalidArgs);
 
     mAutoSelectInterval = aInterval;
 
diff --git a/src/core/utils/channel_manager.hpp b/src/core/utils/channel_manager.hpp
index a74b0eb..8027647 100644
--- a/src/core/utils/channel_manager.hpp
+++ b/src/core/utils/channel_manager.hpp
@@ -120,11 +120,11 @@
      *
      * @param[in]  aDelay             Delay in seconds.
      *
-     * @retval OT_ERROR_NONE          Delay was updated successfully.
-     * @retval OT_ERROR_INVALID_ARGS  The given delay @p aDelay is shorter than `kMinimumDelay`.
+     * @retval kErrorNone          Delay was updated successfully.
+     * @retval kErrorInvalidArgs   The given delay @p aDelay is shorter than `kMinimumDelay`.
      *
      */
-    otError SetDelay(uint16_t aDelay);
+    Error SetDelay(uint16_t aDelay);
 
     /**
      * This method requests that `ChannelManager` checks and selects a new channel and starts a channel change.
@@ -150,12 +150,12 @@
      *
      * @param[in] aSkipQualityCheck        Indicates whether the quality check (step 1) should be skipped.
      *
-     * @retval OT_ERROR_NONE               Channel selection finished successfully.
-     * @retval OT_ERROR_NOT_FOUND          Supported channels is empty, therefore could not select a channel.
-     * @retval OT_ERROR_INVALID_STATE      Thread is not enabled or not enough data to select new channel.
+     * @retval kErrorNone              Channel selection finished successfully.
+     * @retval kErrorNotFound          Supported channels is empty, therefore could not select a channel.
+     * @retval kErrorInvalidState      Thread is not enabled or not enough data to select new channel.
      *
      */
-    otError RequestChannelSelect(bool aSkipQualityCheck);
+    Error RequestChannelSelect(bool aSkipQualityCheck);
 
     /**
      * This method enables/disables the auto-channel-selection functionality.
@@ -181,11 +181,11 @@
      *
      * @param[in] aInterval            The interval (in seconds).
      *
-     * @retval OT_ERROR_NONE           The interval was set successfully.
-     * @retval OT_ERROR_INVALID_ARGS   The @p aInterval is not valid (zero).
+     * @retval kErrorNone          The interval was set successfully.
+     * @retval kErrorInvalidArgs   The @p aInterval is not valid (zero).
      *
      */
-    otError SetAutoChannelSelectionInterval(uint32_t aInterval);
+    Error SetAutoChannelSelectionInterval(uint32_t aInterval);
 
     /**
      * This method gets the period interval (in seconds) used by auto-channel-selection functionality.
@@ -233,9 +233,6 @@
         // Retry interval to resend Pending Dataset in case of tx failure (in ms).
         kPendingDatasetTxRetryInterval = 20000,
 
-        // Wait time after sending Pending Dataset to check whether the channel was changed (in ms).
-        kChangeCheckWaitInterval = 30000,
-
         // Maximum jitter/wait time to start a requested channel change (in ms).
         kRequestStartJitterInterval = 10000,
 
@@ -265,15 +262,15 @@
     };
 
     void        StartDatasetUpdate(void);
-    static void HandleDatasetUpdateDone(otError aError, void *aContext);
-    void        HandleDatasetUpdateDone(otError aError);
+    static void HandleDatasetUpdateDone(Error aError, void *aContext);
+    void        HandleDatasetUpdateDone(Error aError);
     static void HandleTimer(Timer &aTimer);
     void        HandleTimer(void);
     void        StartAutoSelectTimer(void);
 
 #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
-    otError FindBetterChannel(uint8_t &aNewChannel, uint16_t &aOccupancy);
-    bool    ShouldAttemptChannelChange(void);
+    Error FindBetterChannel(uint8_t &aNewChannel, uint16_t &aOccupancy);
+    bool  ShouldAttemptChannelChange(void);
 #endif
 
     Mac::ChannelMask mSupportedChannelMask;
diff --git a/src/core/utils/channel_monitor.cpp b/src/core/utils/channel_monitor.cpp
index 5c5ba27..9fef24f 100644
--- a/src/core/utils/channel_monitor.cpp
+++ b/src/core/utils/channel_monitor.cpp
@@ -62,16 +62,16 @@
     : InstanceLocator(aInstance)
     , mChannelMaskIndex(0)
     , mSampleCount(0)
-    , mTimer(aInstance, ChannelMonitor::HandleTimer, this)
+    , mTimer(aInstance, ChannelMonitor::HandleTimer)
 {
     memset(mChannelOccupancy, 0, sizeof(mChannelOccupancy));
 }
 
-otError ChannelMonitor::Start(void)
+Error ChannelMonitor::Start(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(!IsRunning(), error = OT_ERROR_ALREADY);
+    VerifyOrExit(!IsRunning(), error = kErrorAlready);
     Clear();
     mTimer.Start(kTimerInterval);
     otLogDebgUtil("ChannelMonitor: Starting");
@@ -80,11 +80,11 @@
     return error;
 }
 
-otError ChannelMonitor::Stop(void)
+Error ChannelMonitor::Stop(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(IsRunning(), error = OT_ERROR_ALREADY);
+    VerifyOrExit(IsRunning(), error = kErrorAlready);
     mTimer.Stop();
     otLogDebgUtil("ChannelMonitor: Stopping");
 
@@ -114,7 +114,7 @@
 
 void ChannelMonitor::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<ChannelMonitor>().HandleTimer();
+    aTimer.Get<ChannelMonitor>().HandleTimer();
 }
 
 void ChannelMonitor::HandleTimer(void)
@@ -212,7 +212,7 @@
 
     channel = Mac::ChannelMask::kChannelIteratorFirst;
 
-    while (aMask.GetNextChannel(channel) == OT_ERROR_NONE)
+    while (aMask.GetNextChannel(channel) == kErrorNone)
     {
         uint16_t occupancy = GetChannelOccupancy(channel);
 
diff --git a/src/core/utils/channel_monitor.hpp b/src/core/utils/channel_monitor.hpp
index 076ae55..3f8c900 100644
--- a/src/core/utils/channel_monitor.hpp
+++ b/src/core/utils/channel_monitor.hpp
@@ -110,22 +110,22 @@
      *
      * Once started, any previously collected data is cleared.
      *
-     * @retval OT_ERROR_NONE      Channel Monitoring started successfully.
-     * @retval OT_ERROR_ALREADY   Channel Monitoring has already been started.
+     * @retval kErrorNone      Channel Monitoring started successfully.
+     * @retval kErrorAlready   Channel Monitoring has already been started.
      *
      */
-    otError Start(void);
+    Error Start(void);
 
     /**
      * This method stops the Channel Monitoring operation.
      *
      * @note After `Stop()`, the previous data is still valid and can be read.
      *
-     * @retval OT_ERROR_NONE      Channel Monitoring stopped successfully.
-     * @retval OT_ERROR_ALREADY   Channel Monitoring has already been stopped.
+     * @retval kErrorNone      Channel Monitoring stopped successfully.
+     * @retval kErrorAlready   Channel Monitoring has already been stopped.
      *
      */
-    otError Stop(void);
+    Error Stop(void);
 
     /**
      * This method indicates whether the Channel Monitoring operation is started and running.
diff --git a/src/core/utils/child_supervision.cpp b/src/core/utils/child_supervision.cpp
index 1ca45ef..e6be59f 100644
--- a/src/core/utils/child_supervision.cpp
+++ b/src/core/utils/child_supervision.cpp
@@ -155,7 +155,7 @@
 SupervisionListener::SupervisionListener(Instance &aInstance)
     : InstanceLocator(aInstance)
     , mTimeout(0)
-    , mTimer(aInstance, SupervisionListener::HandleTimer, this)
+    , mTimer(aInstance, SupervisionListener::HandleTimer)
 {
     SetTimeout(kDefaultTimeout);
 }
@@ -206,7 +206,7 @@
 
 void SupervisionListener::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<SupervisionListener>().HandleTimer();
+    aTimer.Get<SupervisionListener>().HandleTimer();
 }
 
 void SupervisionListener::HandleTimer(void)
diff --git a/src/core/utils/dataset_updater.cpp b/src/core/utils/dataset_updater.cpp
deleted file mode 100644
index 10aea32..0000000
--- a/src/core/utils/dataset_updater.cpp
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- *  Copyright (c) 2020, The OpenThread Authors.
- *  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
- *   This file implements Dataset Updater.
- *
- */
-
-#include "dataset_updater.hpp"
-
-#include "common/code_utils.hpp"
-#include "common/instance.hpp"
-#include "common/locator-getters.hpp"
-#include "common/logging.hpp"
-#include "common/random.hpp"
-
-#if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD
-
-namespace ot {
-namespace Utils {
-
-DatasetUpdater::DatasetUpdater(Instance &aInstance)
-    : InstanceLocator(aInstance)
-    , mState(kStateIdle)
-    , mWaitInterval(kWaitInterval)
-    , mCallback(nullptr)
-    , mCallbackContext(nullptr)
-    , mTimer(aInstance, DatasetUpdater::HandleTimer, this)
-    , mDataset(nullptr)
-{
-}
-
-otError DatasetUpdater::RequestUpdate(const MeshCoP::Dataset::Info &aDataset,
-                                      Callback                      aCallback,
-                                      void *                        aContext,
-                                      uint32_t                      aReryWaitInterval)
-{
-    otError  error   = OT_ERROR_NONE;
-    Message *message = nullptr;
-
-    VerifyOrExit(!Get<Mle::Mle>().IsDisabled(), error = OT_ERROR_INVALID_STATE);
-    VerifyOrExit(mState == kStateIdle, error = OT_ERROR_BUSY);
-
-    VerifyOrExit(!aDataset.IsActiveTimestampPresent() && !aDataset.IsPendingTimestampPresent(),
-                 error = OT_ERROR_INVALID_ARGS);
-
-    message = Get<MessagePool>().New(Message::kTypeOther, 0);
-    VerifyOrExit(message != nullptr, error = OT_ERROR_NO_BUFS);
-
-    SuccessOrExit(error = message->Append(aDataset));
-
-    mCallback        = aCallback;
-    mCallbackContext = aContext;
-    mWaitInterval    = aReryWaitInterval;
-    mDataset         = message;
-    mState           = kStateUpdateRequested;
-
-    PreparePendingDataset();
-
-exit:
-    FreeMessageOnError(message, error);
-    return error;
-}
-
-void DatasetUpdater::CancelUpdate(void)
-{
-    if (mState != kStateIdle)
-    {
-        FreeMessage(mDataset);
-        mState = kStateIdle;
-        mTimer.Stop();
-    }
-}
-
-void DatasetUpdater::HandleTimer(Timer &aTimer)
-{
-    aTimer.GetOwner<DatasetUpdater>().HandleTimer();
-}
-
-void DatasetUpdater::HandleTimer(void)
-{
-    switch (mState)
-    {
-    case kStateIdle:
-        break;
-    case kStateUpdateRequested:
-    case kStateSentMgmtPendingDataset:
-        PreparePendingDataset();
-        break;
-    }
-}
-
-void DatasetUpdater::PreparePendingDataset(void)
-{
-    otError                error;
-    MeshCoP::Dataset::Info newDataset;
-    MeshCoP::Dataset::Info curDataset;
-
-    VerifyOrExit(mState != kStateIdle);
-
-    VerifyOrExit(!Get<Mle::Mle>().IsDisabled(), Finish(OT_ERROR_INVALID_STATE));
-
-    error = Get<MeshCoP::ActiveDataset>().Read(curDataset);
-
-    if (error != OT_ERROR_NONE)
-    {
-        // If there is no valid Active Dataset but MLE is not disabled,
-        // set the timer to try again after the retry interval. This
-        // handles the situation where a dataset update request comes
-        // right after the network is formed but before the active
-        // dataset is created.
-
-        mState = kStateUpdateRequested;
-        mTimer.Start(kRetryInterval);
-        ExitNow();
-    }
-
-    IgnoreError(mDataset->Read(0, newDataset));
-
-    if (newDataset.IsSubsetOf(curDataset))
-    {
-        // If new requested Dataset is already contained in the current
-        // Active Dataset, no change is required, and we can report the
-        // update to be successful.
-
-        Finish(OT_ERROR_NONE);
-        ExitNow();
-    }
-
-    if (newDataset.IsActiveTimestampPresent())
-    {
-        // Presence of the active timestamp in the new Dataset
-        // indicates that it is a retry. In this case, we ensure
-        // that the timestamp is ahead of current active dataset.
-        // This covers the case where another device in network
-        // requested a Dataset update after this device.
-
-        VerifyOrExit(newDataset.GetActiveTimestamp() > curDataset.GetActiveTimestamp(), Finish(OT_ERROR_ALREADY));
-    }
-    else
-    {
-        newDataset.SetActiveTimestamp(curDataset.GetActiveTimestamp() +
-                                      Random::NonCrypto::GetUint32InRange(1, kMaxTimestampIncrease));
-    }
-
-    if (!newDataset.IsDelayPresent())
-    {
-        newDataset.SetDelay(kDefaultDelay);
-    }
-
-    if (!newDataset.IsPendingTimestampPresent())
-    {
-        uint32_t timestampIncrease = Random::NonCrypto::GetUint32InRange(1, kMaxTimestampIncrease);
-
-        if (Get<MeshCoP::PendingDataset>().Read(curDataset) == OT_ERROR_NONE)
-        {
-            newDataset.SetPendingTimestamp(curDataset.GetPendingTimestamp() + timestampIncrease);
-        }
-        else
-        {
-            newDataset.SetPendingTimestamp(timestampIncrease);
-        }
-
-        mDataset->Write(0, newDataset);
-    }
-
-    error = Get<MeshCoP::PendingDataset>().SendSetRequest(newDataset, nullptr, 0);
-
-    if (error == OT_ERROR_NONE)
-    {
-        mState = kStateSentMgmtPendingDataset;
-        mTimer.Start(newDataset.GetDelay() + mWaitInterval);
-    }
-    else
-    {
-        mTimer.Start(kRetryInterval);
-    }
-
-exit:
-    return;
-}
-
-void DatasetUpdater::Finish(otError aError)
-{
-    FreeMessage(mDataset);
-    mState = kStateIdle;
-
-    if (mCallback != nullptr)
-    {
-        mCallback(aError, mCallbackContext);
-    }
-}
-
-void DatasetUpdater::HandleNotifierEvents(Events aEvents)
-{
-    VerifyOrExit(mState == kStateSentMgmtPendingDataset);
-
-    if (aEvents.Contains(kEventActiveDatasetChanged))
-    {
-        MeshCoP::Dataset::Info requestedDataset;
-        MeshCoP::Dataset::Info activeDataset;
-
-        SuccessOrExit(Get<MeshCoP::ActiveDataset>().Read(activeDataset));
-        IgnoreError(mDataset->Read(0, requestedDataset));
-
-        if (requestedDataset.IsSubsetOf(activeDataset))
-        {
-            Finish(OT_ERROR_NONE);
-        }
-        else if (requestedDataset.GetActiveTimestamp() <= activeDataset.GetActiveTimestamp())
-        {
-            Finish(OT_ERROR_ALREADY);
-        }
-    }
-
-exit:
-    return;
-}
-
-} // namespace Utils
-} // namespace ot
-
-#endif // #if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD
diff --git a/src/core/utils/flash.cpp b/src/core/utils/flash.cpp
index 8f47176..f7ba540 100644
--- a/src/core/utils/flash.cpp
+++ b/src/core/utils/flash.cpp
@@ -114,9 +114,9 @@
     }
 }
 
-otError Flash::Get(uint16_t aKey, int aIndex, uint8_t *aValue, uint16_t *aValueLength) const
+Error Flash::Get(uint16_t aKey, int aIndex, uint8_t *aValue, uint16_t *aValueLength) const
 {
-    otError      error       = OT_ERROR_NOT_FOUND;
+    Error        error       = kErrorNotFound;
     uint16_t     valueLength = 0;
     int          index       = 0; // This must be initalized to 0. See [Note] in Delete().
     uint32_t     offset;
@@ -151,7 +151,7 @@
             }
 
             valueLength = record.GetLength();
-            error       = OT_ERROR_NONE;
+            error       = kErrorNone;
         }
 
         index++;
@@ -165,22 +165,22 @@
     return error;
 }
 
-otError Flash::Set(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength)
+Error Flash::Set(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength)
 {
     return Add(aKey, true, aValue, aValueLength);
 }
 
-otError Flash::Add(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength)
+Error Flash::Add(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength)
 {
-    bool first = (Get(aKey, 0, nullptr, nullptr) == OT_ERROR_NOT_FOUND);
+    bool first = (Get(aKey, 0, nullptr, nullptr) == kErrorNotFound);
 
     return Add(aKey, first, aValue, aValueLength);
 }
 
-otError Flash::Add(uint16_t aKey, bool aFirst, const uint8_t *aValue, uint16_t aValueLength)
+Error Flash::Add(uint16_t aKey, bool aFirst, const uint8_t *aValue, uint16_t aValueLength)
 {
-    otError error = OT_ERROR_NONE;
-    Record  record;
+    Error  error = kErrorNone;
+    Record record;
 
     record.Init(aKey, aFirst);
     record.SetData(aValue, aValueLength);
@@ -190,7 +190,7 @@
     if ((mSwapSize - record.GetSize()) < mSwapUsed)
     {
         Swap();
-        VerifyOrExit((mSwapSize - record.GetSize()) >= mSwapUsed, error = OT_ERROR_NO_BUFS);
+        VerifyOrExit((mSwapSize - record.GetSize()) >= mSwapUsed, error = kErrorNoBufs);
     }
 
     otPlatFlashWrite(&GetInstance(), mSwapIndex, mSwapUsed, &record, record.GetSize());
@@ -255,9 +255,9 @@
     mSwapUsed  = dstOffset;
 }
 
-otError Flash::Delete(uint16_t aKey, int aIndex)
+Error Flash::Delete(uint16_t aKey, int aIndex)
 {
-    otError      error = OT_ERROR_NOT_FOUND;
+    Error        error = kErrorNotFound;
     int          index = 0; // This must be initalized to 0. See [Note] below.
     RecordHeader record;
 
@@ -279,7 +279,7 @@
         {
             record.SetDeleted();
             otPlatFlashWrite(&GetInstance(), mSwapIndex, offset, &record, sizeof(record));
-            error = OT_ERROR_NONE;
+            error = kErrorNone;
         }
 
         /* [Note] If the operation gets interrupted here and aIndex is 0, the next record (index == 1) will never get
diff --git a/src/core/utils/flash.hpp b/src/core/utils/flash.hpp
index ecac26c..5bb7ed3 100644
--- a/src/core/utils/flash.hpp
+++ b/src/core/utils/flash.hpp
@@ -34,10 +34,10 @@
 #include <stdint.h>
 #include <string.h>
 
-#include <openthread/error.h>
 #include <openthread/platform/toolchain.h>
 
 #include "common/debug.hpp"
+#include "common/error.hpp"
 #include "common/locator.hpp"
 
 namespace ot {
@@ -77,11 +77,11 @@
      *                              At return, the actual length of the setting is written.
      *                              May be nullptr if performing a presence check.
      *
-     * @retval OT_ERROR_NONE        The value was fetched successfully.
-     * @retval OT_ERROR_NOT_FOUND   The key was not found.
+     * @retval kErrorNone       The value was fetched successfully.
+     * @retval kErrorNotFound   The key was not found.
      *
      */
-    otError Get(uint16_t aKey, int aIndex, uint8_t *aValue, uint16_t *aValueLength) const;
+    Error Get(uint16_t aKey, int aIndex, uint8_t *aValue, uint16_t *aValueLength) const;
 
     /**
      * This method sets or replaces the value identified by @p aKey.
@@ -94,11 +94,11 @@
      *                           MUST NOT be nullptr if @p aValueLength is non-zero.
      * @param[in]  aValueLength  The length of the data pointed to by @p aValue. May be zero.
      *
-     * @retval OT_ERROR_NONE     The value was changed.
-     * @retval OT_ERROR_NO_BUFS  Not enough space to store the value.
+     * @retval kErrorNone     The value was changed.
+     * @retval kErrorNoBufs   Not enough space to store the value.
      *
      */
-    otError Set(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength);
+    Error Set(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength);
 
     /**
      * This method adds a value to @p aKey.
@@ -108,11 +108,11 @@
      *                           MUST NOT be nullptr if @p aValueLength is non-zero.
      * @param[in]  aValueLength  The length of the data pointed to by @p aValue. May be zero.
      *
-     * @retval OT_ERROR_NONE     The value was added.
-     * @retval OT_ERROR_NO_BUFS  Not enough space to store the value.
+     * @retval kErrorNone    The value was added.
+     * @retval kErrorNoBufs  Not enough space to store the value.
      *
      */
-    otError Add(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength);
+    Error Add(uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength);
 
     /**
      * This method removes a value from @p aKey.
@@ -122,11 +122,11 @@
      * @param[in] aIndex  The index of the value to be removed.
      *                    If set to -1, all values for @p aKey will be removed.
      *
-     * @retval OT_ERROR_NONE       The given key and index was found and removed successfully.
-     * @retval OT_ERROR_NOT_FOUND  The given key or index was not found.
+     * @retval kErrorNone      The given key and index was found and removed successfully.
+     * @retval kErrorNotFound  The given key or index was not found.
      *
      */
-    otError Delete(uint16_t aKey, int aIndex);
+    Error Delete(uint16_t aKey, int aIndex);
 
     /**
      * This method removes all values.
@@ -220,10 +220,10 @@
         uint8_t mData[kMaxDataSize];
     } OT_TOOL_PACKED_END;
 
-    otError Add(uint16_t aKey, bool aFirst, const uint8_t *aValue, uint16_t aValueLength);
-    bool    DoesValidRecordExist(uint32_t aOffset, uint16_t aKey) const;
-    void    SanitizeFreeSpace(void);
-    void    Swap(void);
+    Error Add(uint16_t aKey, bool aFirst, const uint8_t *aValue, uint16_t aValueLength);
+    bool  DoesValidRecordExist(uint32_t aOffset, uint16_t aKey) const;
+    void  SanitizeFreeSpace(void);
+    void  Swap(void);
 
     uint32_t mSwapSize;
     uint32_t mSwapUsed;
diff --git a/src/core/utils/heap.cpp b/src/core/utils/heap.cpp
index 6ecf45c..0f8432d 100644
--- a/src/core/utils/heap.cpp
+++ b/src/core/utils/heap.cpp
@@ -34,6 +34,8 @@
 
 #include "heap.hpp"
 
+#if !OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
+
 #include <string.h>
 
 #include "common/code_utils.hpp"
@@ -217,3 +219,5 @@
 
 } // namespace Utils
 } // namespace ot
+
+#endif // !OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
diff --git a/src/core/utils/heap.hpp b/src/core/utils/heap.hpp
index 60f2244..e0d1e66 100644
--- a/src/core/utils/heap.hpp
+++ b/src/core/utils/heap.hpp
@@ -37,6 +37,8 @@
 
 #include "openthread-core-config.h"
 
+#if !OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
+
 #include <stddef.h>
 #include <stdint.h>
 
@@ -353,4 +355,6 @@
 } // namespace Utils
 } // namespace ot
 
+#endif // !OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
+
 #endif // OT_HEAP_HPP_
diff --git a/src/core/utils/jam_detector.cpp b/src/core/utils/jam_detector.cpp
index 7a09aee..75b1ab1 100644
--- a/src/core/utils/jam_detector.cpp
+++ b/src/core/utils/jam_detector.cpp
@@ -49,7 +49,7 @@
     : InstanceLocator(aInstance)
     , mHandler(nullptr)
     , mContext(nullptr)
-    , mTimer(aInstance, JamDetector::HandleTimer, this)
+    , mTimer(aInstance, JamDetector::HandleTimer)
     , mHistoryBitmap(0)
     , mCurSecondStartTime(0)
     , mSampleInterval(0)
@@ -62,12 +62,12 @@
 {
 }
 
-otError JamDetector::Start(Handler aHandler, void *aContext)
+Error JamDetector::Start(Handler aHandler, void *aContext)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(!mEnabled, error = OT_ERROR_ALREADY);
-    VerifyOrExit(aHandler != nullptr, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(!mEnabled, error = kErrorAlready);
+    VerifyOrExit(aHandler != nullptr, error = kErrorInvalidArgs);
 
     mHandler = aHandler;
     mContext = aContext;
@@ -81,11 +81,11 @@
     return error;
 }
 
-otError JamDetector::Stop(void)
+Error JamDetector::Stop(void)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(mEnabled, error = OT_ERROR_ALREADY);
+    VerifyOrExit(mEnabled, error = kErrorAlready);
 
     mEnabled  = false;
     mJamState = false;
@@ -131,12 +131,12 @@
     otLogInfoUtil("JamDetector - RSSI threshold set to %d", mRssiThreshold);
 }
 
-otError JamDetector::SetWindow(uint8_t aWindow)
+Error JamDetector::SetWindow(uint8_t aWindow)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(aWindow != 0, error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit(aWindow <= kMaxWindow, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aWindow != 0, error = kErrorInvalidArgs);
+    VerifyOrExit(aWindow <= kMaxWindow, error = kErrorInvalidArgs);
 
     mWindow = aWindow;
     otLogInfoUtil("JamDetector - window set to %d", mWindow);
@@ -145,12 +145,12 @@
     return error;
 }
 
-otError JamDetector::SetBusyPeriod(uint8_t aBusyPeriod)
+Error JamDetector::SetBusyPeriod(uint8_t aBusyPeriod)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(aBusyPeriod != 0, error = OT_ERROR_INVALID_ARGS);
-    VerifyOrExit(aBusyPeriod <= mWindow, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(aBusyPeriod != 0, error = kErrorInvalidArgs);
+    VerifyOrExit(aBusyPeriod <= mWindow, error = kErrorInvalidArgs);
 
     mBusyPeriod = aBusyPeriod;
     otLogInfoUtil("JamDetector - busy period set to %d", mBusyPeriod);
@@ -161,7 +161,7 @@
 
 void JamDetector::HandleTimer(Timer &aTimer)
 {
-    aTimer.GetOwner<JamDetector>().HandleTimer();
+    aTimer.Get<JamDetector>().HandleTimer();
 }
 
 void JamDetector::HandleTimer(void)
diff --git a/src/core/utils/jam_detector.hpp b/src/core/utils/jam_detector.hpp
index 787b945..e0ab57d 100644
--- a/src/core/utils/jam_detector.hpp
+++ b/src/core/utils/jam_detector.hpp
@@ -77,20 +77,20 @@
      * @param[in]  aHandler             A pointer to a function called when jamming is detected.
      * @param[in]  aContext             A pointer to application-specific context.
      *
-     * @retval OT_ERROR_NONE            Successfully started the jamming detection.
-     * @retval OT_ERROR_ALREADY         Jam detection has been started before.
+     * @retval kErrorNone            Successfully started the jamming detection.
+     * @retval kErrorAlready         Jam detection has been started before.
      *
      */
-    otError Start(Handler aHandler, void *aContext);
+    Error Start(Handler aHandler, void *aContext);
 
     /**
      * Stop the jamming detection.
      *
-     * @retval OT_ERROR_NONE            Successfully stopped the jamming detection.
-     * @retval OT_ERROR_ALREADY         Jam detection is already stopped.
+     * @retval kErrorNone            Successfully stopped the jamming detection.
+     * @retval kErrorAlready         Jam detection is already stopped.
      *
      */
-    otError Stop(void);
+    Error Stop(void);
 
     /**
      * Get the Jam Detection Status
@@ -126,11 +126,11 @@
      *
      * @param[in]  aWindow            The Jam Detection window (valid range is 1 to 63)
      *
-     * @retval OT_ERROR_NONE          Successfully set the window.
-     * @retval OT_ERROR_INVALID_ARGS  The given input parameter not within valid range (1-63)
+     * @retval kErrorNone          Successfully set the window.
+     * @retval kErrorInvalidArgs   The given input parameter not within valid range (1-63)
      *
      */
-    otError SetWindow(uint8_t aWindow);
+    Error SetWindow(uint8_t aWindow);
 
     /**
      * Get the Jam Detection Detection Window (in seconds).
@@ -148,11 +148,11 @@
      * @param[in]  aBusyPeriod          The Jam Detection busy period (should be non-zero and
                                         less than or equal to Jam Detection Window)
      *
-     * @retval OT_ERROR_NONE            Successfully set the window.
-     * @retval OT_ERROR_INVALID_ARGS    The given input is not within the valid range.
+     * @retval kErrorNone           Successfully set the window.
+     * @retval kErrorInvalidArgs    The given input is not within the valid range.
      *
      */
-    otError SetBusyPeriod(uint8_t aBusyPeriod);
+    Error SetBusyPeriod(uint8_t aBusyPeriod);
 
     /**
      * Get the Jam Detection Busy Period (in seconds)
diff --git a/src/core/utils/lookup_table.hpp b/src/core/utils/lookup_table.hpp
index 92e4e39..6779588 100644
--- a/src/core/utils/lookup_table.hpp
+++ b/src/core/utils/lookup_table.hpp
@@ -35,7 +35,8 @@
 #define LOOKUP_TABLE_HPP_
 
 #include <stdint.h>
-#include <openthread/error.h>
+
+#include "common/error.hpp"
 
 namespace ot {
 namespace Utils {
diff --git a/src/core/utils/otns.cpp b/src/core/utils/otns.cpp
index f75b342..65d44a6 100644
--- a/src/core/utils/otns.cpp
+++ b/src/core/utils/otns.cpp
@@ -38,6 +38,7 @@
 
 #include "common/debug.hpp"
 #include "common/locator-getters.hpp"
+#include "common/logging.hpp"
 
 namespace ot {
 namespace Utils {
@@ -157,50 +158,50 @@
 
 void Otns::EmitCoapSend(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    char    uriPath[Coap::Message::kMaxReceivedUriPath + 1];
-    otError error;
+    char  uriPath[Coap::Message::kMaxReceivedUriPath + 1];
+    Error error;
 
     SuccessOrExit(error = aMessage.ReadUriPathOptions(uriPath));
 
     EmitStatus("coap=send,%d,%d,%d,%s,%s,%d", aMessage.GetMessageId(), aMessage.GetType(), aMessage.GetCode(), uriPath,
                aMessageInfo.GetPeerAddr().ToString().AsCString(), aMessageInfo.GetPeerPort());
 exit:
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogWarnCore("Otns::EmitCoapSend failed: %s", otThreadErrorToString(error));
+        otLogWarnCore("Otns::EmitCoapSend failed: %s", ErrorToString(error));
     }
 }
 
 void Otns::EmitCoapReceive(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    char    uriPath[Coap::Message::kMaxReceivedUriPath + 1];
-    otError error = OT_ERROR_NONE;
+    char  uriPath[Coap::Message::kMaxReceivedUriPath + 1];
+    Error error = kErrorNone;
 
     SuccessOrExit(error = aMessage.ReadUriPathOptions(uriPath));
 
     EmitStatus("coap=recv,%d,%d,%d,%s,%s,%d", aMessage.GetMessageId(), aMessage.GetType(), aMessage.GetCode(), uriPath,
                aMessageInfo.GetPeerAddr().ToString().AsCString(), aMessageInfo.GetPeerPort());
 exit:
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogWarnCore("Otns::EmitCoapReceive failed: %s", otThreadErrorToString(error));
+        otLogWarnCore("Otns::EmitCoapReceive failed: %s", ErrorToString(error));
     }
 }
 
-void Otns::EmitCoapSendFailure(otError aError, Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
+void Otns::EmitCoapSendFailure(Error aError, Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
 {
-    char    uriPath[Coap::Message::kMaxReceivedUriPath + 1];
-    otError error = OT_ERROR_NONE;
+    char  uriPath[Coap::Message::kMaxReceivedUriPath + 1];
+    Error error = kErrorNone;
 
     SuccessOrExit(error = aMessage.ReadUriPathOptions(uriPath));
 
     EmitStatus("coap=send_error,%d,%d,%d,%s,%s,%d,%s", aMessage.GetMessageId(), aMessage.GetType(), aMessage.GetCode(),
                uriPath, aMessageInfo.GetPeerAddr().ToString().AsCString(), aMessageInfo.GetPeerPort(),
-               otThreadErrorToString(aError));
+               ErrorToString(aError));
 exit:
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
-        otLogWarnCore("Otns::EmitCoapSendFailure failed: %s", otThreadErrorToString(error));
+        otLogWarnCore("Otns::EmitCoapSendFailure failed: %s", ErrorToString(error));
     }
 }
 
diff --git a/src/core/utils/otns.hpp b/src/core/utils/otns.hpp
index 2d670d9..d877b36 100644
--- a/src/core/utils/otns.hpp
+++ b/src/core/utils/otns.hpp
@@ -160,7 +160,7 @@
      * @param[in] aMessageInfo  The message info.
      *
      */
-    static void EmitCoapSendFailure(otError aError, Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
+    static void EmitCoapSendFailure(Error aError, Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
 
     /**
      * This function emits the received COAP message info to OTNS.
diff --git a/src/core/utils/parse_cmdline.cpp b/src/core/utils/parse_cmdline.cpp
index c9008e3..993012d 100644
--- a/src/core/utils/parse_cmdline.cpp
+++ b/src/core/utils/parse_cmdline.cpp
@@ -53,20 +53,20 @@
     return IsSeparator(aChar) || (aChar == '\\');
 }
 
-static otError ParseDigit(char aDigitChar, uint8_t &aValue)
+static Error ParseDigit(char aDigitChar, uint8_t &aValue)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
-    VerifyOrExit(('0' <= aDigitChar) && (aDigitChar <= '9'), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(('0' <= aDigitChar) && (aDigitChar <= '9'), error = kErrorInvalidArgs);
     aValue = static_cast<uint8_t>(aDigitChar - '0');
 
 exit:
     return error;
 }
 
-static otError ParseHexDigit(char aHexChar, uint8_t &aValue)
+static Error ParseHexDigit(char aHexChar, uint8_t &aValue)
 {
-    otError error = OT_ERROR_NONE;
+    Error error = kErrorNone;
 
     if (('A' <= aHexChar) && (aHexChar <= 'F'))
     {
@@ -84,10 +84,10 @@
     return error;
 }
 
-otError ParseCmd(char *aCommandString, uint8_t &aArgsLength, char *aArgs[], uint8_t aArgsLengthMax)
+Error ParseCmd(char *aCommandString, uint8_t &aArgsLength, char *aArgs[], uint8_t aArgsLengthMax)
 {
-    otError error = OT_ERROR_NONE;
-    char *  cmd;
+    Error error = kErrorNone;
+    char *cmd;
 
     aArgsLength = 0;
 
@@ -105,7 +105,7 @@
 
         if ((*cmd != '\0') && ((aArgsLength == 0) || (*(cmd - 1) == '\0')))
         {
-            VerifyOrExit(aArgsLength < aArgsLengthMax, error = OT_ERROR_INVALID_ARGS);
+            VerifyOrExit(aArgsLength < aArgsLengthMax, error = kErrorInvalidArgs);
             aArgs[aArgsLength++] = cmd;
         }
     }
@@ -114,38 +114,38 @@
     return error;
 }
 
-template <typename UintType> otError ParseUint(const char *aString, UintType &aUint)
+template <typename UintType> Error ParseUint(const char *aString, UintType &aUint)
 {
-    otError  error;
+    Error    error;
     uint64_t value;
 
     SuccessOrExit(error = ParseAsUint64(aString, value));
 
-    VerifyOrExit(value <= NumericLimits<UintType>::Max(), error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(value <= NumericLimits<UintType>::Max(), error = kErrorInvalidArgs);
     aUint = static_cast<UintType>(value);
 
 exit:
     return error;
 }
 
-otError ParseAsUint8(const char *aString, uint8_t &aUint8)
+Error ParseAsUint8(const char *aString, uint8_t &aUint8)
 {
     return ParseUint<uint8_t>(aString, aUint8);
 }
 
-otError ParseAsUint16(const char *aString, uint16_t &aUint16)
+Error ParseAsUint16(const char *aString, uint16_t &aUint16)
 {
     return ParseUint<uint16_t>(aString, aUint16);
 }
 
-otError ParseAsUint32(const char *aString, uint32_t &aUint32)
+Error ParseAsUint32(const char *aString, uint32_t &aUint32)
 {
     return ParseUint<uint32_t>(aString, aUint32);
 }
 
-otError ParseAsUint64(const char *aString, uint64_t &aUint64)
+Error ParseAsUint64(const char *aString, uint64_t &aUint64)
 {
-    otError     error = OT_ERROR_NONE;
+    Error       error = kErrorNone;
     uint64_t    value = 0;
     const char *cur   = aString;
     bool        isHex = false;
@@ -168,10 +168,10 @@
         uint64_t newValue;
 
         SuccessOrExit(error = isHex ? ParseHexDigit(*cur, digit) : ParseDigit(*cur, digit));
-        VerifyOrExit(value <= (isHex ? kMaxHexBeforeOveflow : kMaxDecBeforeOverlow), error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit(value <= (isHex ? kMaxHexBeforeOveflow : kMaxDecBeforeOverlow), error = kErrorInvalidArgs);
         value    = isHex ? (value << 4) : (value * 10);
         newValue = value + digit;
-        VerifyOrExit(newValue >= value, error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit(newValue >= value, error = kErrorInvalidArgs);
         value = newValue;
         cur++;
     } while (*cur != '\0');
@@ -182,34 +182,34 @@
     return error;
 }
 
-template <typename IntType> otError ParseInt(const char *aString, IntType &aInt)
+template <typename IntType> Error ParseInt(const char *aString, IntType &aInt)
 {
-    otError error;
+    Error   error;
     int32_t value;
 
     SuccessOrExit(error = ParseAsInt32(aString, value));
 
     VerifyOrExit((NumericLimits<IntType>::Min() <= value) && (value <= NumericLimits<IntType>::Max()),
-                 error = OT_ERROR_INVALID_ARGS);
+                 error = kErrorInvalidArgs);
     aInt = static_cast<IntType>(value);
 
 exit:
     return error;
 }
 
-otError ParseAsInt8(const char *aString, int8_t &aInt8)
+Error ParseAsInt8(const char *aString, int8_t &aInt8)
 {
     return ParseInt<int8_t>(aString, aInt8);
 }
 
-otError ParseAsInt16(const char *aString, int16_t &aInt16)
+Error ParseAsInt16(const char *aString, int16_t &aInt16)
 {
     return ParseInt<int16_t>(aString, aInt16);
 }
 
-otError ParseAsInt32(const char *aString, int32_t &aInt32)
+Error ParseAsInt32(const char *aString, int32_t &aInt32)
 {
-    otError  error;
+    Error    error;
     uint64_t value;
     bool     isNegavtive = false;
 
@@ -226,16 +226,16 @@
     SuccessOrExit(error = ParseAsUint64(aString, value));
     VerifyOrExit(value <= (isNegavtive ? static_cast<uint64_t>(-static_cast<int64_t>(NumericLimits<int32_t>::Min()))
                                        : static_cast<uint64_t>(NumericLimits<int32_t>::Max())),
-                 error = OT_ERROR_INVALID_ARGS);
+                 error = kErrorInvalidArgs);
     aInt32 = static_cast<int32_t>(isNegavtive ? -static_cast<int64_t>(value) : static_cast<int64_t>(value));
 
 exit:
     return error;
 }
 
-otError ParseAsBool(const char *aString, bool &aBool)
+Error ParseAsBool(const char *aString, bool &aBool)
 {
-    otError  error;
+    Error    error;
     uint32_t value;
 
     SuccessOrExit(error = ParseAsUint32(aString, value));
@@ -246,14 +246,14 @@
 }
 #if OPENTHREAD_FTD || OPENTHREAD_MTD
 
-otError ParseAsIp6Prefix(const char *aString, otIp6Prefix &aPrefix)
+Error ParseAsIp6Prefix(const char *aString, otIp6Prefix &aPrefix)
 {
     enum : uint8_t
     {
         kMaxIp6AddressStringSize = 45,
     };
 
-    otError     error = OT_ERROR_INVALID_ARGS;
+    Error       error = kErrorInvalidArgs;
     char        string[kMaxIp6AddressStringSize];
     const char *prefixLengthStr;
 
@@ -273,21 +273,21 @@
 }
 #endif // #if OPENTHREAD_FTD || OPENTHREAD_MTD
 
-otError ParseAsHexString(const char *aString, uint8_t *aBuffer, uint16_t aSize)
+Error ParseAsHexString(const char *aString, uint8_t *aBuffer, uint16_t aSize)
 {
-    otError  error;
+    Error    error;
     uint16_t readSize = aSize;
 
     SuccessOrExit(error = ParseAsHexString(aString, readSize, aBuffer, kDisallowTruncate));
-    VerifyOrExit(readSize == aSize, error = OT_ERROR_INVALID_ARGS);
+    VerifyOrExit(readSize == aSize, error = kErrorInvalidArgs);
 
 exit:
     return error;
 }
 
-otError ParseAsHexString(const char *aString, uint16_t &aSize, uint8_t *aBuffer, HexStringParseMode aMode)
+Error ParseAsHexString(const char *aString, uint16_t &aSize, uint8_t *aBuffer, HexStringParseMode aMode)
 {
-    otError     error     = OT_ERROR_NONE;
+    Error       error     = kErrorNone;
     uint8_t     byte      = 0;
     uint16_t    readBytes = 0;
     const char *hex       = aString;
@@ -296,7 +296,7 @@
 
     if (aMode == kDisallowTruncate)
     {
-        VerifyOrExit((hexLength + 1) / 2 <= aSize, error = OT_ERROR_INVALID_ARGS);
+        VerifyOrExit((hexLength + 1) / 2 <= aSize, error = kErrorInvalidArgs);
     }
 
     // Handle the case where number of chars in hex string is odd.
diff --git a/src/core/utils/parse_cmdline.hpp b/src/core/utils/parse_cmdline.hpp
index 6c3facd..f680fff 100644
--- a/src/core/utils/parse_cmdline.hpp
+++ b/src/core/utils/parse_cmdline.hpp
@@ -85,8 +85,8 @@
  * @param[in]  aString   The string to parse.
  * @param[out] aUint8    A reference to an `uint8_t` variable to output the parsed value.
  *
- * @retval OT_ERROR_NONE          The string was parsed successfully.
- * @retval OT_ERROR_INVALID_ARGS  The string does not contain valid number (e.g., value out of range).
+ * @retval kErrorNone         The string was parsed successfully.
+ * @retval kErrorInvalidArgs  The string does not contain valid number (e.g., value out of range).
  *
  */
 otError ParseAsUint8(const char *aString, uint8_t &aUint8);
@@ -99,8 +99,8 @@
  * @param[in]  aString   The string to parse.
  * @param[out] aUint16   A reference to an `uint16_t` variable to output the parsed value.
  *
- * @retval OT_ERROR_NONE          The string was parsed successfully.
- * @retval OT_ERROR_INVALID_ARGS  The string does not contain valid number (e.g., value out of range).
+ * @retval kErrorNone         The string was parsed successfully.
+ * @retval kErrorInvalidArgs  The string does not contain valid number (e.g., value out of range).
  *
  */
 otError ParseAsUint16(const char *aString, uint16_t &aUint16);
@@ -113,8 +113,8 @@
  * @param[in]  aString   The string to parse.
  * @param[out] aUint32   A reference to an `uint32_t` variable to output the parsed value.
  *
- * @retval OT_ERROR_NONE          The string was parsed successfully.
- * @retval OT_ERROR_INVALID_ARGS  The string does not contain valid number (e.g., value out of range).
+ * @retval kErrorNone         The string was parsed successfully.
+ * @retval kErrorInvalidArgs  The string does not contain valid number (e.g., value out of range).
  *
  */
 otError ParseAsUint32(const char *aString, uint32_t &aUint32);
@@ -127,8 +127,8 @@
  * @param[in]  aString   The string to parse.
  * @param[out] aUint64   A reference to an `uint64_t` variable to output the parsed value.
  *
- * @retval OT_ERROR_NONE          The string was parsed successfully.
- * @retval OT_ERROR_INVALID_ARGS  The string does not contain valid number (e.g., value out of range).
+ * @retval kErrorNone         The string was parsed successfully.
+ * @retval kErrorInvalidArgs  The string does not contain valid number (e.g., value out of range).
  *
  */
 otError ParseAsUint64(const char *aString, uint64_t &aUint64);
@@ -142,8 +142,8 @@
  * @param[in]  aString   The string to parse.
  * @param[out] aInt8     A reference to an `int8_t` variable to output the parsed value.
  *
- * @retval OT_ERROR_NONE          The string was parsed successfully.
- * @retval OT_ERROR_INVALID_ARGS  The string does not contain valid number (e.g., value out of range).
+ * @retval kErrorNone         The string was parsed successfully.
+ * @retval kErrorInvalidArgs  The string does not contain valid number (e.g., value out of range).
  *
  */
 otError ParseAsInt8(const char *aString, int8_t &aInt8);
@@ -157,8 +157,8 @@
  * @param[in]  aString   The string to parse.
  * @param[out] aInt16    A reference to an `int16_t` variable to output the parsed value.
  *
- * @retval OT_ERROR_NONE          The string was parsed successfully.
- * @retval OT_ERROR_INVALID_ARGS  The string does not contain valid number (e.g., value out of range).
+ * @retval kErrorNone         The string was parsed successfully.
+ * @retval kErrorInvalidArgs  The string does not contain valid number (e.g., value out of range).
  *
  */
 otError ParseAsInt16(const char *aString, int16_t &aInt16);
@@ -172,8 +172,8 @@
  * @param[in]  aString   The string to parse.
  * @param[out] aInt32    A reference to an `int32_t` variable to output the parsed value.
  *
- * @retval OT_ERROR_NONE          The string was parsed successfully.
- * @retval OT_ERROR_INVALID_ARGS  The string does not contain valid number (e.g., value out of range).
+ * @retval kErrorNone          The string was parsed successfully.
+ * @retval kErrorInvalidArgs  The string does not contain valid number (e.g., value out of range).
  *
  */
 otError ParseAsInt32(const char *aString, int32_t &aInt32);
@@ -186,8 +186,8 @@
  * @param[in]  aString   The string to parse.
  * @param[out] aBool     A reference to a `bool` variable to output the parsed value.
  *
- * @retval OT_ERROR_NONE          The string was parsed successfully.
- * @retval OT_ERROR_INVALID_ARGS  The string does not contain valid number.
+ * @retval kErrorNone         The string was parsed successfully.
+ * @retval kErrorInvalidArgs  The string does not contain valid number.
  *
  */
 otError ParseAsBool(const char *aString, bool &aBool);
@@ -200,8 +200,8 @@
  * @param[in]  aString   The string to parse.
  * @param[out] aAddress  A reference to an `otIp6Address` to output the parsed IPv6 address.
  *
- * @retval OT_ERROR_NONE          The string was parsed successfully.
- * @retval OT_ERROR_INVALID_ARGS  The string does not contain valid IPv6 address.
+ * @retval kErrorNone          The string was parsed successfully.
+ * @retval kErrorInvalidArgs  The string does not contain valid IPv6 address.
  *
  */
 inline otError ParseAsIp6Address(const char *aString, otIp6Address &aAddress)
@@ -217,8 +217,8 @@
  * @param[in]  aString   The string to parse.
  * @param[out] aPrefix   A reference to an `otIp6Prefix` to output the parsed IPv6 prefix.
  *
- * @retval OT_ERROR_NONE          The string was parsed successfully.
- * @retval OT_ERROR_INVALID_ARGS  The string does not contain valid IPv6 prefix
+ * @retval kErrorNone         The string was parsed successfully.
+ * @retval kErrorInvalidArgs  The string does not contain valid IPv6 prefix
  *
  */
 otError ParseAsIp6Prefix(const char *aString, otIp6Prefix &aPrefix);
@@ -227,16 +227,16 @@
 /**
  * This function parses a hex string into a byte array of fixed expected size.
  *
- * This function returns `OT_ERROR_NONE` only when the hex string contains exactly @p aSize bytes (after parsing). If
+ * This function returns `kErrorNone` only when the hex string contains exactly @p aSize bytes (after parsing). If
  * there are fewer or more bytes in hex string that @p aSize, the parsed bytes (up to @p aSize) are copied into the
- * `aBuffer` and `OT_ERROR_INVALID_ARGS` is returned.
+ * `aBuffer` and `kErrorInvalidArgs` is returned.
  *
  * @param[in]  aString   The string to parse.
  * @param[out] aBuffer   A pointer to a buffer to output the parsed byte sequence.
  * @param[in]  aSize     The expected size of byte sequence (number of bytes after parsing).
  *
- * @retval OT_ERROR_NONE          The string was parsed successfully.
- * @retval OT_ERROR_INVALID_ARGS  The string does not contain valid hex bytes and/or not @p aSize bytes.
+ * @retval kErrorNone         The string was parsed successfully.
+ * @retval kErrorInvalidArgs  The string does not contain valid hex bytes and/or not @p aSize bytes.
  *
  */
 otError ParseAsHexString(const char *aString, uint8_t *aBuffer, uint16_t aSize);
@@ -244,17 +244,17 @@
 /**
  * This template function parses a hex string into a a given fixed size array.
  *
- * This function returns `OT_ERROR_NONE` only when the hex string contains exactly @p kBufferSize bytes (after parsing).
+ * This function returns `kErrorNone` only when the hex string contains exactly @p kBufferSize bytes (after parsing).
  * If there are fewer or more bytes in hex string that @p kBufferSize, the parsed bytes (up to @p kBufferSize) are
- * copied into the `aBuffer` and `OT_ERROR_INVALID_ARGS` is returned.
+ * copied into the `aBuffer` and `kErrorInvalidArgs` is returned.
  *
  * @tparam kBufferSize   The byte array size (number of bytes).
  *
  * @param[in]  aString   The string to parse.
  * @param[out] aBuffer   A reference to a byte array to output the parsed byte sequence.
  *
- * @retval OT_ERROR_NONE          The string was parsed successfully.
- * @retval OT_ERROR_INVALID_ARGS  The string does not contain valid hex bytes and/or not @p aSize bytes.
+ * @retval kErrorNone         The string was parsed successfully.
+ * @retval kErrorInvalidArgs  The string does not contain valid hex bytes and/or not @p aSize bytes.
  *
  */
 template <uint16_t kBufferSize> static otError ParseAsHexString(const char *aString, uint8_t (&aBuffer)[kBufferSize])
@@ -275,8 +275,8 @@
  * @param[out]    aBuffer   A pointer to a buffer to output the parsed byte sequence.
  * @param[in]     aMode     Indicates parsing mode whether to allow truncation or not.
  *
- * @retval OT_ERROR_NONE         The string was parsed successfully.
- * @retval OT_ERROR_INVALID_ARGS The string does not contain valid format or too many bytes (if truncation not allowed)
+ * @retval kErrorNone        The string was parsed successfully.
+ * @retval kErrorInvalidArgs The string does not contain valid format or too many bytes (if truncation not allowed)
  *
  */
 otError ParseAsHexString(const char *       aString,
diff --git a/src/core/utils/ping_sender.cpp b/src/core/utils/ping_sender.cpp
new file mode 100644
index 0000000..22d4045
--- /dev/null
+++ b/src/core/utils/ping_sender.cpp
@@ -0,0 +1,196 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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
+ *   This file implements the ping sender module.
+ */
+
+#include "ping_sender.hpp"
+
+#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
+
+#include "common/encoding.hpp"
+#include "common/locator-getters.hpp"
+#include "common/random.hpp"
+
+namespace ot {
+namespace Utils {
+
+using Encoding::BigEndian::HostSwap32;
+
+void PingSender::Config::SetUnspecifiedToDefault(void)
+{
+    if (mSize == 0)
+    {
+        mSize = kDefaultSize;
+    }
+
+    if (mCount == 0)
+    {
+        mCount = kDefaultCount;
+    }
+
+    if (mInterval == 0)
+    {
+        mInterval = kDefaultInterval;
+    }
+}
+
+void PingSender::Config::InvokeCallback(const Reply &aReply) const
+{
+    VerifyOrExit(mCallback != nullptr);
+    mCallback(&aReply, mCallbackContext);
+
+exit:
+    return;
+}
+
+PingSender::PingSender(Instance &aInstance)
+    : InstanceLocator(aInstance)
+    , mIdentifier(0)
+    , mTimer(aInstance, PingSender::HandleTimer)
+    , mIcmpHandler(PingSender::HandleIcmpReceive, this)
+{
+    IgnoreError(Get<Ip6::Icmp>().RegisterHandler(mIcmpHandler));
+}
+
+Error PingSender::Ping(const Config &aConfig)
+{
+    Error error = kErrorNone;
+
+    VerifyOrExit(!mTimer.IsRunning(), error = kErrorBusy);
+
+    mConfig = aConfig;
+    mConfig.SetUnspecifiedToDefault();
+    VerifyOrExit(mConfig.mInterval <= Timer::kMaxDelay, error = kErrorInvalidArgs);
+
+    mIdentifier++;
+    SendPing();
+
+exit:
+    return error;
+}
+
+void PingSender::Stop(void)
+{
+    mTimer.Stop();
+    mIdentifier++;
+}
+
+void PingSender::SendPing(void)
+{
+    TimeMilli        now     = TimerMilli::GetNow();
+    Message *        message = nullptr;
+    Ip6::MessageInfo messageInfo;
+
+    messageInfo.SetPeerAddr(mConfig.GetDestination());
+    messageInfo.mHopLimit          = mConfig.mHopLimit;
+    messageInfo.mAllowZeroHopLimit = mConfig.mAllowZeroHopLimit;
+
+    message = Get<Ip6::Icmp>().NewMessage(0);
+    VerifyOrExit(message != nullptr);
+
+    SuccessOrExit(message->Append(HostSwap32(now.GetValue())));
+
+    if (mConfig.mSize > message->GetLength())
+    {
+        SuccessOrExit(message->SetLength(mConfig.mSize));
+    }
+
+    SuccessOrExit(Get<Ip6::Icmp>().SendEchoRequest(*message, messageInfo, mIdentifier));
+
+#if OPENTHREAD_CONFIG_OTNS_ENABLE
+    Get<Utils::Otns>().EmitPingRequest(mConfig.GetDestination(), mConfig.mSize, now.GetValue(), mConfig.mHopLimit);
+#endif
+
+    message = nullptr;
+
+exit:
+    FreeMessage(message);
+    mConfig.mCount--;
+
+    if (mConfig.mCount != 0)
+    {
+        mTimer.Start(mConfig.mInterval);
+    }
+}
+
+void PingSender::HandleTimer(Timer &aTimer)
+{
+    aTimer.Get<PingSender>().HandleTimer();
+}
+
+void PingSender::HandleTimer(void)
+{
+    SendPing();
+}
+
+void PingSender::HandleIcmpReceive(void *               aContext,
+                                   otMessage *          aMessage,
+                                   const otMessageInfo *aMessageInfo,
+                                   const otIcmp6Header *aIcmpHeader)
+{
+    reinterpret_cast<PingSender *>(aContext)->HandleIcmpReceive(*static_cast<Message *>(aMessage),
+                                                                *static_cast<const Ip6::MessageInfo *>(aMessageInfo),
+                                                                *static_cast<const Ip6::Icmp::Header *>(aIcmpHeader));
+}
+
+void PingSender::HandleIcmpReceive(const Message &          aMessage,
+                                   const Ip6::MessageInfo & aMessageInfo,
+                                   const Ip6::Icmp::Header &aIcmpHeader)
+{
+    Reply    reply;
+    uint32_t timestamp;
+
+    VerifyOrExit(aIcmpHeader.GetType() == Ip6::Icmp::Header::kTypeEchoReply);
+    VerifyOrExit(aIcmpHeader.GetId() == mIdentifier);
+
+    SuccessOrExit(aMessage.Read(aMessage.GetOffset(), timestamp));
+    timestamp = HostSwap32(timestamp);
+
+    reply.mSenderAddress  = aMessageInfo.GetPeerAddr();
+    reply.mRoundTripTime  = TimerMilli::GetNow() - TimeMilli(timestamp);
+    reply.mSize           = aMessage.GetLength() - aMessage.GetOffset();
+    reply.mSequenceNumber = aIcmpHeader.GetSequence();
+    reply.mHopLimit       = aMessageInfo.GetHopLimit();
+
+#if OPENTHREAD_CONFIG_OTNS_ENABLE
+    Get<Utils::Otns>().EmitPingReply(aMessageInfo.GetPeerAddr(), reply.mSize, timestamp, reply.mHopLimit);
+#endif
+
+    mConfig.InvokeCallback(reply);
+
+exit:
+    return;
+}
+
+} // namespace Utils
+} // namespace ot
+
+#endif // #if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
diff --git a/src/core/utils/ping_sender.hpp b/src/core/utils/ping_sender.hpp
new file mode 100644
index 0000000..9e283ed
--- /dev/null
+++ b/src/core/utils/ping_sender.hpp
@@ -0,0 +1,158 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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
+ *   This file includes definitions to support ping functionality.
+ */
+
+#ifndef PING_SENDER_HPP_
+#define PING_SENDER_HPP_
+
+#include "openthread-core-config.h"
+
+#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
+
+#include <openthread/ping_sender.h>
+
+#include "common/code_utils.hpp"
+#include "common/locator.hpp"
+#include "common/message.hpp"
+#include "common/non_copyable.hpp"
+#include "common/time.hpp"
+#include "common/timer.hpp"
+#include "net/icmp6.hpp"
+#include "net/ip6_address.hpp"
+
+namespace ot {
+namespace Utils {
+
+/**
+ * This class implements sending ICMPv6 Echo Request messages and processing ICMPv6 Echo Reply messages.
+ *
+ */
+class PingSender : public InstanceLocator, private NonCopyable
+{
+public:
+    /**
+     * This class represents a ping reply.
+     *
+     */
+    typedef otPingSenderReply Reply;
+
+    /**
+     * This class represents a ping request configuration.
+     *
+     */
+    class Config : public otPingSenderConfig
+    {
+        friend class PingSender;
+
+    public:
+        /**
+         * This method gets the destination IPv6 address to ping.
+         *
+         * @returns The ping destination IPv6 address.
+         *
+         */
+        Ip6::Address &GetDestination(void) { return static_cast<Ip6::Address &>(mDestination); }
+
+        /**
+         * This method gets the destination IPv6 address to ping.
+         *
+         * @returns The ping destination IPv6 address.
+         *
+         */
+        const Ip6::Address &GetDestination(void) const { return static_cast<const Ip6::Address &>(mDestination); }
+
+    private:
+        enum : uint16_t
+        {
+            kDefaultSize  = OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_SIZE,
+            kDefaultCount = OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_COUNT,
+        };
+
+        enum : uint32_t
+        {
+            kDefaultInterval = OPENTHREAD_CONFIG_PING_SENDER_DEFAULT_INTEVRAL,
+        };
+
+        void SetUnspecifiedToDefault(void);
+        void InvokeCallback(const Reply &aReply) const;
+    };
+
+    /**
+     * This constructor initializes the `PingSender` object.
+     *
+     * @param[in]  aInstance     A reference to the OpenThread instance.
+     *
+     */
+    explicit PingSender(Instance &aInstance);
+
+    /**
+     * This method starts a ping.
+     *
+     * @param[in] aConfig          The ping config to use.
+     *
+     * @retval kErrorNone          The ping started successfully.
+     * @retval kErrorBusy          Could not start since busy with a previous ongoing ping request.
+     * @retval kErrorInvalidArgs   The @p aConfig contains invalid parameters (e.g., ping interval is too long).
+     *
+     */
+    Error Ping(const Config &aConfig);
+
+    /**
+     * This method stops an ongoing ping.
+     *
+     */
+    void Stop(void);
+
+private:
+    void        SendPing(void);
+    static void HandleTimer(Timer &aTimer);
+    void        HandleTimer(void);
+    static void HandleIcmpReceive(void *               aContext,
+                                  otMessage *          aMessage,
+                                  const otMessageInfo *aMessageInfo,
+                                  const otIcmp6Header *aIcmpHeader);
+    void        HandleIcmpReceive(const Message &          aMessage,
+                                  const Ip6::MessageInfo & aMessageInfo,
+                                  const Ip6::Icmp::Header &aIcmpHeader);
+
+    Config             mConfig;
+    uint16_t           mIdentifier;
+    TimerMilli         mTimer;
+    Ip6::Icmp::Handler mIcmpHandler;
+};
+
+} // namespace Utils
+} // namespace ot
+
+#endif // OPENTHREAD_CONFIG_PING_SENDER_ENABLE
+
+#endif // PING_SENDER_HPP_
diff --git a/src/core/utils/slaac_address.cpp b/src/core/utils/slaac_address.cpp
index 4222c38..168d3c0 100644
--- a/src/core/utils/slaac_address.cpp
+++ b/src/core/utils/slaac_address.cpp
@@ -164,7 +164,7 @@
             {
                 iterator = NetworkData::kIteratorInit;
 
-                while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, config) == OT_ERROR_NONE)
+                while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, config) == kErrorNone)
                 {
                     if (config.mDp)
                     {
@@ -197,7 +197,7 @@
 
         iterator = NetworkData::kIteratorInit;
 
-        while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, config) == OT_ERROR_NONE)
+        while (Get<NetworkData::Leader>().GetNextOnMeshPrefix(iterator, config) == kErrorNone)
         {
             Ip6::Prefix &prefix = config.GetPrefix();
 
@@ -252,10 +252,10 @@
     }
 }
 
-otError Slaac::GenerateIid(Ip6::NetifUnicastAddress &aAddress,
-                           uint8_t *                 aNetworkId,
-                           uint8_t                   aNetworkIdLength,
-                           uint8_t *                 aDadCounter) const
+Error Slaac::GenerateIid(Ip6::NetifUnicastAddress &aAddress,
+                         uint8_t *                 aNetworkId,
+                         uint8_t                   aNetworkIdLength,
+                         uint8_t *                 aDadCounter) const
 {
     /*
      *  This method generates a semantically opaque IID per RFC 7217.
@@ -272,7 +272,7 @@
      *
      */
 
-    otError              error      = OT_ERROR_FAILED;
+    Error                error      = kErrorFailed;
     const uint8_t        netIface[] = {'w', 'p', 'a', 'n'};
     uint8_t              dadCounter = aDadCounter ? *aDadCounter : 0;
     IidSecretKey         secretKey;
@@ -294,9 +294,9 @@
             sha256.Update(aNetworkId, aNetworkIdLength);
         }
 
-        sha256.Update(netIface, sizeof(netIface));
-        sha256.Update(reinterpret_cast<uint8_t *>(&dadCounter), sizeof(dadCounter));
-        sha256.Update(secretKey.m8, sizeof(IidSecretKey));
+        sha256.Update(netIface);
+        sha256.Update(dadCounter);
+        sha256.Update(secretKey);
         sha256.Finish(hash);
 
         aAddress.GetAddress().GetIid().SetBytes(hash.GetBytes());
@@ -313,7 +313,7 @@
         }
 
         // Exit and return the address if the IID is not reserved,
-        ExitNow(error = OT_ERROR_NONE);
+        ExitNow(error = kErrorNone);
     }
 
     otLogWarnUtil("SLAAC: Failed to generate a non-reserved IID after %d attempts", kMaxIidCreationAttempts);
@@ -324,17 +324,17 @@
 
 void Slaac::GetIidSecretKey(IidSecretKey &aKey) const
 {
-    otError error;
+    Error error;
 
     error = Get<Settings>().ReadSlaacIidSecretKey(aKey);
-    VerifyOrExit(error != OT_ERROR_NONE);
+    VerifyOrExit(error != kErrorNone);
 
     // If there is no previously saved secret key, generate
     // a random one and save it.
 
     error = Random::Crypto::FillBuffer(aKey.m8, sizeof(IidSecretKey));
 
-    if (error != OT_ERROR_NONE)
+    if (error != kErrorNone)
     {
         IgnoreError(Random::Crypto::FillBuffer(aKey.m8, sizeof(IidSecretKey)));
     }
diff --git a/src/core/utils/slaac_address.hpp b/src/core/utils/slaac_address.hpp
index f8a82e7..1b0d3cf 100644
--- a/src/core/utils/slaac_address.hpp
+++ b/src/core/utils/slaac_address.hpp
@@ -135,14 +135,14 @@
      * @param[inout]  aDadCounter          A pointer to the DAD_Counter that is employed to resolve Duplicate
      *                                     Address Detection connflicts.
      *
-     * @retval OT_ERROR_NONE   If successfully generated the IID.
-     * @retval OT_ERROR_FAILED If no valid IID was generated.
+     * @retval kErrorNone    If successfully generated the IID.
+     * @retval kErrorFailed  If no valid IID was generated.
      *
      */
-    otError GenerateIid(Ip6::NetifUnicastAddress &aAddress,
-                        uint8_t *                 aNetworkId       = nullptr,
-                        uint8_t                   aNetworkIdLength = 0,
-                        uint8_t *                 aDadCounter      = nullptr) const;
+    Error GenerateIid(Ip6::NetifUnicastAddress &aAddress,
+                      uint8_t *                 aNetworkId       = nullptr,
+                      uint8_t                   aNetworkIdLength = 0,
+                      uint8_t *                 aDadCounter      = nullptr) const;
 
 private:
     enum
diff --git a/tests/scripts/expect/cli-anycast.exp b/src/lib/platform/BUILD.gn
similarity index 74%
copy from tests/scripts/expect/cli-anycast.exp
copy to src/lib/platform/BUILD.gn
index 3081a6b..c46831e 100644
--- a/tests/scripts/expect/cli-anycast.exp
+++ b/src/lib/platform/BUILD.gn
@@ -1,6 +1,4 @@
-#!/usr/bin/expect -f
-#
-#  Copyright (c) 2020, The OpenThread Authors.
+#  Copyright (c) 2021, The OpenThread Authors.
 #  All rights reserved.
 #
 #  Redistribution and use in source and binary forms, with or without
@@ -27,30 +25,18 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
-source "tests/scripts/expect/_common.exp"
+visibility = [ "../../../*" ]
 
+platform_sources = [
+  "exit_code.c",
+  "exit_code.h",
+]
 
-set spawn_id [spawn_node 1]
-
-send "panid 0xface\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-
-wait_for "state" "leader"
-expect "Done"
-
-send "ping fdde:ad00:beef:0:0:ff:fe00:fc00\n"
-expect "Done"
-expect "16 bytes from "
-expect {
-    "fdde:ad00:beef:0:0:ff:fe00:fc00" abort
-
-    -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})} {}
-
-    timeout abort
+config("platform_config") {
+  include_dirs = [ ".." ]
 }
 
-dispose
+static_library("libopenthread-platform") {
+  sources = platform_sources
+  public_configs = [ ":platform_config" ]
+}
diff --git a/src/lib/spinel/BUILD.gn b/src/lib/spinel/BUILD.gn
index 0747f91..e4e8f03 100644
--- a/src/lib/spinel/BUILD.gn
+++ b/src/lib/spinel/BUILD.gn
@@ -69,6 +69,7 @@
   public_deps = [
     ":spinel-api",
     "../../core:libopenthread_core_headers",
+    "../platform:libopenthread-platform",
   ]
   public_configs = [ ":spinel_config_openthread_message_enable" ]
 }
@@ -78,6 +79,7 @@
   public_deps = [
     ":spinel-api",
     "../../core:libopenthread_core_headers",
+    "../platform:libopenthread-platform",
   ]
   public_configs = [ ":spinel_config_openthread_message_disable" ]
 }
diff --git a/src/lib/spinel/CMakeLists.txt b/src/lib/spinel/CMakeLists.txt
index f7b24ba..1f2dd8d 100644
--- a/src/lib/spinel/CMakeLists.txt
+++ b/src/lib/spinel/CMakeLists.txt
@@ -38,13 +38,13 @@
 
 target_compile_definitions(openthread-spinel-ncp PRIVATE
     OPENTHREAD_FTD=1
-    OPENTHREAD_CONFIG_NCP_UART_ENABLE=1
+    OPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1
     PUBLIC OPENTHREAD_SPINEL_CONFIG_OPENTHREAD_MESSAGE_ENABLE=1
 )
 
 target_compile_definitions(openthread-spinel-rcp PRIVATE
     OPENTHREAD_RADIO=1
-    OPENTHREAD_CONFIG_NCP_UART_ENABLE=1
+    OPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1
     PUBLIC OPENTHREAD_SPINEL_CONFIG_OPENTHREAD_MESSAGE_ENABLE=0
 )
 
diff --git a/src/lib/spinel/radio_spinel.hpp b/src/lib/spinel/radio_spinel.hpp
index df57b34..e21083a 100644
--- a/src/lib/spinel/radio_spinel.hpp
+++ b/src/lib/spinel/radio_spinel.hpp
@@ -113,13 +113,14 @@
     /**
      * Initialize this radio transceiver.
      *
-     * @param[in]  aResetRadio            TRUE to reset on init, FALSE to not reset on init.
-     * @param[in]  aRestoreDatasetFromNcp TRUE to restore dataset to host from non-volatile memory
-     *                                    (only used when attempts to upgrade from NCP to RCP mode),
-     *                                    FALSE otherwise.
+     * @param[in]  aResetRadio                 TRUE to reset on init, FALSE to not reset on init.
+     * @param[in]  aRestoreDatasetFromNcp      TRUE to restore dataset to host from non-volatile memory
+     *                                         (only used when attempts to upgrade from NCP to RCP mode),
+     *                                         FALSE otherwise.
+     * @param[in]  aSkipRcpCompatibilityCheck  TRUE to skip RCP compatibility check, FALSE to perform the check.
      *
      */
-    void Init(bool aResetRadio, bool aRestoreDataSetFromNcp);
+    void Init(bool aResetRadio, bool aRestoreDataSetFromNcp, bool aSkipRcpCompatibilityCheck);
 
     /**
      * Deinitialize this radio transceiver.
@@ -651,6 +652,29 @@
     otError SetMacFrameCounter(uint32_t aMacFrameCounter);
 
     /**
+     * This method sets the radio region code.
+     *
+     * @param[in]   aRegionCode  The radio region code.
+     *
+     * @retval  OT_ERROR_NONE             Successfully set region code.
+     * @retval  OT_ERROR_FAILED           Other platform specific errors.
+     *
+     */
+    otError SetRadioRegion(uint16_t aRegionCode);
+
+    /**
+     * This method gets the radio region code.
+     *
+     * @param[out]   aRegionCode  The radio region code.
+     *
+     * @retval  OT_ERROR_INVALID_ARGS     @p aRegionCode is nullptr.
+     * @retval  OT_ERROR_NONE             Successfully got region code.
+     * @retval  OT_ERROR_FAILED           Other platform specific errors.
+     *
+     */
+    otError GetRadioRegion(uint16_t *aRegionCode);
+
+    /**
      * This method checks whether the spinel interface is radio-only.
      *
      * @param[out] aSupportsRcpApiVersion   A reference to a boolean variable to update whether the list of spinel
diff --git a/src/lib/spinel/radio_spinel_impl.hpp b/src/lib/spinel/radio_spinel_impl.hpp
index be5c201..612443b 100644
--- a/src/lib/spinel/radio_spinel_impl.hpp
+++ b/src/lib/spinel/radio_spinel_impl.hpp
@@ -38,7 +38,6 @@
 
 #include <openthread/dataset.h>
 #include <openthread/platform/diag.h>
-#include <openthread/platform/settings.h>
 #include <openthread/platform/time.h>
 
 #include "common/code_utils.hpp"
@@ -80,7 +79,7 @@
 namespace ot {
 namespace Spinel {
 
-static otError SpinelStatusToOtError(spinel_status_t aError)
+static inline otError SpinelStatusToOtError(spinel_status_t aError)
 {
     otError ret;
 
@@ -154,7 +153,7 @@
     return ret;
 }
 
-static void LogIfFail(const char *aText, otError aError)
+static inline void LogIfFail(const char *aText, otError aError)
 {
     OT_UNUSED_VARIABLE(aText);
     OT_UNUSED_VARIABLE(aError);
@@ -220,7 +219,9 @@
 }
 
 template <typename InterfaceType, typename ProcessContextType>
-void RadioSpinel<InterfaceType, ProcessContextType>::Init(bool aResetRadio, bool aRestoreDatasetFromNcp)
+void RadioSpinel<InterfaceType, ProcessContextType>::Init(bool aResetRadio,
+                                                          bool aRestoreDatasetFromNcp,
+                                                          bool aSkipRcpCompatibilityCheck)
 {
     otError error = OT_ERROR_NONE;
     bool    supportsRcpApiVersion;
@@ -247,14 +248,19 @@
 
         if (aRestoreDatasetFromNcp)
         {
+#if !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
             exitCode = (RestoreDatasetFromNcp() == OT_ERROR_NONE) ? OT_EXIT_SUCCESS : OT_EXIT_FAILURE;
+#endif
         }
 
         DieNow(exitCode);
     }
 
-    SuccessOrDie(CheckRcpApiVersion(supportsRcpApiVersion));
-    SuccessOrDie(CheckRadioCapabilities());
+    if (!aSkipRcpCompatibilityCheck)
+    {
+        SuccessOrDie(CheckRcpApiVersion(supportsRcpApiVersion));
+        SuccessOrDie(CheckRadioCapabilities());
+    }
 
     mRxRadioFrame.mPsdu  = mRxPsdu;
     mTxRadioFrame.mPsdu  = mTxPsdu;
@@ -408,12 +414,13 @@
     return error;
 }
 
+#if !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
 template <typename InterfaceType, typename ProcessContextType>
 otError RadioSpinel<InterfaceType, ProcessContextType>::RestoreDatasetFromNcp(void)
 {
     otError error = OT_ERROR_NONE;
 
-    otPlatSettingsInit(mInstance);
+    Instance::Get().template Get<SettingsDriver>().Init();
 
     otLogInfoPlat("Trying to get saved dataset from NCP");
     SuccessOrExit(
@@ -422,9 +429,10 @@
         error = Get(SPINEL_PROP_THREAD_PENDING_DATASET, SPINEL_DATATYPE_VOID_S, &RadioSpinel::ThreadDatasetHandler));
 
 exit:
-    otPlatSettingsDeinit(mInstance);
+    Instance::Get().template Get<SettingsDriver>().Deinit();
     return error;
 }
+#endif
 
 template <typename InterfaceType, typename ProcessContextType>
 void RadioSpinel<InterfaceType, ProcessContextType>::Deinit(void)
@@ -581,6 +589,7 @@
     LogIfFail("Error processing response", error);
 }
 
+#if !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
 template <typename InterfaceType, typename ProcessContextType>
 otError RadioSpinel<InterfaceType, ProcessContextType>::ThreadDatasetHandler(const uint8_t *aBuffer, uint16_t aLength)
 {
@@ -727,13 +736,14 @@
     opDataset.mComponents.mIsActiveTimestampPresent = true;
 
     SuccessOrExit(error = dataset.SetFrom(static_cast<MeshCoP::Dataset::Info &>(opDataset)));
-    SuccessOrExit(error = otPlatSettingsSet(
-                      mInstance, isActive ? SettingsBase::kKeyActiveDataset : SettingsBase::kKeyPendingDataset,
-                      dataset.GetBytes(), dataset.GetSize()));
+    SuccessOrExit(error = Instance::Get().template Get<SettingsDriver>().Set(
+                      isActive ? SettingsBase::kKeyActiveDataset : SettingsBase::kKeyPendingDataset, dataset.GetBytes(),
+                      dataset.GetSize()));
 
 exit:
     return error;
 }
+#endif // #if !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
 
 template <typename InterfaceType, typename ProcessContextType>
 void RadioSpinel<InterfaceType, ProcessContextType>::HandleWaitingResponse(uint32_t          aCommand,
@@ -2116,13 +2126,14 @@
     localTxTimestamp = otPlatTimeGet();
 
     // Dummy timestamp payload to make request length same as response
-    error = GetWithParam(SPINEL_PROP_RCP_TIMESTAMP, buffer, packed, SPINEL_DATATYPE_UINT64_S, &remoteTimestamp);
+    error = GetWithParam(SPINEL_PROP_RCP_TIMESTAMP, buffer, static_cast<spinel_size_t>(packed),
+                         SPINEL_DATATYPE_UINT64_S, &remoteTimestamp);
 
     localRxTimestamp = otPlatTimeGet();
 
     VerifyOrExit(error == OT_ERROR_NONE, mRadioTimeRecalcStart = localRxTimestamp);
 
-    mRadioTimeOffset      = remoteTimestamp - ((localRxTimestamp / 2) + (localTxTimestamp / 2));
+    mRadioTimeOffset      = static_cast<int64_t>(remoteTimestamp - ((localRxTimestamp / 2) + (localTxTimestamp / 2)));
     mIsTimeSynced         = true;
     mRadioTimeRecalcStart = localRxTimestamp + RCP_TIME_OFFSET_CHECK_INTERVAL;
 
@@ -2146,6 +2157,8 @@
 template <typename InterfaceType, typename ProcessContextType>
 void RadioSpinel<InterfaceType, ProcessContextType>::HandleRcpUnexpectedReset(spinel_status_t aStatus)
 {
+    OT_UNUSED_VARIABLE(aStatus);
+
     otLogCritPlat("Unexpected RCP reset: %s", spinel_status_to_cstr(aStatus));
 
 #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0
@@ -2262,8 +2275,12 @@
                          sizeof(otMacKey)));
     }
 
-    SuccessOrDie(Instance::Get().template Get<Settings>().ReadNetworkInfo(networkInfo));
-    SuccessOrDie(Set(SPINEL_PROP_RCP_MAC_FRAME_COUNTER, SPINEL_DATATYPE_UINT32_S, networkInfo.GetMacFrameCounter()));
+    if (mInstance != nullptr)
+    {
+        SuccessOrDie(static_cast<Instance *>(mInstance)->template Get<Settings>().ReadNetworkInfo(networkInfo));
+        SuccessOrDie(
+            Set(SPINEL_PROP_RCP_MAC_FRAME_COUNTER, SPINEL_DATATYPE_UINT32_S, networkInfo.GetMacFrameCounter()));
+    }
 
     for (int i = 0; i < mSrcMatchShortEntryCount; ++i)
     {
@@ -2329,5 +2346,23 @@
     return error;
 }
 
+template <typename InterfaceType, typename ProcessContextType>
+otError RadioSpinel<InterfaceType, ProcessContextType>::SetRadioRegion(uint16_t aRegionCode)
+{
+    return Set(SPINEL_PROP_PHY_REGION_CODE, SPINEL_DATATYPE_UINT16_S, aRegionCode);
+}
+
+template <typename InterfaceType, typename ProcessContextType>
+otError RadioSpinel<InterfaceType, ProcessContextType>::GetRadioRegion(uint16_t *aRegionCode)
+{
+    otError error = OT_ERROR_NONE;
+
+    VerifyOrExit(aRegionCode != nullptr, error = OT_ERROR_INVALID_ARGS);
+    error = Get(SPINEL_PROP_PHY_REGION_CODE, SPINEL_DATATYPE_UINT16_S, aRegionCode);
+
+exit:
+    return error;
+}
+
 } // namespace Spinel
 } // namespace ot
diff --git a/src/lib/spinel/spinel.c b/src/lib/spinel/spinel.c
index 7595a7f..9181e6e 100644
--- a/src/lib/spinel/spinel.c
+++ b/src/lib/spinel/spinel.c
@@ -1148,1619 +1148,489 @@
 
 // LCOV_EXCL_START
 
+struct spinel_cstr
+{
+    uint32_t    val;
+    const char *str;
+};
+#define __SPINEL_CSTR(PREFIX, VALUE)     \
+    {                                    \
+        (uint32_t) PREFIX##VALUE, #VALUE \
+    }
+
+static const char *spinel_to_cstr(const struct spinel_cstr *table, uint32_t val)
+{
+    int i;
+
+    for (i = 0; table[i].str; i++)
+        if (val == table[i].val)
+            return table[i].str;
+    return "UNKNOWN";
+}
+
+#define SPINEL_COMMAND_CSTR(VALUE) __SPINEL_CSTR(SPINEL_CMD_, VALUE)
 const char *spinel_command_to_cstr(spinel_command_t command)
 {
-    const char *ret = "UNKNOWN";
+    static const struct spinel_cstr spinel_commands_cstr[] = {
+        SPINEL_COMMAND_CSTR(NOOP),
+        SPINEL_COMMAND_CSTR(RESET),
+        SPINEL_COMMAND_CSTR(PROP_VALUE_GET),
+        SPINEL_COMMAND_CSTR(PROP_VALUE_SET),
+        SPINEL_COMMAND_CSTR(PROP_VALUE_INSERT),
+        SPINEL_COMMAND_CSTR(PROP_VALUE_REMOVE),
+        SPINEL_COMMAND_CSTR(PROP_VALUE_IS),
+        SPINEL_COMMAND_CSTR(PROP_VALUE_INSERTED),
+        SPINEL_COMMAND_CSTR(PROP_VALUE_REMOVED),
+        SPINEL_COMMAND_CSTR(NET_SAVE),
+        SPINEL_COMMAND_CSTR(NET_CLEAR),
+        SPINEL_COMMAND_CSTR(NET_RECALL),
+        SPINEL_COMMAND_CSTR(HBO_OFFLOAD),
+        SPINEL_COMMAND_CSTR(HBO_RECLAIM),
+        SPINEL_COMMAND_CSTR(HBO_DROP),
+        SPINEL_COMMAND_CSTR(HBO_OFFLOADED),
+        SPINEL_COMMAND_CSTR(HBO_RECLAIMED),
+        SPINEL_COMMAND_CSTR(HBO_DROPPED),
+        SPINEL_COMMAND_CSTR(PEEK),
+        SPINEL_COMMAND_CSTR(PEEK_RET),
+        SPINEL_COMMAND_CSTR(POKE),
+        SPINEL_COMMAND_CSTR(PROP_VALUE_MULTI_GET),
+        SPINEL_COMMAND_CSTR(PROP_VALUE_MULTI_SET),
+        SPINEL_COMMAND_CSTR(PROP_VALUES_ARE),
+        {0},
+    };
 
-    switch (command)
-    {
-    case SPINEL_CMD_NOOP:
-        ret = "NOOP";
-        break;
-
-    case SPINEL_CMD_RESET:
-        ret = "RESET";
-        break;
-
-    case SPINEL_CMD_PROP_VALUE_GET:
-        ret = "PROP_VALUE_GET";
-        break;
-
-    case SPINEL_CMD_PROP_VALUE_SET:
-        ret = "PROP_VALUE_SET";
-        break;
-
-    case SPINEL_CMD_PROP_VALUE_INSERT:
-        ret = "PROP_VALUE_INSERT";
-        break;
-
-    case SPINEL_CMD_PROP_VALUE_REMOVE:
-        ret = "PROP_VALUE_REMOVE";
-        break;
-
-    case SPINEL_CMD_PROP_VALUE_IS:
-        ret = "PROP_VALUE_IS";
-        break;
-
-    case SPINEL_CMD_PROP_VALUE_INSERTED:
-        ret = "PROP_VALUE_INSERTED";
-        break;
-
-    case SPINEL_CMD_PROP_VALUE_REMOVED:
-        ret = "PROP_VALUE_REMOVED";
-        break;
-
-    case SPINEL_CMD_NET_SAVE:
-        ret = "NET_SAVE";
-        break;
-
-    case SPINEL_CMD_NET_CLEAR:
-        ret = "NET_CLEAR";
-        break;
-
-    case SPINEL_CMD_NET_RECALL:
-        ret = "NET_RECALL";
-        break;
-
-    case SPINEL_CMD_HBO_OFFLOAD:
-        ret = "HBO_OFFLOAD";
-        break;
-
-    case SPINEL_CMD_HBO_RECLAIM:
-        ret = "HBO_RECLAIM";
-        break;
-
-    case SPINEL_CMD_HBO_DROP:
-        ret = "HBO_DROP";
-        break;
-
-    case SPINEL_CMD_HBO_OFFLOADED:
-        ret = "HBO_OFFLOADED";
-        break;
-
-    case SPINEL_CMD_HBO_RECLAIMED:
-        ret = "HBO_RECLAIMED";
-        break;
-
-    case SPINEL_CMD_HBO_DROPPED:
-        ret = "HBO_DROPPED";
-        break;
-
-    case SPINEL_CMD_PEEK:
-        ret = "PEEK";
-        break;
-
-    case SPINEL_CMD_PEEK_RET:
-        ret = "PEEK_RET";
-        break;
-
-    case SPINEL_CMD_POKE:
-        ret = "POKE";
-        break;
-
-    case SPINEL_CMD_PROP_VALUE_MULTI_GET:
-        ret = "PROP_VALUE_MULTI_GET";
-        break;
-
-    case SPINEL_CMD_PROP_VALUE_MULTI_SET:
-        ret = "PROP_VALUE_MULTI_SET";
-        break;
-
-    case SPINEL_CMD_PROP_VALUES_ARE:
-        ret = "PROP_VALUES_ARE";
-        break;
-
-    default:
-        break;
-    }
-
-    return ret;
+    return spinel_to_cstr(spinel_commands_cstr, command);
 }
 
+#define SPINEL_PROP_CSTR(VALUE) __SPINEL_CSTR(SPINEL_PROP_, VALUE)
 const char *spinel_prop_key_to_cstr(spinel_prop_key_t prop_key)
 {
-    const char *ret = "UNKNOWN";
+    static const struct spinel_cstr spinel_prop_cstr[] = {
+        SPINEL_PROP_CSTR(LAST_STATUS),
+        SPINEL_PROP_CSTR(PROTOCOL_VERSION),
+        SPINEL_PROP_CSTR(NCP_VERSION),
+        SPINEL_PROP_CSTR(INTERFACE_TYPE),
+        SPINEL_PROP_CSTR(VENDOR_ID),
+        SPINEL_PROP_CSTR(CAPS),
+        SPINEL_PROP_CSTR(INTERFACE_COUNT),
+        SPINEL_PROP_CSTR(POWER_STATE),
+        SPINEL_PROP_CSTR(HWADDR),
+        SPINEL_PROP_CSTR(LOCK),
+        SPINEL_PROP_CSTR(HBO_MEM_MAX),
+        SPINEL_PROP_CSTR(HBO_BLOCK_MAX),
+        SPINEL_PROP_CSTR(HOST_POWER_STATE),
+        SPINEL_PROP_CSTR(MCU_POWER_STATE),
+        SPINEL_PROP_CSTR(GPIO_CONFIG),
+        SPINEL_PROP_CSTR(GPIO_STATE),
+        SPINEL_PROP_CSTR(GPIO_STATE_SET),
+        SPINEL_PROP_CSTR(GPIO_STATE_CLEAR),
+        SPINEL_PROP_CSTR(TRNG_32),
+        SPINEL_PROP_CSTR(TRNG_128),
+        SPINEL_PROP_CSTR(TRNG_RAW_32),
+        SPINEL_PROP_CSTR(UNSOL_UPDATE_FILTER),
+        SPINEL_PROP_CSTR(UNSOL_UPDATE_LIST),
+        SPINEL_PROP_CSTR(PHY_ENABLED),
+        SPINEL_PROP_CSTR(PHY_CHAN),
+        SPINEL_PROP_CSTR(PHY_CHAN_SUPPORTED),
+        SPINEL_PROP_CSTR(PHY_FREQ),
+        SPINEL_PROP_CSTR(PHY_CCA_THRESHOLD),
+        SPINEL_PROP_CSTR(PHY_TX_POWER),
+        SPINEL_PROP_CSTR(PHY_FEM_LNA_GAIN),
+        SPINEL_PROP_CSTR(PHY_RSSI),
+        SPINEL_PROP_CSTR(PHY_RX_SENSITIVITY),
+        SPINEL_PROP_CSTR(PHY_PCAP_ENABLED),
+        SPINEL_PROP_CSTR(PHY_CHAN_PREFERRED),
+        SPINEL_PROP_CSTR(PHY_CHAN_MAX_POWER),
+        SPINEL_PROP_CSTR(JAM_DETECT_ENABLE),
+        SPINEL_PROP_CSTR(JAM_DETECTED),
+        SPINEL_PROP_CSTR(JAM_DETECT_RSSI_THRESHOLD),
+        SPINEL_PROP_CSTR(JAM_DETECT_WINDOW),
+        SPINEL_PROP_CSTR(JAM_DETECT_BUSY),
+        SPINEL_PROP_CSTR(JAM_DETECT_HISTORY_BITMAP),
+        SPINEL_PROP_CSTR(CHANNEL_MONITOR_SAMPLE_INTERVAL),
+        SPINEL_PROP_CSTR(CHANNEL_MONITOR_RSSI_THRESHOLD),
+        SPINEL_PROP_CSTR(CHANNEL_MONITOR_SAMPLE_WINDOW),
+        SPINEL_PROP_CSTR(CHANNEL_MONITOR_SAMPLE_COUNT),
+        SPINEL_PROP_CSTR(CHANNEL_MONITOR_CHANNEL_OCCUPANCY),
+        SPINEL_PROP_CSTR(RADIO_CAPS),
+        SPINEL_PROP_CSTR(RADIO_COEX_METRICS),
+        SPINEL_PROP_CSTR(RADIO_COEX_ENABLE),
+        SPINEL_PROP_CSTR(MAC_SCAN_STATE),
+        SPINEL_PROP_CSTR(MAC_SCAN_MASK),
+        SPINEL_PROP_CSTR(MAC_SCAN_PERIOD),
+        SPINEL_PROP_CSTR(MAC_SCAN_BEACON),
+        SPINEL_PROP_CSTR(MAC_15_4_LADDR),
+        SPINEL_PROP_CSTR(MAC_15_4_SADDR),
+        SPINEL_PROP_CSTR(MAC_15_4_PANID),
+        SPINEL_PROP_CSTR(MAC_RAW_STREAM_ENABLED),
+        SPINEL_PROP_CSTR(MAC_PROMISCUOUS_MODE),
+        SPINEL_PROP_CSTR(MAC_ENERGY_SCAN_RESULT),
+        SPINEL_PROP_CSTR(MAC_DATA_POLL_PERIOD),
+        SPINEL_PROP_CSTR(MAC_ALLOWLIST),
+        SPINEL_PROP_CSTR(MAC_ALLOWLIST_ENABLED),
+        SPINEL_PROP_CSTR(MAC_EXTENDED_ADDR),
+        SPINEL_PROP_CSTR(MAC_SRC_MATCH_ENABLED),
+        SPINEL_PROP_CSTR(MAC_SRC_MATCH_SHORT_ADDRESSES),
+        SPINEL_PROP_CSTR(MAC_SRC_MATCH_EXTENDED_ADDRESSES),
+        SPINEL_PROP_CSTR(MAC_DENYLIST),
+        SPINEL_PROP_CSTR(MAC_DENYLIST_ENABLED),
+        SPINEL_PROP_CSTR(MAC_FIXED_RSS),
+        SPINEL_PROP_CSTR(MAC_CCA_FAILURE_RATE),
+        SPINEL_PROP_CSTR(MAC_MAX_RETRY_NUMBER_DIRECT),
+        SPINEL_PROP_CSTR(MAC_MAX_RETRY_NUMBER_INDIRECT),
+        SPINEL_PROP_CSTR(NET_SAVED),
+        SPINEL_PROP_CSTR(NET_IF_UP),
+        SPINEL_PROP_CSTR(NET_STACK_UP),
+        SPINEL_PROP_CSTR(NET_ROLE),
+        SPINEL_PROP_CSTR(NET_NETWORK_NAME),
+        SPINEL_PROP_CSTR(NET_XPANID),
+        SPINEL_PROP_CSTR(NET_MASTER_KEY),
+        SPINEL_PROP_CSTR(NET_KEY_SEQUENCE_COUNTER),
+        SPINEL_PROP_CSTR(NET_PARTITION_ID),
+        SPINEL_PROP_CSTR(NET_REQUIRE_JOIN_EXISTING),
+        SPINEL_PROP_CSTR(NET_KEY_SWITCH_GUARDTIME),
+        SPINEL_PROP_CSTR(NET_PSKC),
+        SPINEL_PROP_CSTR(THREAD_LEADER_ADDR),
+        SPINEL_PROP_CSTR(THREAD_PARENT),
+        SPINEL_PROP_CSTR(THREAD_CHILD_TABLE),
+        SPINEL_PROP_CSTR(THREAD_LEADER_RID),
+        SPINEL_PROP_CSTR(THREAD_LEADER_WEIGHT),
+        SPINEL_PROP_CSTR(THREAD_LOCAL_LEADER_WEIGHT),
+        SPINEL_PROP_CSTR(THREAD_NETWORK_DATA),
+        SPINEL_PROP_CSTR(THREAD_NETWORK_DATA_VERSION),
+        SPINEL_PROP_CSTR(THREAD_STABLE_NETWORK_DATA),
+        SPINEL_PROP_CSTR(THREAD_STABLE_NETWORK_DATA_VERSION),
+        SPINEL_PROP_CSTR(THREAD_ON_MESH_NETS),
+        SPINEL_PROP_CSTR(THREAD_OFF_MESH_ROUTES),
+        SPINEL_PROP_CSTR(THREAD_ASSISTING_PORTS),
+        SPINEL_PROP_CSTR(THREAD_ALLOW_LOCAL_NET_DATA_CHANGE),
+        SPINEL_PROP_CSTR(THREAD_MODE),
+        SPINEL_PROP_CSTR(THREAD_CHILD_TIMEOUT),
+        SPINEL_PROP_CSTR(THREAD_RLOC16),
+        SPINEL_PROP_CSTR(THREAD_ROUTER_UPGRADE_THRESHOLD),
+        SPINEL_PROP_CSTR(THREAD_CONTEXT_REUSE_DELAY),
+        SPINEL_PROP_CSTR(THREAD_NETWORK_ID_TIMEOUT),
+        SPINEL_PROP_CSTR(THREAD_ACTIVE_ROUTER_IDS),
+        SPINEL_PROP_CSTR(THREAD_RLOC16_DEBUG_PASSTHRU),
+        SPINEL_PROP_CSTR(THREAD_ROUTER_ROLE_ENABLED),
+        SPINEL_PROP_CSTR(THREAD_ROUTER_DOWNGRADE_THRESHOLD),
+        SPINEL_PROP_CSTR(THREAD_ROUTER_SELECTION_JITTER),
+        SPINEL_PROP_CSTR(THREAD_PREFERRED_ROUTER_ID),
+        SPINEL_PROP_CSTR(THREAD_NEIGHBOR_TABLE),
+        SPINEL_PROP_CSTR(THREAD_CHILD_COUNT_MAX),
+        SPINEL_PROP_CSTR(THREAD_LEADER_NETWORK_DATA),
+        SPINEL_PROP_CSTR(THREAD_STABLE_LEADER_NETWORK_DATA),
+        SPINEL_PROP_CSTR(THREAD_JOINERS),
+        SPINEL_PROP_CSTR(THREAD_COMMISSIONER_ENABLED),
+        SPINEL_PROP_CSTR(THREAD_TMF_PROXY_ENABLED),
+        SPINEL_PROP_CSTR(THREAD_TMF_PROXY_STREAM),
+        SPINEL_PROP_CSTR(THREAD_UDP_FORWARD_STREAM),
+        SPINEL_PROP_CSTR(THREAD_DISCOVERY_SCAN_JOINER_FLAG),
+        SPINEL_PROP_CSTR(THREAD_DISCOVERY_SCAN_ENABLE_FILTERING),
+        SPINEL_PROP_CSTR(THREAD_DISCOVERY_SCAN_PANID),
+        SPINEL_PROP_CSTR(THREAD_STEERING_DATA),
+        SPINEL_PROP_CSTR(THREAD_ROUTER_TABLE),
+        SPINEL_PROP_CSTR(THREAD_ACTIVE_DATASET),
+        SPINEL_PROP_CSTR(THREAD_PENDING_DATASET),
+        SPINEL_PROP_CSTR(THREAD_MGMT_SET_ACTIVE_DATASET),
+        SPINEL_PROP_CSTR(THREAD_MGMT_SET_PENDING_DATASET),
+        SPINEL_PROP_CSTR(DATASET_ACTIVE_TIMESTAMP),
+        SPINEL_PROP_CSTR(DATASET_PENDING_TIMESTAMP),
+        SPINEL_PROP_CSTR(DATASET_DELAY_TIMER),
+        SPINEL_PROP_CSTR(DATASET_SECURITY_POLICY),
+        SPINEL_PROP_CSTR(DATASET_RAW_TLVS),
+        SPINEL_PROP_CSTR(THREAD_CHILD_TABLE_ADDRESSES),
+        SPINEL_PROP_CSTR(THREAD_NEIGHBOR_TABLE_ERROR_RATES),
+        SPINEL_PROP_CSTR(THREAD_ADDRESS_CACHE_TABLE),
+        SPINEL_PROP_CSTR(THREAD_MGMT_GET_ACTIVE_DATASET),
+        SPINEL_PROP_CSTR(THREAD_MGMT_GET_PENDING_DATASET),
+        SPINEL_PROP_CSTR(DATASET_DEST_ADDRESS),
+        SPINEL_PROP_CSTR(THREAD_NEW_DATASET),
+        SPINEL_PROP_CSTR(THREAD_CSL_PERIOD),
+        SPINEL_PROP_CSTR(THREAD_CSL_TIMEOUT),
+        SPINEL_PROP_CSTR(THREAD_CSL_CHANNEL),
+        SPINEL_PROP_CSTR(THREAD_DOMAIN_NAME),
+        SPINEL_PROP_CSTR(THREAD_MLR_REQUEST),
+        SPINEL_PROP_CSTR(THREAD_MLR_RESPONSE),
+        SPINEL_PROP_CSTR(THREAD_BACKBONE_ROUTER_PRIMARY),
+        SPINEL_PROP_CSTR(THREAD_BACKBONE_ROUTER_LOCAL_STATE),
+        SPINEL_PROP_CSTR(THREAD_BACKBONE_ROUTER_LOCAL_CONFIG),
+        SPINEL_PROP_CSTR(THREAD_BACKBONE_ROUTER_LOCAL_REGISTER),
+        SPINEL_PROP_CSTR(THREAD_BACKBONE_ROUTER_LOCAL_REGISTRATION_JITTER),
+        SPINEL_PROP_CSTR(MESHCOP_JOINER_STATE),
+        SPINEL_PROP_CSTR(MESHCOP_JOINER_COMMISSIONING),
+        SPINEL_PROP_CSTR(IPV6_LL_ADDR),
+        SPINEL_PROP_CSTR(IPV6_ML_ADDR),
+        SPINEL_PROP_CSTR(IPV6_ML_PREFIX),
+        SPINEL_PROP_CSTR(IPV6_ADDRESS_TABLE),
+        SPINEL_PROP_CSTR(IPV6_ROUTE_TABLE),
+        SPINEL_PROP_CSTR(IPV6_ICMP_PING_OFFLOAD),
+        SPINEL_PROP_CSTR(IPV6_MULTICAST_ADDRESS_TABLE),
+        SPINEL_PROP_CSTR(IPV6_ICMP_PING_OFFLOAD_MODE),
+        SPINEL_PROP_CSTR(STREAM_DEBUG),
+        SPINEL_PROP_CSTR(STREAM_RAW),
+        SPINEL_PROP_CSTR(STREAM_NET),
+        SPINEL_PROP_CSTR(STREAM_NET_INSECURE),
+        SPINEL_PROP_CSTR(STREAM_LOG),
+        SPINEL_PROP_CSTR(MESHCOP_COMMISSIONER_STATE),
+        SPINEL_PROP_CSTR(MESHCOP_COMMISSIONER_JOINERS),
+        SPINEL_PROP_CSTR(MESHCOP_COMMISSIONER_PROVISIONING_URL),
+        SPINEL_PROP_CSTR(MESHCOP_COMMISSIONER_SESSION_ID),
+        SPINEL_PROP_CSTR(MESHCOP_JOINER_DISCERNER),
+        SPINEL_PROP_CSTR(MESHCOP_COMMISSIONER_ANNOUNCE_BEGIN),
+        SPINEL_PROP_CSTR(MESHCOP_COMMISSIONER_ENERGY_SCAN),
+        SPINEL_PROP_CSTR(MESHCOP_COMMISSIONER_ENERGY_SCAN_RESULT),
+        SPINEL_PROP_CSTR(MESHCOP_COMMISSIONER_PAN_ID_QUERY),
+        SPINEL_PROP_CSTR(MESHCOP_COMMISSIONER_PAN_ID_CONFLICT_RESULT),
+        SPINEL_PROP_CSTR(MESHCOP_COMMISSIONER_MGMT_GET),
+        SPINEL_PROP_CSTR(MESHCOP_COMMISSIONER_MGMT_SET),
+        SPINEL_PROP_CSTR(MESHCOP_COMMISSIONER_GENERATE_PSKC),
+        SPINEL_PROP_CSTR(CHANNEL_MANAGER_NEW_CHANNEL),
+        SPINEL_PROP_CSTR(CHANNEL_MANAGER_DELAY),
+        SPINEL_PROP_CSTR(CHANNEL_MANAGER_SUPPORTED_CHANNELS),
+        SPINEL_PROP_CSTR(CHANNEL_MANAGER_FAVORED_CHANNELS),
+        SPINEL_PROP_CSTR(CHANNEL_MANAGER_CHANNEL_SELECT),
+        SPINEL_PROP_CSTR(CHANNEL_MANAGER_AUTO_SELECT_ENABLED),
+        SPINEL_PROP_CSTR(CHANNEL_MANAGER_AUTO_SELECT_INTERVAL),
+        SPINEL_PROP_CSTR(THREAD_NETWORK_TIME),
+        SPINEL_PROP_CSTR(TIME_SYNC_PERIOD),
+        SPINEL_PROP_CSTR(TIME_SYNC_XTAL_THRESHOLD),
+        SPINEL_PROP_CSTR(CHILD_SUPERVISION_INTERVAL),
+        SPINEL_PROP_CSTR(CHILD_SUPERVISION_CHECK_TIMEOUT),
+        SPINEL_PROP_CSTR(RCP_VERSION),
+        SPINEL_PROP_CSTR(PARENT_RESPONSE_INFO),
+        SPINEL_PROP_CSTR(SLAAC_ENABLED),
+        SPINEL_PROP_CSTR(SUPPORTED_RADIO_LINKS),
+        SPINEL_PROP_CSTR(NEIGHBOR_TABLE_MULTI_RADIO_INFO),
+        SPINEL_PROP_CSTR(SRP_CLIENT_START),
+        SPINEL_PROP_CSTR(SRP_CLIENT_LEASE_INTERVAL),
+        SPINEL_PROP_CSTR(SRP_CLIENT_KEY_LEASE_INTERVAL),
+        SPINEL_PROP_CSTR(SRP_CLIENT_HOST_INFO),
+        SPINEL_PROP_CSTR(SRP_CLIENT_HOST_NAME),
+        SPINEL_PROP_CSTR(SRP_CLIENT_HOST_ADDRESSES),
+        SPINEL_PROP_CSTR(SRP_CLIENT_SERVICES),
+        SPINEL_PROP_CSTR(SRP_CLIENT_HOST_SERVICES_REMOVE),
+        SPINEL_PROP_CSTR(SRP_CLIENT_HOST_SERVICES_CLEAR),
+        SPINEL_PROP_CSTR(SRP_CLIENT_EVENT),
+        SPINEL_PROP_CSTR(SERVER_ALLOW_LOCAL_DATA_CHANGE),
+        SPINEL_PROP_CSTR(SERVER_SERVICES),
+        SPINEL_PROP_CSTR(SERVER_LEADER_SERVICES),
+        SPINEL_PROP_CSTR(RCP_API_VERSION),
+        SPINEL_PROP_CSTR(UART_BITRATE),
+        SPINEL_PROP_CSTR(UART_XON_XOFF),
+        SPINEL_PROP_CSTR(15_4_PIB_PHY_CHANNELS_SUPPORTED),
+        SPINEL_PROP_CSTR(15_4_PIB_MAC_PROMISCUOUS_MODE),
+        SPINEL_PROP_CSTR(15_4_PIB_MAC_SECURITY_ENABLED),
+        SPINEL_PROP_CSTR(CNTR_RESET),
+        SPINEL_PROP_CSTR(CNTR_TX_PKT_TOTAL),
+        SPINEL_PROP_CSTR(CNTR_TX_PKT_ACK_REQ),
+        SPINEL_PROP_CSTR(CNTR_TX_PKT_ACKED),
+        SPINEL_PROP_CSTR(CNTR_TX_PKT_NO_ACK_REQ),
+        SPINEL_PROP_CSTR(CNTR_TX_PKT_DATA),
+        SPINEL_PROP_CSTR(CNTR_TX_PKT_DATA_POLL),
+        SPINEL_PROP_CSTR(CNTR_TX_PKT_BEACON),
+        SPINEL_PROP_CSTR(CNTR_TX_PKT_BEACON_REQ),
+        SPINEL_PROP_CSTR(CNTR_TX_PKT_OTHER),
+        SPINEL_PROP_CSTR(CNTR_TX_PKT_RETRY),
+        SPINEL_PROP_CSTR(CNTR_TX_ERR_CCA),
+        SPINEL_PROP_CSTR(CNTR_TX_PKT_UNICAST),
+        SPINEL_PROP_CSTR(CNTR_TX_PKT_BROADCAST),
+        SPINEL_PROP_CSTR(CNTR_TX_ERR_ABORT),
+        SPINEL_PROP_CSTR(CNTR_RX_PKT_TOTAL),
+        SPINEL_PROP_CSTR(CNTR_RX_PKT_DATA),
+        SPINEL_PROP_CSTR(CNTR_RX_PKT_DATA_POLL),
+        SPINEL_PROP_CSTR(CNTR_RX_PKT_BEACON),
+        SPINEL_PROP_CSTR(CNTR_RX_PKT_BEACON_REQ),
+        SPINEL_PROP_CSTR(CNTR_RX_PKT_OTHER),
+        SPINEL_PROP_CSTR(CNTR_RX_PKT_FILT_WL),
+        SPINEL_PROP_CSTR(CNTR_RX_PKT_FILT_DA),
+        SPINEL_PROP_CSTR(CNTR_RX_ERR_EMPTY),
+        SPINEL_PROP_CSTR(CNTR_RX_ERR_UKWN_NBR),
+        SPINEL_PROP_CSTR(CNTR_RX_ERR_NVLD_SADDR),
+        SPINEL_PROP_CSTR(CNTR_RX_ERR_SECURITY),
+        SPINEL_PROP_CSTR(CNTR_RX_ERR_BAD_FCS),
+        SPINEL_PROP_CSTR(CNTR_RX_ERR_OTHER),
+        SPINEL_PROP_CSTR(CNTR_RX_PKT_DUP),
+        SPINEL_PROP_CSTR(CNTR_RX_PKT_UNICAST),
+        SPINEL_PROP_CSTR(CNTR_RX_PKT_BROADCAST),
+        SPINEL_PROP_CSTR(CNTR_TX_IP_SEC_TOTAL),
+        SPINEL_PROP_CSTR(CNTR_TX_IP_INSEC_TOTAL),
+        SPINEL_PROP_CSTR(CNTR_TX_IP_DROPPED),
+        SPINEL_PROP_CSTR(CNTR_RX_IP_SEC_TOTAL),
+        SPINEL_PROP_CSTR(CNTR_RX_IP_INSEC_TOTAL),
+        SPINEL_PROP_CSTR(CNTR_RX_IP_DROPPED),
+        SPINEL_PROP_CSTR(CNTR_TX_SPINEL_TOTAL),
+        SPINEL_PROP_CSTR(CNTR_RX_SPINEL_TOTAL),
+        SPINEL_PROP_CSTR(CNTR_RX_SPINEL_ERR),
+        SPINEL_PROP_CSTR(CNTR_RX_SPINEL_OUT_OF_ORDER_TID),
+        SPINEL_PROP_CSTR(CNTR_IP_TX_SUCCESS),
+        SPINEL_PROP_CSTR(CNTR_IP_RX_SUCCESS),
+        SPINEL_PROP_CSTR(CNTR_IP_TX_FAILURE),
+        SPINEL_PROP_CSTR(CNTR_IP_RX_FAILURE),
+        SPINEL_PROP_CSTR(MSG_BUFFER_COUNTERS),
+        SPINEL_PROP_CSTR(CNTR_ALL_MAC_COUNTERS),
+        SPINEL_PROP_CSTR(CNTR_MLE_COUNTERS),
+        SPINEL_PROP_CSTR(CNTR_ALL_IP_COUNTERS),
+        SPINEL_PROP_CSTR(CNTR_MAC_RETRY_HISTOGRAM),
+        SPINEL_PROP_CSTR(NEST_STREAM_MFG),
+        SPINEL_PROP_CSTR(NEST_LEGACY_ULA_PREFIX),
+        SPINEL_PROP_CSTR(NEST_LEGACY_LAST_NODE_JOINED),
+        SPINEL_PROP_CSTR(DEBUG_TEST_ASSERT),
+        SPINEL_PROP_CSTR(DEBUG_NCP_LOG_LEVEL),
+        SPINEL_PROP_CSTR(DEBUG_TEST_WATCHDOG),
+        SPINEL_PROP_CSTR(RCP_MAC_FRAME_COUNTER),
+        SPINEL_PROP_CSTR(RCP_MAC_KEY),
+        SPINEL_PROP_CSTR(DEBUG_LOG_TIMESTAMP_BASE),
+        SPINEL_PROP_CSTR(DEBUG_TREL_TEST_MODE_ENABLE),
+        {0},
+    };
 
-    switch (prop_key)
-    {
-    case SPINEL_PROP_LAST_STATUS:
-        ret = "LAST_STATUS";
-        break;
-
-    case SPINEL_PROP_PROTOCOL_VERSION:
-        ret = "PROTOCOL_VERSION";
-        break;
-
-    case SPINEL_PROP_NCP_VERSION:
-        ret = "NCP_VERSION";
-        break;
-
-    case SPINEL_PROP_INTERFACE_TYPE:
-        ret = "INTERFACE_TYPE";
-        break;
-
-    case SPINEL_PROP_VENDOR_ID:
-        ret = "VENDOR_ID";
-        break;
-
-    case SPINEL_PROP_CAPS:
-        ret = "CAPS";
-        break;
-
-    case SPINEL_PROP_INTERFACE_COUNT:
-        ret = "INTERFACE_COUNT";
-        break;
-
-    case SPINEL_PROP_POWER_STATE:
-        ret = "POWER_STATE";
-        break;
-
-    case SPINEL_PROP_HWADDR:
-        ret = "HWADDR";
-        break;
-
-    case SPINEL_PROP_LOCK:
-        ret = "LOCK";
-        break;
-
-    case SPINEL_PROP_HBO_MEM_MAX:
-        ret = "HBO_MEM_MAX";
-        break;
-
-    case SPINEL_PROP_HBO_BLOCK_MAX:
-        ret = "HBO_BLOCK_MAX";
-        break;
-
-    case SPINEL_PROP_HOST_POWER_STATE:
-        ret = "HOST_POWER_STATE";
-        break;
-
-    case SPINEL_PROP_MCU_POWER_STATE:
-        ret = "MCU_POWER_STATE";
-        break;
-
-    case SPINEL_PROP_GPIO_CONFIG:
-        ret = "GPIO_CONFIG";
-        break;
-
-    case SPINEL_PROP_GPIO_STATE:
-        ret = "GPIO_STATE";
-        break;
-
-    case SPINEL_PROP_GPIO_STATE_SET:
-        ret = "GPIO_STATE_SET";
-        break;
-
-    case SPINEL_PROP_GPIO_STATE_CLEAR:
-        ret = "GPIO_STATE_CLEAR";
-        break;
-
-    case SPINEL_PROP_TRNG_32:
-        ret = "TRNG_32";
-        break;
-
-    case SPINEL_PROP_TRNG_128:
-        ret = "TRNG_128";
-        break;
-
-    case SPINEL_PROP_TRNG_RAW_32:
-        ret = "TRNG_RAW_32";
-        break;
-
-    case SPINEL_PROP_UNSOL_UPDATE_FILTER:
-        ret = "UNSOL_UPDATE_FILTER";
-        break;
-
-    case SPINEL_PROP_UNSOL_UPDATE_LIST:
-        ret = "UNSOL_UPDATE_LIST";
-        break;
-
-    case SPINEL_PROP_PHY_ENABLED:
-        ret = "PHY_ENABLED";
-        break;
-
-    case SPINEL_PROP_PHY_CHAN:
-        ret = "PHY_CHAN";
-        break;
-
-    case SPINEL_PROP_PHY_CHAN_SUPPORTED:
-        ret = "PHY_CHAN_SUPPORTED";
-        break;
-
-    case SPINEL_PROP_PHY_FREQ:
-        ret = "PHY_FREQ";
-        break;
-
-    case SPINEL_PROP_PHY_CCA_THRESHOLD:
-        ret = "PHY_CCA_THRESHOLD";
-        break;
-
-    case SPINEL_PROP_PHY_TX_POWER:
-        ret = "PHY_TX_POWER";
-        break;
-
-    case SPINEL_PROP_PHY_FEM_LNA_GAIN:
-        ret = "PHY_FEM_LNA_GAIN";
-        break;
-
-    case SPINEL_PROP_PHY_RSSI:
-        ret = "PHY_RSSI";
-        break;
-
-    case SPINEL_PROP_PHY_RX_SENSITIVITY:
-        ret = "PHY_RX_SENSITIVITY";
-        break;
-
-    case SPINEL_PROP_PHY_PCAP_ENABLED:
-        ret = "PHY_PCAP_ENABLED";
-        break;
-
-    case SPINEL_PROP_PHY_CHAN_PREFERRED:
-        ret = "PHY_CHAN_PREFERRED";
-        break;
-
-    case SPINEL_PROP_PHY_CHAN_MAX_POWER:
-        ret = "PHY_CHAN_MAX_POWER";
-        break;
-
-    case SPINEL_PROP_JAM_DETECT_ENABLE:
-        ret = "JAM_DETECT_ENABLE";
-        break;
-
-    case SPINEL_PROP_JAM_DETECTED:
-        ret = "JAM_DETECTED";
-        break;
-
-    case SPINEL_PROP_JAM_DETECT_RSSI_THRESHOLD:
-        ret = "JAM_DETECT_RSSI_THRESHOLD";
-        break;
-
-    case SPINEL_PROP_JAM_DETECT_WINDOW:
-        ret = "JAM_DETECT_WINDOW";
-        break;
-
-    case SPINEL_PROP_JAM_DETECT_BUSY:
-        ret = "JAM_DETECT_BUSY";
-        break;
-
-    case SPINEL_PROP_JAM_DETECT_HISTORY_BITMAP:
-        ret = "JAM_DETECT_HISTORY_BITMAP";
-        break;
-
-    case SPINEL_PROP_CHANNEL_MONITOR_SAMPLE_INTERVAL:
-        ret = "CHANNEL_MONITOR_SAMPLE_INTERVAL";
-        break;
-
-    case SPINEL_PROP_CHANNEL_MONITOR_RSSI_THRESHOLD:
-        ret = "CHANNEL_MONITOR_RSSI_THRESHOLD";
-        break;
-
-    case SPINEL_PROP_CHANNEL_MONITOR_SAMPLE_WINDOW:
-        ret = "CHANNEL_MONITOR_SAMPLE_WINDOW";
-        break;
-
-    case SPINEL_PROP_CHANNEL_MONITOR_SAMPLE_COUNT:
-        ret = "CHANNEL_MONITOR_SAMPLE_COUNT";
-        break;
-
-    case SPINEL_PROP_CHANNEL_MONITOR_CHANNEL_OCCUPANCY:
-        ret = "CHANNEL_MONITOR_CHANNEL_OCCUPANCY";
-        break;
-
-    case SPINEL_PROP_RADIO_CAPS:
-        ret = "RADIO_CAPS";
-        break;
-
-    case SPINEL_PROP_RADIO_COEX_METRICS:
-        ret = "RADIO_COEX_METRICS";
-        break;
-
-    case SPINEL_PROP_RADIO_COEX_ENABLE:
-        ret = "RADIO_COEX_ENABLE";
-        break;
-
-    case SPINEL_PROP_MAC_SCAN_STATE:
-        ret = "MAC_SCAN_STATE";
-        break;
-
-    case SPINEL_PROP_MAC_SCAN_MASK:
-        ret = "MAC_SCAN_MASK";
-        break;
-
-    case SPINEL_PROP_MAC_SCAN_PERIOD:
-        ret = "MAC_SCAN_PERIOD";
-        break;
-
-    case SPINEL_PROP_MAC_SCAN_BEACON:
-        ret = "MAC_SCAN_BEACON";
-        break;
-
-    case SPINEL_PROP_MAC_15_4_LADDR:
-        ret = "MAC_15_4_LADDR";
-        break;
-
-    case SPINEL_PROP_MAC_15_4_SADDR:
-        ret = "MAC_15_4_SADDR";
-        break;
-
-    case SPINEL_PROP_MAC_15_4_PANID:
-        ret = "MAC_15_4_PANID";
-        break;
-
-    case SPINEL_PROP_MAC_RAW_STREAM_ENABLED:
-        ret = "MAC_RAW_STREAM_ENABLED";
-        break;
-
-    case SPINEL_PROP_MAC_PROMISCUOUS_MODE:
-        ret = "MAC_PROMISCUOUS_MODE";
-        break;
-
-    case SPINEL_PROP_MAC_ENERGY_SCAN_RESULT:
-        ret = "MAC_ENERGY_SCAN_RESULT";
-        break;
-
-    case SPINEL_PROP_MAC_DATA_POLL_PERIOD:
-        ret = "MAC_DATA_POLL_PERIOD";
-        break;
-
-    case SPINEL_PROP_MAC_ALLOWLIST:
-        ret = "MAC_ALLOWLIST";
-        break;
-
-    case SPINEL_PROP_MAC_ALLOWLIST_ENABLED:
-        ret = "MAC_ALLOWLIST_ENABLED";
-        break;
-
-    case SPINEL_PROP_MAC_EXTENDED_ADDR:
-        ret = "MAC_EXTENDED_ADDR";
-        break;
-
-    case SPINEL_PROP_MAC_SRC_MATCH_ENABLED:
-        ret = "MAC_SRC_MATCH_ENABLED";
-        break;
-
-    case SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES:
-        ret = "MAC_SRC_MATCH_SHORT_ADDRESSES";
-        break;
-
-    case SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES:
-        ret = "MAC_SRC_MATCH_EXTENDED_ADDRESSES";
-        break;
-
-    case SPINEL_PROP_MAC_DENYLIST:
-        ret = "MAC_DENYLIST";
-        break;
-
-    case SPINEL_PROP_MAC_DENYLIST_ENABLED:
-        ret = "MAC_DENYLIST_ENABLED";
-        break;
-
-    case SPINEL_PROP_MAC_FIXED_RSS:
-        ret = "MAC_FIXED_RSS";
-        break;
-
-    case SPINEL_PROP_MAC_CCA_FAILURE_RATE:
-        ret = "MAC_CCA_FAILURE_RATE";
-        break;
-
-    case SPINEL_PROP_MAC_MAX_RETRY_NUMBER_DIRECT:
-        ret = "MAC_MAX_RETRY_NUMBER_DIRECT";
-        break;
-
-    case SPINEL_PROP_MAC_MAX_RETRY_NUMBER_INDIRECT:
-        ret = "MAC_MAX_RETRY_NUMBER_INDIRECT";
-        break;
-
-    case SPINEL_PROP_NET_SAVED:
-        ret = "NET_SAVED";
-        break;
-
-    case SPINEL_PROP_NET_IF_UP:
-        ret = "NET_IF_UP";
-        break;
-
-    case SPINEL_PROP_NET_STACK_UP:
-        ret = "NET_STACK_UP";
-        break;
-
-    case SPINEL_PROP_NET_ROLE:
-        ret = "NET_ROLE";
-        break;
-
-    case SPINEL_PROP_NET_NETWORK_NAME:
-        ret = "NET_NETWORK_NAME";
-        break;
-
-    case SPINEL_PROP_NET_XPANID:
-        ret = "NET_XPANID";
-        break;
-
-    case SPINEL_PROP_NET_MASTER_KEY:
-        ret = "NET_MASTER_KEY";
-        break;
-
-    case SPINEL_PROP_NET_KEY_SEQUENCE_COUNTER:
-        ret = "NET_KEY_SEQUENCE_COUNTER";
-        break;
-
-    case SPINEL_PROP_NET_PARTITION_ID:
-        ret = "NET_PARTITION_ID";
-        break;
-
-    case SPINEL_PROP_NET_REQUIRE_JOIN_EXISTING:
-        ret = "NET_REQUIRE_JOIN_EXISTING";
-        break;
-
-    case SPINEL_PROP_NET_KEY_SWITCH_GUARDTIME:
-        ret = "NET_KEY_SWITCH_GUARDTIME";
-        break;
-
-    case SPINEL_PROP_NET_PSKC:
-        ret = "NET_PSKC";
-        break;
-
-    case SPINEL_PROP_THREAD_LEADER_ADDR:
-        ret = "THREAD_LEADER_ADDR";
-        break;
-
-    case SPINEL_PROP_THREAD_PARENT:
-        ret = "THREAD_PARENT";
-        break;
-
-    case SPINEL_PROP_THREAD_CHILD_TABLE:
-        ret = "THREAD_CHILD_TABLE";
-        break;
-
-    case SPINEL_PROP_THREAD_LEADER_RID:
-        ret = "THREAD_LEADER_RID";
-        break;
-
-    case SPINEL_PROP_THREAD_LEADER_WEIGHT:
-        ret = "THREAD_LEADER_WEIGHT";
-        break;
-
-    case SPINEL_PROP_THREAD_LOCAL_LEADER_WEIGHT:
-        ret = "THREAD_LOCAL_LEADER_WEIGHT";
-        break;
-
-    case SPINEL_PROP_THREAD_NETWORK_DATA:
-        ret = "THREAD_NETWORK_DATA";
-        break;
-
-    case SPINEL_PROP_THREAD_NETWORK_DATA_VERSION:
-        ret = "THREAD_NETWORK_DATA_VERSION";
-        break;
-
-    case SPINEL_PROP_THREAD_STABLE_NETWORK_DATA:
-        ret = "THREAD_STABLE_NETWORK_DATA";
-        break;
-
-    case SPINEL_PROP_THREAD_STABLE_NETWORK_DATA_VERSION:
-        ret = "THREAD_STABLE_NETWORK_DATA_VERSION";
-        break;
-
-    case SPINEL_PROP_THREAD_ON_MESH_NETS:
-        ret = "THREAD_ON_MESH_NETS";
-        break;
-
-    case SPINEL_PROP_THREAD_OFF_MESH_ROUTES:
-        ret = "THREAD_OFF_MESH_ROUTES";
-        break;
-
-    case SPINEL_PROP_THREAD_ASSISTING_PORTS:
-        ret = "THREAD_ASSISTING_PORTS";
-        break;
-
-    case SPINEL_PROP_THREAD_ALLOW_LOCAL_NET_DATA_CHANGE:
-        ret = "THREAD_ALLOW_LOCAL_NET_DATA_CHANGE";
-        break;
-
-    case SPINEL_PROP_THREAD_MODE:
-        ret = "THREAD_MODE";
-        break;
-
-    case SPINEL_PROP_THREAD_CHILD_TIMEOUT:
-        ret = "THREAD_CHILD_TIMEOUT";
-        break;
-
-    case SPINEL_PROP_THREAD_RLOC16:
-        ret = "THREAD_RLOC16";
-        break;
-
-    case SPINEL_PROP_THREAD_ROUTER_UPGRADE_THRESHOLD:
-        ret = "THREAD_ROUTER_UPGRADE_THRESHOLD";
-        break;
-
-    case SPINEL_PROP_THREAD_CONTEXT_REUSE_DELAY:
-        ret = "THREAD_CONTEXT_REUSE_DELAY";
-        break;
-
-    case SPINEL_PROP_THREAD_NETWORK_ID_TIMEOUT:
-        ret = "THREAD_NETWORK_ID_TIMEOUT";
-        break;
-
-    case SPINEL_PROP_THREAD_ACTIVE_ROUTER_IDS:
-        ret = "THREAD_ACTIVE_ROUTER_IDS";
-        break;
-
-    case SPINEL_PROP_THREAD_RLOC16_DEBUG_PASSTHRU:
-        ret = "THREAD_RLOC16_DEBUG_PASSTHRU";
-        break;
-
-    case SPINEL_PROP_THREAD_ROUTER_ROLE_ENABLED:
-        ret = "THREAD_ROUTER_ROLE_ENABLED";
-        break;
-
-    case SPINEL_PROP_THREAD_ROUTER_DOWNGRADE_THRESHOLD:
-        ret = "THREAD_ROUTER_DOWNGRADE_THRESHOLD";
-        break;
-
-    case SPINEL_PROP_THREAD_ROUTER_SELECTION_JITTER:
-        ret = "THREAD_ROUTER_SELECTION_JITTER";
-        break;
-
-    case SPINEL_PROP_THREAD_PREFERRED_ROUTER_ID:
-        ret = "THREAD_PREFERRED_ROUTER_ID";
-        break;
-
-    case SPINEL_PROP_THREAD_NEIGHBOR_TABLE:
-        ret = "THREAD_NEIGHBOR_TABLE";
-        break;
-
-    case SPINEL_PROP_THREAD_CHILD_COUNT_MAX:
-        ret = "THREAD_CHILD_COUNT_MAX";
-        break;
-
-    case SPINEL_PROP_THREAD_LEADER_NETWORK_DATA:
-        ret = "THREAD_LEADER_NETWORK_DATA";
-        break;
-
-    case SPINEL_PROP_THREAD_STABLE_LEADER_NETWORK_DATA:
-        ret = "THREAD_STABLE_LEADER_NETWORK_DATA";
-        break;
-
-    case SPINEL_PROP_THREAD_JOINERS:
-        ret = "THREAD_JOINERS";
-        break;
-
-    case SPINEL_PROP_THREAD_COMMISSIONER_ENABLED:
-        ret = "THREAD_COMMISSIONER_ENABLED";
-        break;
-
-    case SPINEL_PROP_THREAD_TMF_PROXY_ENABLED:
-        ret = "THREAD_TMF_PROXY_ENABLED";
-        break;
-
-    case SPINEL_PROP_THREAD_TMF_PROXY_STREAM:
-        ret = "THREAD_TMF_PROXY_STREAM";
-        break;
-
-    case SPINEL_PROP_THREAD_UDP_FORWARD_STREAM:
-        ret = "THREAD_UDP_FORWARD_STREAM";
-        break;
-
-    case SPINEL_PROP_THREAD_DISCOVERY_SCAN_JOINER_FLAG:
-        ret = "THREAD_DISCOVERY_SCAN_JOINER_FLAG";
-        break;
-
-    case SPINEL_PROP_THREAD_DISCOVERY_SCAN_ENABLE_FILTERING:
-        ret = "THREAD_DISCOVERY_SCAN_ENABLE_FILTERING";
-        break;
-
-    case SPINEL_PROP_THREAD_DISCOVERY_SCAN_PANID:
-        ret = "THREAD_DISCOVERY_SCAN_PANID";
-        break;
-
-    case SPINEL_PROP_THREAD_STEERING_DATA:
-        ret = "THREAD_STEERING_DATA";
-        break;
-
-    case SPINEL_PROP_THREAD_ROUTER_TABLE:
-        ret = "THREAD_ROUTER_TABLE";
-        break;
-
-    case SPINEL_PROP_THREAD_ACTIVE_DATASET:
-        ret = "THREAD_ACTIVE_DATASET";
-        break;
-
-    case SPINEL_PROP_THREAD_PENDING_DATASET:
-        ret = "THREAD_PENDING_DATASET";
-        break;
-
-    case SPINEL_PROP_THREAD_MGMT_SET_ACTIVE_DATASET:
-        ret = "THREAD_MGMT_SET_ACTIVE_DATASET";
-        break;
-
-    case SPINEL_PROP_THREAD_MGMT_SET_PENDING_DATASET:
-        ret = "THREAD_MGMT_SET_PENDING_DATASET";
-        break;
-
-    case SPINEL_PROP_DATASET_ACTIVE_TIMESTAMP:
-        ret = "DATASET_ACTIVE_TIMESTAMP";
-        break;
-
-    case SPINEL_PROP_DATASET_PENDING_TIMESTAMP:
-        ret = "DATASET_PENDING_TIMESTAMP";
-        break;
-
-    case SPINEL_PROP_DATASET_DELAY_TIMER:
-        ret = "DATASET_DELAY_TIMER";
-        break;
-
-    case SPINEL_PROP_DATASET_SECURITY_POLICY:
-        ret = "DATASET_SECURITY_POLICY";
-        break;
-
-    case SPINEL_PROP_DATASET_RAW_TLVS:
-        ret = "DATASET_RAW_TLVS";
-        break;
-
-    case SPINEL_PROP_THREAD_CHILD_TABLE_ADDRESSES:
-        ret = "THREAD_CHILD_TABLE_ADDRESSES";
-        break;
-
-    case SPINEL_PROP_THREAD_NEIGHBOR_TABLE_ERROR_RATES:
-        ret = "THREAD_NEIGHBOR_TABLE_ERROR_RATES";
-        break;
-
-    case SPINEL_PROP_THREAD_ADDRESS_CACHE_TABLE:
-        ret = "THREAD_ADDRESS_CACHE_TABLE";
-        break;
-
-    case SPINEL_PROP_THREAD_MGMT_GET_ACTIVE_DATASET:
-        ret = "THREAD_MGMT_GET_ACTIVE_DATASET";
-        break;
-
-    case SPINEL_PROP_THREAD_MGMT_GET_PENDING_DATASET:
-        ret = "THREAD_MGMT_GET_PENDING_DATASET";
-        break;
-
-    case SPINEL_PROP_DATASET_DEST_ADDRESS:
-        ret = "DATASET_DEST_ADDRESS";
-        break;
-
-    case SPINEL_PROP_THREAD_NEW_DATASET:
-        ret = "THREAD_NEW_DATASET";
-        break;
-
-    case SPINEL_PROP_MESHCOP_JOINER_STATE:
-        ret = "MESHCOP_JOINER_STATE";
-        break;
-
-    case SPINEL_PROP_MESHCOP_JOINER_COMMISSIONING:
-        ret = "MESHCOP_JOINER_COMMISSIONING";
-        break;
-
-    case SPINEL_PROP_IPV6_LL_ADDR:
-        ret = "IPV6_LL_ADDR";
-        break;
-
-    case SPINEL_PROP_IPV6_ML_ADDR:
-        ret = "IPV6_ML_ADDR";
-        break;
-
-    case SPINEL_PROP_IPV6_ML_PREFIX:
-        ret = "IPV6_ML_PREFIX";
-        break;
-
-    case SPINEL_PROP_IPV6_ADDRESS_TABLE:
-        ret = "IPV6_ADDRESS_TABLE";
-        break;
-
-    case SPINEL_PROP_IPV6_ROUTE_TABLE:
-        ret = "IPV6_ROUTE_TABLE";
-        break;
-
-    case SPINEL_PROP_IPV6_ICMP_PING_OFFLOAD:
-        ret = "IPV6_ICMP_PING_OFFLOAD";
-        break;
-
-    case SPINEL_PROP_IPV6_MULTICAST_ADDRESS_TABLE:
-        ret = "IPV6_MULTICAST_ADDRESS_TABLE";
-        break;
-
-    case SPINEL_PROP_IPV6_ICMP_PING_OFFLOAD_MODE:
-        ret = "IPV6_ICMP_PING_OFFLOAD_MODE";
-        break;
-
-    case SPINEL_PROP_STREAM_DEBUG:
-        ret = "STREAM_DEBUG";
-        break;
-
-    case SPINEL_PROP_STREAM_RAW:
-        ret = "STREAM_RAW";
-        break;
-
-    case SPINEL_PROP_STREAM_NET:
-        ret = "STREAM_NET";
-        break;
-
-    case SPINEL_PROP_STREAM_NET_INSECURE:
-        ret = "STREAM_NET_INSECURE";
-        break;
-
-    case SPINEL_PROP_STREAM_LOG:
-        ret = "STREAM_LOG";
-        break;
-
-    case SPINEL_PROP_MESHCOP_COMMISSIONER_STATE:
-        ret = "MESHCOP_COMMISSIONER_STATE";
-        break;
-
-    case SPINEL_PROP_MESHCOP_COMMISSIONER_JOINERS:
-        ret = "MESHCOP_COMMISSIONER_JOINERS";
-        break;
-
-    case SPINEL_PROP_MESHCOP_COMMISSIONER_PROVISIONING_URL:
-        ret = "MESHCOP_COMMISSIONER_PROVISIONING_URL";
-        break;
-
-    case SPINEL_PROP_MESHCOP_COMMISSIONER_SESSION_ID:
-        ret = "MESHCOP_COMMISSIONER_SESSION_ID";
-        break;
-
-    case SPINEL_PROP_MESHCOP_JOINER_DISCERNER:
-        ret = "MESHCOP_JOINER_DISCERNER";
-        break;
-
-    case SPINEL_PROP_MESHCOP_COMMISSIONER_ANNOUNCE_BEGIN:
-        ret = "MESHCOP_COMMISSIONER_ANNOUNCE_BEGIN";
-        break;
-
-    case SPINEL_PROP_MESHCOP_COMMISSIONER_ENERGY_SCAN:
-        ret = "MESHCOP_COMMISSIONER_ENERGY_SCAN";
-        break;
-
-    case SPINEL_PROP_MESHCOP_COMMISSIONER_ENERGY_SCAN_RESULT:
-        ret = "MESHCOP_COMMISSIONER_ENERGY_SCAN_RESULT";
-        break;
-
-    case SPINEL_PROP_MESHCOP_COMMISSIONER_PAN_ID_QUERY:
-        ret = "MESHCOP_COMMISSIONER_PAN_ID_QUERY";
-        break;
-
-    case SPINEL_PROP_MESHCOP_COMMISSIONER_PAN_ID_CONFLICT_RESULT:
-        ret = "MESHCOP_COMMISSIONER_PAN_ID_CONFLICT_RESULT";
-        break;
-
-    case SPINEL_PROP_MESHCOP_COMMISSIONER_MGMT_GET:
-        ret = "MESHCOP_COMMISSIONER_MGMT_GET";
-        break;
-
-    case SPINEL_PROP_MESHCOP_COMMISSIONER_MGMT_SET:
-        ret = "MESHCOP_COMMISSIONER_MGMT_SET";
-        break;
-
-    case SPINEL_PROP_MESHCOP_COMMISSIONER_GENERATE_PSKC:
-        ret = "MESHCOP_COMMISSIONER_GENERATE_PSKC";
-        break;
-
-    case SPINEL_PROP_CHANNEL_MANAGER_NEW_CHANNEL:
-        ret = "CHANNEL_MANAGER_NEW_CHANNEL";
-        break;
-
-    case SPINEL_PROP_CHANNEL_MANAGER_DELAY:
-        ret = "CHANNEL_MANAGER_DELAY";
-        break;
-
-    case SPINEL_PROP_CHANNEL_MANAGER_SUPPORTED_CHANNELS:
-        ret = "CHANNEL_MANAGER_SUPPORTED_CHANNELS";
-        break;
-
-    case SPINEL_PROP_CHANNEL_MANAGER_FAVORED_CHANNELS:
-        ret = "CHANNEL_MANAGER_FAVORED_CHANNELS";
-        break;
-
-    case SPINEL_PROP_CHANNEL_MANAGER_CHANNEL_SELECT:
-        ret = "CHANNEL_MANAGER_CHANNEL_SELECT";
-        break;
-
-    case SPINEL_PROP_CHANNEL_MANAGER_AUTO_SELECT_ENABLED:
-        ret = "CHANNEL_MANAGER_AUTO_SELECT_ENABLED";
-        break;
-
-    case SPINEL_PROP_CHANNEL_MANAGER_AUTO_SELECT_INTERVAL:
-        ret = "CHANNEL_MANAGER_AUTO_SELECT_INTERVAL";
-        break;
-
-    case SPINEL_PROP_THREAD_NETWORK_TIME:
-        ret = "THREAD_NETWORK_TIME";
-        break;
-
-    case SPINEL_PROP_TIME_SYNC_PERIOD:
-        ret = "TIME_SYNC_PERIOD";
-        break;
-
-    case SPINEL_PROP_TIME_SYNC_XTAL_THRESHOLD:
-        ret = "TIME_SYNC_XTAL_THRESHOLD";
-        break;
-
-    case SPINEL_PROP_CHILD_SUPERVISION_INTERVAL:
-        ret = "CHILD_SUPERVISION_INTERVAL";
-        break;
-
-    case SPINEL_PROP_CHILD_SUPERVISION_CHECK_TIMEOUT:
-        ret = "CHILD_SUPERVISION_CHECK_TIMEOUT";
-        break;
-
-    case SPINEL_PROP_RCP_VERSION:
-        ret = "RCP_VERSION";
-        break;
-
-    case SPINEL_PROP_PARENT_RESPONSE_INFO:
-        ret = "PARENT_RESPONSE_INFO";
-        break;
-
-    case SPINEL_PROP_SLAAC_ENABLED:
-        ret = "SLAAC_ENABLED";
-        break;
-
-    case SPINEL_PROP_SUPPORTED_RADIO_LINKS:
-        ret = "SUPPORTED_RADIO_LINKS";
-        break;
-
-    case SPINEL_PROP_NEIGHBOR_TABLE_MULTI_RADIO_INFO:
-        ret = "NEIGHBOR_TABLE_MULTI_RADIO_INFO";
-        break;
-
-    case SPINEL_PROP_SERVER_ALLOW_LOCAL_DATA_CHANGE:
-        ret = "SERVER_ALLOW_LOCAL_DATA_CHANGE";
-        break;
-
-    case SPINEL_PROP_SERVER_SERVICES:
-        ret = "SERVER_SERVICES";
-        break;
-
-    case SPINEL_PROP_SERVER_LEADER_SERVICES:
-        ret = "SERVER_LEADER_SERVICES";
-        break;
-
-    case SPINEL_PROP_RCP_API_VERSION:
-        ret = "RCP_API_VERSION";
-        break;
-
-    case SPINEL_PROP_UART_BITRATE:
-        ret = "UART_BITRATE";
-        break;
-
-    case SPINEL_PROP_UART_XON_XOFF:
-        ret = "UART_XON_XOFF";
-        break;
-
-    case SPINEL_PROP_15_4_PIB_PHY_CHANNELS_SUPPORTED:
-        ret = "15_4_PIB_PHY_CHANNELS_SUPPORTED";
-        break;
-
-    case SPINEL_PROP_15_4_PIB_MAC_PROMISCUOUS_MODE:
-        ret = "15_4_PIB_MAC_PROMISCUOUS_MODE";
-        break;
-
-    case SPINEL_PROP_15_4_PIB_MAC_SECURITY_ENABLED:
-        ret = "15_4_PIB_MAC_SECURITY_ENABLED";
-        break;
-
-    case SPINEL_PROP_CNTR_RESET:
-        ret = "CNTR_RESET";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_PKT_TOTAL:
-        ret = "CNTR_TX_PKT_TOTAL";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_PKT_ACK_REQ:
-        ret = "CNTR_TX_PKT_ACK_REQ";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_PKT_ACKED:
-        ret = "CNTR_TX_PKT_ACKED";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_PKT_NO_ACK_REQ:
-        ret = "CNTR_TX_PKT_NO_ACK_REQ";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_PKT_DATA:
-        ret = "CNTR_TX_PKT_DATA";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_PKT_DATA_POLL:
-        ret = "CNTR_TX_PKT_DATA_POLL";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_PKT_BEACON:
-        ret = "CNTR_TX_PKT_BEACON";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_PKT_BEACON_REQ:
-        ret = "CNTR_TX_PKT_BEACON_REQ";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_PKT_OTHER:
-        ret = "CNTR_TX_PKT_OTHER";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_PKT_RETRY:
-        ret = "CNTR_TX_PKT_RETRY";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_ERR_CCA:
-        ret = "CNTR_TX_ERR_CCA";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_PKT_UNICAST:
-        ret = "CNTR_TX_PKT_UNICAST";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_PKT_BROADCAST:
-        ret = "CNTR_TX_PKT_BROADCAST";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_ERR_ABORT:
-        ret = "CNTR_TX_ERR_ABORT";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_PKT_TOTAL:
-        ret = "CNTR_RX_PKT_TOTAL";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_PKT_DATA:
-        ret = "CNTR_RX_PKT_DATA";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_PKT_DATA_POLL:
-        ret = "CNTR_RX_PKT_DATA_POLL";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_PKT_BEACON:
-        ret = "CNTR_RX_PKT_BEACON";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_PKT_BEACON_REQ:
-        ret = "CNTR_RX_PKT_BEACON_REQ";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_PKT_OTHER:
-        ret = "CNTR_RX_PKT_OTHER";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_PKT_FILT_WL:
-        ret = "CNTR_RX_PKT_FILT_WL";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_PKT_FILT_DA:
-        ret = "CNTR_RX_PKT_FILT_DA";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_ERR_EMPTY:
-        ret = "CNTR_RX_ERR_EMPTY";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_ERR_UKWN_NBR:
-        ret = "CNTR_RX_ERR_UKWN_NBR";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_ERR_NVLD_SADDR:
-        ret = "CNTR_RX_ERR_NVLD_SADDR";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_ERR_SECURITY:
-        ret = "CNTR_RX_ERR_SECURITY";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_ERR_BAD_FCS:
-        ret = "CNTR_RX_ERR_BAD_FCS";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_ERR_OTHER:
-        ret = "CNTR_RX_ERR_OTHER";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_PKT_DUP:
-        ret = "CNTR_RX_PKT_DUP";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_PKT_UNICAST:
-        ret = "CNTR_RX_PKT_UNICAST";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_PKT_BROADCAST:
-        ret = "CNTR_RX_PKT_BROADCAST";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_IP_SEC_TOTAL:
-        ret = "CNTR_TX_IP_SEC_TOTAL";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_IP_INSEC_TOTAL:
-        ret = "CNTR_TX_IP_INSEC_TOTAL";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_IP_DROPPED:
-        ret = "CNTR_TX_IP_DROPPED";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_IP_SEC_TOTAL:
-        ret = "CNTR_RX_IP_SEC_TOTAL";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_IP_INSEC_TOTAL:
-        ret = "CNTR_RX_IP_INSEC_TOTAL";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_IP_DROPPED:
-        ret = "CNTR_RX_IP_DROPPED";
-        break;
-
-    case SPINEL_PROP_CNTR_TX_SPINEL_TOTAL:
-        ret = "CNTR_TX_SPINEL_TOTAL";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_SPINEL_TOTAL:
-        ret = "CNTR_RX_SPINEL_TOTAL";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_SPINEL_ERR:
-        ret = "CNTR_RX_SPINEL_ERR";
-        break;
-
-    case SPINEL_PROP_CNTR_RX_SPINEL_OUT_OF_ORDER_TID:
-        ret = "CNTR_RX_SPINEL_OUT_OF_ORDER_TID";
-        break;
-
-    case SPINEL_PROP_CNTR_IP_TX_SUCCESS:
-        ret = "CNTR_IP_TX_SUCCESS";
-        break;
-
-    case SPINEL_PROP_CNTR_IP_RX_SUCCESS:
-        ret = "CNTR_IP_RX_SUCCESS";
-        break;
-
-    case SPINEL_PROP_CNTR_IP_TX_FAILURE:
-        ret = "CNTR_IP_TX_FAILURE";
-        break;
-
-    case SPINEL_PROP_CNTR_IP_RX_FAILURE:
-        ret = "CNTR_IP_RX_FAILURE";
-        break;
-
-    case SPINEL_PROP_MSG_BUFFER_COUNTERS:
-        ret = "MSG_BUFFER_COUNTERS";
-        break;
-
-    case SPINEL_PROP_CNTR_ALL_MAC_COUNTERS:
-        ret = "CNTR_ALL_MAC_COUNTERS";
-        break;
-
-    case SPINEL_PROP_CNTR_MLE_COUNTERS:
-        ret = "CNTR_MLE_COUNTERS";
-        break;
-
-    case SPINEL_PROP_CNTR_ALL_IP_COUNTERS:
-        ret = "CNTR_ALL_IP_COUNTERS";
-        break;
-
-    case SPINEL_PROP_CNTR_MAC_RETRY_HISTOGRAM:
-        ret = "CNTR_MAC_RETRY_HISTOGRAM";
-        break;
-
-    case SPINEL_PROP_NEST_STREAM_MFG:
-        ret = "NEST_STREAM_MFG";
-        break;
-
-    case SPINEL_PROP_NEST_LEGACY_ULA_PREFIX:
-        ret = "NEST_LEGACY_ULA_PREFIX";
-        break;
-
-    case SPINEL_PROP_NEST_LEGACY_LAST_NODE_JOINED:
-        ret = "NEST_LEGACY_LAST_NODE_JOINED";
-        break;
-
-    case SPINEL_PROP_DEBUG_TEST_ASSERT:
-        ret = "DEBUG_TEST_ASSERT";
-        break;
-
-    case SPINEL_PROP_DEBUG_NCP_LOG_LEVEL:
-        ret = "DEBUG_NCP_LOG_LEVEL";
-        break;
-
-    case SPINEL_PROP_DEBUG_TEST_WATCHDOG:
-        ret = "DEBUG_TEST_WATCHDOG";
-        break;
-
-    case SPINEL_PROP_RCP_MAC_FRAME_COUNTER:
-        ret = "RCP_MAC_FRAME_COUNTER";
-        break;
-
-    case SPINEL_PROP_RCP_MAC_KEY:
-        ret = "RCP_MAC_KEY";
-        break;
-
-    case SPINEL_PROP_DEBUG_LOG_TIMESTAMP_BASE:
-        ret = "DEBUG_LOG_TIMESTAMP_BASE";
-        break;
-
-    case SPINEL_PROP_DEBUG_TREL_TEST_MODE_ENABLE:
-        ret = "DEBUG_TREL_TEST_MODE_ENABLE";
-        break;
-
-    default:
-        break;
-    }
-
-    return ret;
+    return spinel_to_cstr(spinel_prop_cstr, prop_key);
 }
 
+#define SPINEL_NET_CSTR(VALUE) __SPINEL_CSTR(SPINEL_, VALUE)
 const char *spinel_net_role_to_cstr(uint8_t net_role)
 {
-    const char *ret = "NET_ROLE_UNKNOWN";
+    static const struct spinel_cstr spinel_net_cstr[] = {
+        SPINEL_NET_CSTR(NET_ROLE_DETACHED),
+        SPINEL_NET_CSTR(NET_ROLE_CHILD),
+        SPINEL_NET_CSTR(NET_ROLE_ROUTER),
+        SPINEL_NET_CSTR(NET_ROLE_LEADER),
+        {0},
+    };
 
-    switch (net_role)
-    {
-    case SPINEL_NET_ROLE_DETACHED:
-        ret = "NET_ROLE_DETACHED";
-        break;
-
-    case SPINEL_NET_ROLE_CHILD:
-        ret = "NET_ROLE_CHILD";
-        break;
-
-    case SPINEL_NET_ROLE_ROUTER:
-        ret = "NET_ROLE_ROUTER";
-        break;
-
-    case SPINEL_NET_ROLE_LEADER:
-        ret = "NET_ROLE_LEADER";
-        break;
-
-    default:
-        break;
-    }
-
-    return ret;
+    return spinel_to_cstr(spinel_net_cstr, net_role);
 }
 
+#define SPINEL_MCU_CSTR(VALUE) __SPINEL_CSTR(SPINEL_, VALUE)
 const char *spinel_mcu_power_state_to_cstr(uint8_t mcu_power_state)
 {
-    const char *ret = "MCU_POWER_STATE_UNKNOWN";
+    static const struct spinel_cstr spinel_mcu_power_state_cstr[] = {
+        SPINEL_MCU_CSTR(MCU_POWER_STATE_ON),
+        SPINEL_MCU_CSTR(MCU_POWER_STATE_LOW_POWER),
+        SPINEL_MCU_CSTR(MCU_POWER_STATE_OFF),
+        {0},
+    };
 
-    switch (mcu_power_state)
-    {
-    case SPINEL_MCU_POWER_STATE_ON:
-        ret = "MCU_POWER_STATE_ON";
-        break;
-
-    case SPINEL_MCU_POWER_STATE_LOW_POWER:
-        ret = "MCU_POWER_STATE_LOW_POWER";
-        break;
-
-    case SPINEL_MCU_POWER_STATE_OFF:
-        ret = "MCU_POWER_STATE_OFF";
-        break;
-
-    default:
-        break;
-    }
-
-    return ret;
+    return spinel_to_cstr(spinel_mcu_power_state_cstr, mcu_power_state);
 }
 
+#define SPINEL_STATUS_CSTR(VALUE) __SPINEL_CSTR(SPINEL_STATUS_, VALUE)
 const char *spinel_status_to_cstr(spinel_status_t status)
 {
-    const char *ret = "UNKNOWN";
+    static const struct spinel_cstr spinel_status_cstr[] = {
+        SPINEL_STATUS_CSTR(OK),
+        SPINEL_STATUS_CSTR(FAILURE),
+        SPINEL_STATUS_CSTR(UNIMPLEMENTED),
+        SPINEL_STATUS_CSTR(INVALID_ARGUMENT),
+        SPINEL_STATUS_CSTR(INVALID_STATE),
+        SPINEL_STATUS_CSTR(INVALID_COMMAND),
+        SPINEL_STATUS_CSTR(INVALID_INTERFACE),
+        SPINEL_STATUS_CSTR(INTERNAL_ERROR),
+        SPINEL_STATUS_CSTR(SECURITY_ERROR),
+        SPINEL_STATUS_CSTR(PARSE_ERROR),
+        SPINEL_STATUS_CSTR(IN_PROGRESS),
+        SPINEL_STATUS_CSTR(NOMEM),
+        SPINEL_STATUS_CSTR(BUSY),
+        SPINEL_STATUS_CSTR(PROP_NOT_FOUND),
+        SPINEL_STATUS_CSTR(DROPPED),
+        SPINEL_STATUS_CSTR(EMPTY),
+        SPINEL_STATUS_CSTR(CMD_TOO_BIG),
+        SPINEL_STATUS_CSTR(NO_ACK),
+        SPINEL_STATUS_CSTR(CCA_FAILURE),
+        SPINEL_STATUS_CSTR(ALREADY),
+        SPINEL_STATUS_CSTR(ITEM_NOT_FOUND),
+        SPINEL_STATUS_CSTR(INVALID_COMMAND_FOR_PROP),
+        SPINEL_STATUS_CSTR(RESPONSE_TIMEOUT),
+        SPINEL_STATUS_CSTR(JOIN_FAILURE),
+        SPINEL_STATUS_CSTR(JOIN_SECURITY),
+        SPINEL_STATUS_CSTR(JOIN_NO_PEERS),
+        SPINEL_STATUS_CSTR(JOIN_INCOMPATIBLE),
+        SPINEL_STATUS_CSTR(JOIN_RSP_TIMEOUT),
+        SPINEL_STATUS_CSTR(JOIN_SUCCESS),
+        SPINEL_STATUS_CSTR(RESET_POWER_ON),
+        SPINEL_STATUS_CSTR(RESET_EXTERNAL),
+        SPINEL_STATUS_CSTR(RESET_SOFTWARE),
+        SPINEL_STATUS_CSTR(RESET_FAULT),
+        SPINEL_STATUS_CSTR(RESET_CRASH),
+        SPINEL_STATUS_CSTR(RESET_ASSERT),
+        SPINEL_STATUS_CSTR(RESET_OTHER),
+        SPINEL_STATUS_CSTR(RESET_UNKNOWN),
+        SPINEL_STATUS_CSTR(RESET_WATCHDOG),
+        {0},
+    };
 
-    switch (status)
-    {
-    case SPINEL_STATUS_OK:
-        ret = "OK";
-        break;
-
-    case SPINEL_STATUS_FAILURE:
-        ret = "FAILURE";
-        break;
-
-    case SPINEL_STATUS_UNIMPLEMENTED:
-        ret = "UNIMPLEMENTED";
-        break;
-
-    case SPINEL_STATUS_INVALID_ARGUMENT:
-        ret = "INVALID_ARGUMENT";
-        break;
-
-    case SPINEL_STATUS_INVALID_STATE:
-        ret = "INVALID_STATE";
-        break;
-
-    case SPINEL_STATUS_INVALID_COMMAND:
-        ret = "INVALID_COMMAND";
-        break;
-
-    case SPINEL_STATUS_INVALID_INTERFACE:
-        ret = "INVALID_INTERFACE";
-        break;
-
-    case SPINEL_STATUS_INTERNAL_ERROR:
-        ret = "INTERNAL_ERROR";
-        break;
-
-    case SPINEL_STATUS_SECURITY_ERROR:
-        ret = "SECURITY_ERROR";
-        break;
-
-    case SPINEL_STATUS_PARSE_ERROR:
-        ret = "PARSE_ERROR";
-        break;
-
-    case SPINEL_STATUS_IN_PROGRESS:
-        ret = "IN_PROGRESS";
-        break;
-
-    case SPINEL_STATUS_NOMEM:
-        ret = "NOMEM";
-        break;
-
-    case SPINEL_STATUS_BUSY:
-        ret = "BUSY";
-        break;
-
-    case SPINEL_STATUS_PROP_NOT_FOUND:
-        ret = "PROP_NOT_FOUND";
-        break;
-
-    case SPINEL_STATUS_DROPPED:
-        ret = "DROPPED";
-        break;
-
-    case SPINEL_STATUS_EMPTY:
-        ret = "EMPTY";
-        break;
-
-    case SPINEL_STATUS_CMD_TOO_BIG:
-        ret = "CMD_TOO_BIG";
-        break;
-
-    case SPINEL_STATUS_NO_ACK:
-        ret = "NO_ACK";
-        break;
-
-    case SPINEL_STATUS_CCA_FAILURE:
-        ret = "CCA_FAILURE";
-        break;
-
-    case SPINEL_STATUS_ALREADY:
-        ret = "ALREADY";
-        break;
-
-    case SPINEL_STATUS_ITEM_NOT_FOUND:
-        ret = "ITEM_NOT_FOUND";
-        break;
-
-    case SPINEL_STATUS_INVALID_COMMAND_FOR_PROP:
-        ret = "INVALID_COMMAND_FOR_PROP";
-        break;
-
-    case SPINEL_STATUS_JOIN_FAILURE:
-        ret = "JOIN_FAILURE";
-        break;
-
-    case SPINEL_STATUS_JOIN_SECURITY:
-        ret = "JOIN_SECURITY";
-        break;
-
-    case SPINEL_STATUS_JOIN_NO_PEERS:
-        ret = "JOIN_NO_PEERS";
-        break;
-
-    case SPINEL_STATUS_JOIN_INCOMPATIBLE:
-        ret = "JOIN_INCOMPATIBLE";
-        break;
-
-    case SPINEL_STATUS_JOIN_RSP_TIMEOUT:
-        ret = "JOIN_RSP_TIMEOUT";
-        break;
-
-    case SPINEL_STATUS_JOIN_SUCCESS:
-        ret = "JOIN_SUCCESS";
-        break;
-
-    case SPINEL_STATUS_RESET_POWER_ON:
-        ret = "RESET_POWER_ON";
-        break;
-
-    case SPINEL_STATUS_RESET_EXTERNAL:
-        ret = "RESET_EXTERNAL";
-        break;
-
-    case SPINEL_STATUS_RESET_SOFTWARE:
-        ret = "RESET_SOFTWARE";
-        break;
-
-    case SPINEL_STATUS_RESET_FAULT:
-        ret = "RESET_FAULT";
-        break;
-
-    case SPINEL_STATUS_RESET_CRASH:
-        ret = "RESET_CRASH";
-        break;
-
-    case SPINEL_STATUS_RESET_ASSERT:
-        ret = "RESET_ASSERT";
-        break;
-
-    case SPINEL_STATUS_RESET_OTHER:
-        ret = "RESET_OTHER";
-        break;
-
-    case SPINEL_STATUS_RESET_UNKNOWN:
-        ret = "RESET_UNKNOWN";
-        break;
-
-    case SPINEL_STATUS_RESET_WATCHDOG:
-        ret = "RESET_WATCHDOG";
-        break;
-
-    default:
-        break;
-    }
-
-    return ret;
+    return spinel_to_cstr(spinel_status_cstr, status);
 }
 
+#define SPINEL_CAP_CSTR(VALUE) __SPINEL_CSTR(SPINEL_CAP_, VALUE)
 const char *spinel_capability_to_cstr(spinel_capability_t capability)
 {
-    const char *ret = "UNKNOWN";
+    static const struct spinel_cstr spinel_cap_cstr[] = {
+        SPINEL_CAP_CSTR(LOCK),
+        SPINEL_CAP_CSTR(NET_SAVE),
+        SPINEL_CAP_CSTR(HBO),
+        SPINEL_CAP_CSTR(POWER_SAVE),
+        SPINEL_CAP_CSTR(COUNTERS),
+        SPINEL_CAP_CSTR(JAM_DETECT),
+        SPINEL_CAP_CSTR(PEEK_POKE),
+        SPINEL_CAP_CSTR(WRITABLE_RAW_STREAM),
+        SPINEL_CAP_CSTR(GPIO),
+        SPINEL_CAP_CSTR(TRNG),
+        SPINEL_CAP_CSTR(CMD_MULTI),
+        SPINEL_CAP_CSTR(UNSOL_UPDATE_FILTER),
+        SPINEL_CAP_CSTR(MCU_POWER_STATE),
+        SPINEL_CAP_CSTR(PCAP),
+        SPINEL_CAP_CSTR(802_15_4_2003),
+        SPINEL_CAP_CSTR(802_15_4_2006),
+        SPINEL_CAP_CSTR(802_15_4_2011),
+        SPINEL_CAP_CSTR(802_15_4_PIB),
+        SPINEL_CAP_CSTR(802_15_4_2450MHZ_OQPSK),
+        SPINEL_CAP_CSTR(802_15_4_915MHZ_OQPSK),
+        SPINEL_CAP_CSTR(802_15_4_868MHZ_OQPSK),
+        SPINEL_CAP_CSTR(802_15_4_915MHZ_BPSK),
+        SPINEL_CAP_CSTR(802_15_4_868MHZ_BPSK),
+        SPINEL_CAP_CSTR(802_15_4_915MHZ_ASK),
+        SPINEL_CAP_CSTR(802_15_4_868MHZ_ASK),
+        SPINEL_CAP_CSTR(CONFIG_FTD),
+        SPINEL_CAP_CSTR(CONFIG_MTD),
+        SPINEL_CAP_CSTR(CONFIG_RADIO),
+        SPINEL_CAP_CSTR(ROLE_ROUTER),
+        SPINEL_CAP_CSTR(ROLE_SLEEPY),
+        SPINEL_CAP_CSTR(NET_THREAD_1_0),
+        SPINEL_CAP_CSTR(NET_THREAD_1_1),
+        SPINEL_CAP_CSTR(NET_THREAD_1_2),
+        SPINEL_CAP_CSTR(RCP_API_VERSION),
+        SPINEL_CAP_CSTR(MAC_ALLOWLIST),
+        SPINEL_CAP_CSTR(MAC_RAW),
+        SPINEL_CAP_CSTR(OOB_STEERING_DATA),
+        SPINEL_CAP_CSTR(CHANNEL_MONITOR),
+        SPINEL_CAP_CSTR(CHANNEL_MANAGER),
+        SPINEL_CAP_CSTR(OPENTHREAD_LOG_METADATA),
+        SPINEL_CAP_CSTR(TIME_SYNC),
+        SPINEL_CAP_CSTR(CHILD_SUPERVISION),
+        SPINEL_CAP_CSTR(POSIX),
+        SPINEL_CAP_CSTR(SLAAC),
+        SPINEL_CAP_CSTR(RADIO_COEX),
+        SPINEL_CAP_CSTR(MAC_RETRY_HISTOGRAM),
+        SPINEL_CAP_CSTR(MULTI_RADIO),
+        SPINEL_CAP_CSTR(SRP_CLIENT),
+        SPINEL_CAP_CSTR(ERROR_RATE_TRACKING),
+        SPINEL_CAP_CSTR(THREAD_COMMISSIONER),
+        SPINEL_CAP_CSTR(THREAD_TMF_PROXY),
+        SPINEL_CAP_CSTR(THREAD_UDP_FORWARD),
+        SPINEL_CAP_CSTR(THREAD_JOINER),
+        SPINEL_CAP_CSTR(THREAD_BORDER_ROUTER),
+        SPINEL_CAP_CSTR(THREAD_SERVICE),
+        SPINEL_CAP_CSTR(THREAD_CSL_RECEIVER),
+        SPINEL_CAP_CSTR(THREAD_BACKBONE_ROUTER),
+        SPINEL_CAP_CSTR(NEST_LEGACY_INTERFACE),
+        SPINEL_CAP_CSTR(NEST_LEGACY_NET_WAKE),
+        SPINEL_CAP_CSTR(NEST_TRANSMIT_HOOK),
+        {0},
+    };
 
-    switch (capability)
-    {
-    case SPINEL_CAP_LOCK:
-        ret = "LOCK";
-        break;
-
-    case SPINEL_CAP_NET_SAVE:
-        ret = "NET_SAVE";
-        break;
-
-    case SPINEL_CAP_HBO:
-        ret = "HBO";
-        break;
-
-    case SPINEL_CAP_POWER_SAVE:
-        ret = "POWER_SAVE";
-        break;
-
-    case SPINEL_CAP_COUNTERS:
-        ret = "COUNTERS";
-        break;
-
-    case SPINEL_CAP_JAM_DETECT:
-        ret = "JAM_DETECT";
-        break;
-
-    case SPINEL_CAP_PEEK_POKE:
-        ret = "PEEK_POKE";
-        break;
-
-    case SPINEL_CAP_WRITABLE_RAW_STREAM:
-        ret = "WRITABLE_RAW_STREAM";
-        break;
-
-    case SPINEL_CAP_GPIO:
-        ret = "GPIO";
-        break;
-
-    case SPINEL_CAP_TRNG:
-        ret = "TRNG";
-        break;
-
-    case SPINEL_CAP_CMD_MULTI:
-        ret = "CMD_MULTI";
-        break;
-
-    case SPINEL_CAP_UNSOL_UPDATE_FILTER:
-        ret = "UNSOL_UPDATE_FILTER";
-        break;
-
-    case SPINEL_CAP_MCU_POWER_STATE:
-        ret = "MCU_POWER_STATE";
-        break;
-
-    case SPINEL_CAP_PCAP:
-        ret = "PCAP";
-        break;
-
-    case SPINEL_CAP_802_15_4_2003:
-        ret = "802_15_4_2003";
-        break;
-
-    case SPINEL_CAP_802_15_4_2006:
-        ret = "802_15_4_2006";
-        break;
-
-    case SPINEL_CAP_802_15_4_2011:
-        ret = "802_15_4_2011";
-        break;
-
-    case SPINEL_CAP_802_15_4_PIB:
-        ret = "802_15_4_PIB";
-        break;
-
-    case SPINEL_CAP_802_15_4_2450MHZ_OQPSK:
-        ret = "802_15_4_2450MHZ_OQPSK";
-        break;
-
-    case SPINEL_CAP_802_15_4_915MHZ_OQPSK:
-        ret = "802_15_4_915MHZ_OQPSK";
-        break;
-
-    case SPINEL_CAP_802_15_4_868MHZ_OQPSK:
-        ret = "802_15_4_868MHZ_OQPSK";
-        break;
-
-    case SPINEL_CAP_802_15_4_915MHZ_BPSK:
-        ret = "802_15_4_915MHZ_BPSK";
-        break;
-
-    case SPINEL_CAP_802_15_4_868MHZ_BPSK:
-        ret = "802_15_4_868MHZ_BPSK";
-        break;
-
-    case SPINEL_CAP_802_15_4_915MHZ_ASK:
-        ret = "802_15_4_915MHZ_ASK";
-        break;
-
-    case SPINEL_CAP_802_15_4_868MHZ_ASK:
-        ret = "802_15_4_868MHZ_ASK";
-        break;
-
-    case SPINEL_CAP_CONFIG_FTD:
-        ret = "CONFIG_FTD";
-        break;
-
-    case SPINEL_CAP_CONFIG_MTD:
-        ret = "CONFIG_MTD";
-        break;
-
-    case SPINEL_CAP_CONFIG_RADIO:
-        ret = "CONFIG_RADIO";
-        break;
-
-    case SPINEL_CAP_ROLE_ROUTER:
-        ret = "ROLE_ROUTER";
-        break;
-
-    case SPINEL_CAP_ROLE_SLEEPY:
-        ret = "ROLE_SLEEPY";
-        break;
-
-    case SPINEL_CAP_NET_THREAD_1_0:
-        ret = "NET_THREAD_1_0";
-        break;
-
-    case SPINEL_CAP_NET_THREAD_1_1:
-        ret = "NET_THREAD_1_1";
-        break;
-
-    case SPINEL_CAP_RCP_API_VERSION:
-        ret = "RCP_API_VERSION";
-        break;
-
-    case SPINEL_CAP_MAC_ALLOWLIST:
-        ret = "MAC_ALLOWLIST";
-        break;
-
-    case SPINEL_CAP_MAC_RAW:
-        ret = "MAC_RAW";
-        break;
-
-    case SPINEL_CAP_OOB_STEERING_DATA:
-        ret = "OOB_STEERING_DATA";
-        break;
-
-    case SPINEL_CAP_CHANNEL_MONITOR:
-        ret = "CHANNEL_MONITOR";
-        break;
-
-    case SPINEL_CAP_CHANNEL_MANAGER:
-        ret = "CHANNEL_MANAGER";
-        break;
-
-    case SPINEL_CAP_OPENTHREAD_LOG_METADATA:
-        ret = "OPENTHREAD_LOG_METADATA";
-        break;
-
-    case SPINEL_CAP_TIME_SYNC:
-        ret = "TIME_SYNC";
-        break;
-
-    case SPINEL_CAP_CHILD_SUPERVISION:
-        ret = "CHILD_SUPERVISION";
-        break;
-
-    case SPINEL_CAP_POSIX:
-        ret = "POSIX";
-        break;
-
-    case SPINEL_CAP_SLAAC:
-        ret = "SLAAC";
-        break;
-
-    case SPINEL_CAP_RADIO_COEX:
-        ret = "RADIO_COEX";
-        break;
-
-    case SPINEL_CAP_MAC_RETRY_HISTOGRAM:
-        ret = "MAC_RETRY_HISTOGRAM";
-        break;
-
-    case SPINEL_CAP_MULTI_RADIO:
-        ret = "MULTI_RADIO";
-        break;
-
-    case SPINEL_CAP_ERROR_RATE_TRACKING:
-        ret = "ERROR_RATE_TRACKING";
-        break;
-
-    case SPINEL_CAP_THREAD_COMMISSIONER:
-        ret = "THREAD_COMMISSIONER";
-        break;
-
-    case SPINEL_CAP_THREAD_TMF_PROXY:
-        ret = "THREAD_TMF_PROXY";
-        break;
-
-    case SPINEL_CAP_THREAD_UDP_FORWARD:
-        ret = "THREAD_UDP_FORWARD";
-        break;
-
-    case SPINEL_CAP_THREAD_JOINER:
-        ret = "THREAD_JOINER";
-        break;
-
-    case SPINEL_CAP_THREAD_BORDER_ROUTER:
-        ret = "THREAD_BORDER_ROUTER";
-        break;
-
-    case SPINEL_CAP_THREAD_SERVICE:
-        ret = "THREAD_SERVICE";
-        break;
-
-    case SPINEL_CAP_NEST_LEGACY_INTERFACE:
-        ret = "NEST_LEGACY_INTERFACE";
-        break;
-
-    case SPINEL_CAP_NEST_LEGACY_NET_WAKE:
-        ret = "NEST_LEGACY_NET_WAKE";
-        break;
-
-    case SPINEL_CAP_NEST_TRANSMIT_HOOK:
-        ret = "NEST_TRANSMIT_HOOK";
-        break;
-
-    default:
-        break;
-    }
-
-    return ret;
-}
-
-const char *spinel_radio_link_to_cstr(uint32_t radio)
-{
-    const char *ret = "UNKNOWN";
-
-    switch (radio)
-    {
-    case SPINEL_RADIO_LINK_IEEE_802_15_4:
-        ret = "IEEE_802_15_4";
-        break;
-
-    case SPINEL_RADIO_LINK_TREL_UDP6:
-        ret = "TREL_UDP6";
-        break;
-
-    default:
-        break;
-    }
-
-    return ret;
+    return spinel_to_cstr(spinel_cap_cstr, capability);
 }
 
 // LCOV_EXCL_STOP
diff --git a/src/lib/spinel/spinel.h b/src/lib/spinel/spinel.h
index 5452682..4732569 100644
--- a/src/lib/spinel/spinel.h
+++ b/src/lib/spinel/spinel.h
@@ -377,7 +377,7 @@
  * Please see section "Spinel definition compatibility guideline" for more details.
  *
  */
-#define SPINEL_RCP_API_VERSION 2
+#define SPINEL_RCP_API_VERSION 3
 
 /**
  * @def SPINEL_MIN_HOST_SUPPORTED_RCP_API_VERSION
@@ -466,6 +466,7 @@
     SPINEL_STATUS_ALREADY                  = 19, ///< The operation is already in progress.
     SPINEL_STATUS_ITEM_NOT_FOUND           = 20, ///< The given item could not be found.
     SPINEL_STATUS_INVALID_COMMAND_FOR_PROP = 21, ///< The given command cannot be performed on this property.
+    SPINEL_STATUS_RESPONSE_TIMEOUT         = 24, ///< No response received from remote node
 
     SPINEL_STATUS_JOIN__BEGIN = 104,
 
@@ -618,6 +619,12 @@
 
 enum
 {
+    SPINEL_NET_FLAG_EXT_DP  = (1 << 6),
+    SPINEL_NET_FLAG_EXT_DNS = (1 << 7),
+};
+
+enum
+{
     SPINEL_ROUTE_PREFERENCE_HIGH   = (1 << SPINEL_NET_FLAG_PREFERENCE_OFFSET),
     SPINEL_ROUTE_PREFERENCE_MEDIUM = (0 << SPINEL_NET_FLAG_PREFERENCE_OFFSET),
     SPINEL_ROUTE_PREFERENCE_LOW    = (3 << SPINEL_NET_FLAG_PREFERENCE_OFFSET),
@@ -692,6 +699,9 @@
     SPINEL_NCP_LOG_REGION_OT_BBR      = 17,
     SPINEL_NCP_LOG_REGION_OT_MLR      = 18,
     SPINEL_NCP_LOG_REGION_OT_DUA      = 19,
+    SPINEL_NCP_LOG_REGION_OT_BR       = 20,
+    SPINEL_NCP_LOG_REGION_OT_SRP      = 21,
+    SPINEL_NCP_LOG_REGION_OT_DNS      = 22,
 };
 
 enum
@@ -715,6 +725,48 @@
     SPINEL_RADIO_LINK_TREL_UDP6     = 1,
 };
 
+// Parameter ids used for:
+// @ref SPINEL_PROP_THREAD_MLR_REQUEST
+enum
+{
+    SPINEL_THREAD_MLR_PARAMID_TIMEOUT = 0
+};
+
+// Backbone Router states used for:
+// @ref SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_STATE
+enum
+{
+    SPINEL_THREAD_BACKBONE_ROUTER_STATE_DISABLED  = 0,
+    SPINEL_THREAD_BACKBONE_ROUTER_STATE_SECONDARY = 1,
+    SPINEL_THREAD_BACKBONE_ROUTER_STATE_PRIMARY   = 2,
+};
+
+typedef enum
+{
+    SPINEL_SRP_CLIENT_ITEM_STATE_TO_ADD     = 0, // Item to be added/registered.
+    SPINEL_SRP_CLIENT_ITEM_STATE_ADDING     = 1, // Item is being added/registered.
+    SPINEL_SRP_CLIENT_ITEM_STATE_TO_REFRESH = 2, // Item to be refreshed (re-register to renew lease).
+    SPINEL_SRP_CLIENT_ITEM_STATE_REFRESHING = 3, // Item is being refreshed.
+    SPINEL_SRP_CLIENT_ITEM_STATE_TO_REMOVE  = 4, // Item to be removed.
+    SPINEL_SRP_CLIENT_ITEM_STATE_REMOVING   = 5, // Item is being removed.
+    SPINEL_SRP_CLIENT_ITEM_STATE_REGISTERED = 6, // Item is registered with server.
+    SPINEL_SRP_CLIENT_ITEM_STATE_REMOVED    = 7, // Item is removed.
+} spinel_srp_client_item_state_t;
+
+typedef enum
+{
+    SPINEL_SRP_CLIENT_ERROR_NONE             = 0, // No error.
+    SPINEL_SRP_CLIENT_ERROR_PARSE            = 1, // Server unable to interpret due to format error.
+    SPINEL_SRP_CLIENT_ERROR_FAILED           = 2, // Server encountered an internal failure.
+    SPINEL_SRP_CLIENT_ERROR_NOT_FOUND        = 3, // Name that ought to exist, does not exists.
+    SPINEL_SRP_CLIENT_ERROR_NOT_IMPLEMENTED  = 4, // Server does not support the query type.
+    SPINEL_SRP_CLIENT_ERROR_SECURITY         = 5, // Service is not authoritative for zone.
+    SPINEL_SRP_CLIENT_ERROR_DUPLICATED       = 6, // Some name that ought not to exist, does exist.
+    SPINEL_SRP_CLIENT_ERROR_RESPONSE_TIMEOUT = 7, // Timed out waiting for response from server (client would retry).
+    SPINEL_SRP_CLIENT_ERROR_INVALID_ARGS     = 8, // Invalid args (e.g., bad service name or TXT-DATA).
+    SPINEL_SRP_CLIENT_ERROR_NO_BUFS          = 9, // No buffer to send the SRP update message.
+} spinel_srp_client_error_t;
+
 typedef struct
 {
     uint8_t bytes[8];
@@ -1129,6 +1181,7 @@
     SPINEL_CAP_NET__BEGIN     = 52,
     SPINEL_CAP_NET_THREAD_1_0 = (SPINEL_CAP_NET__BEGIN + 0),
     SPINEL_CAP_NET_THREAD_1_1 = (SPINEL_CAP_NET__BEGIN + 1),
+    SPINEL_CAP_NET_THREAD_1_2 = (SPINEL_CAP_NET__BEGIN + 2),
     SPINEL_CAP_NET__END       = 64,
 
     SPINEL_CAP_RCP__BEGIN      = 64,
@@ -1150,16 +1203,20 @@
     SPINEL_CAP_RADIO_COEX              = (SPINEL_CAP_OPENTHREAD__BEGIN + 11),
     SPINEL_CAP_MAC_RETRY_HISTOGRAM     = (SPINEL_CAP_OPENTHREAD__BEGIN + 12),
     SPINEL_CAP_MULTI_RADIO             = (SPINEL_CAP_OPENTHREAD__BEGIN + 13),
+    SPINEL_CAP_SRP_CLIENT              = (SPINEL_CAP_OPENTHREAD__BEGIN + 14),
+    SPINEL_CAP_DUA                     = (SPINEL_CAP_OPENTHREAD__BEGIN + 15),
     SPINEL_CAP_OPENTHREAD__END         = 640,
 
-    SPINEL_CAP_THREAD__BEGIN        = 1024,
-    SPINEL_CAP_THREAD_COMMISSIONER  = (SPINEL_CAP_THREAD__BEGIN + 0),
-    SPINEL_CAP_THREAD_TMF_PROXY     = (SPINEL_CAP_THREAD__BEGIN + 1),
-    SPINEL_CAP_THREAD_UDP_FORWARD   = (SPINEL_CAP_THREAD__BEGIN + 2),
-    SPINEL_CAP_THREAD_JOINER        = (SPINEL_CAP_THREAD__BEGIN + 3),
-    SPINEL_CAP_THREAD_BORDER_ROUTER = (SPINEL_CAP_THREAD__BEGIN + 4),
-    SPINEL_CAP_THREAD_SERVICE       = (SPINEL_CAP_THREAD__BEGIN + 5),
-    SPINEL_CAP_THREAD__END          = 1152,
+    SPINEL_CAP_THREAD__BEGIN          = 1024,
+    SPINEL_CAP_THREAD_COMMISSIONER    = (SPINEL_CAP_THREAD__BEGIN + 0),
+    SPINEL_CAP_THREAD_TMF_PROXY       = (SPINEL_CAP_THREAD__BEGIN + 1),
+    SPINEL_CAP_THREAD_UDP_FORWARD     = (SPINEL_CAP_THREAD__BEGIN + 2),
+    SPINEL_CAP_THREAD_JOINER          = (SPINEL_CAP_THREAD__BEGIN + 3),
+    SPINEL_CAP_THREAD_BORDER_ROUTER   = (SPINEL_CAP_THREAD__BEGIN + 4),
+    SPINEL_CAP_THREAD_SERVICE         = (SPINEL_CAP_THREAD__BEGIN + 5),
+    SPINEL_CAP_THREAD_CSL_RECEIVER    = (SPINEL_CAP_THREAD__BEGIN + 6),
+    SPINEL_CAP_THREAD_BACKBONE_ROUTER = (SPINEL_CAP_THREAD__BEGIN + 8),
+    SPINEL_CAP_THREAD__END            = 1152,
 
     SPINEL_CAP_NEST__BEGIN           = 15296,
     SPINEL_CAP_NEST_LEGACY_INTERFACE = (SPINEL_CAP_NEST__BEGIN + 0),
@@ -1582,6 +1639,13 @@
      * First byte is the channel then the max transmit power, write-only.
      */
     SPINEL_PROP_PHY_CHAN_MAX_POWER = SPINEL_PROP_PHY__BEGIN + 11,
+    /// Region code
+    /** Format: `S`
+     *
+     * The ascii representation of the ISO 3166 alpha-2 code.
+     *
+     */
+    SPINEL_PROP_PHY_REGION_CODE = SPINEL_PROP_PHY__BEGIN + 12,
 
     SPINEL_PROP_PHY__END = 0x30,
 
@@ -2255,7 +2319,7 @@
     SPINEL_PROP_THREAD_STABLE_NETWORK_DATA_VERSION = SPINEL_PROP_THREAD__BEGIN + 9,
 
     /// On-Mesh Prefixes
-    /** Format: `A(t(6CbCbS))`
+    /** Format: `A(t(6CbCbSC))`
      *
      * Data per item is:
      *
@@ -2266,8 +2330,11 @@
      *  `b`: "Is defined locally" flag. Set if this network was locally
      *       defined. Assumed to be true for set, insert and replace. Clear if
      *       the on mesh network was defined by another node.
+     *       This field is ignored for INSERT and REMOVE commands.
      *  `S`: The RLOC16 of the device that registered this on-mesh prefix entry.
      *       This value is not used and ignored when adding an on-mesh prefix.
+     *       This field is ignored for INSERT and REMOVE commands.
+     *  `C`: TLV flags extended (additional field for Thread 1.2 features).
      *
      */
     SPINEL_PROP_THREAD_ON_MESH_NETS = SPINEL_PROP_THREAD__BEGIN + 10,
@@ -2860,6 +2927,154 @@
      */
     SPINEL_PROP_THREAD_NEW_DATASET = SPINEL_PROP_THREAD_EXT__BEGIN + 40,
 
+    /// MAC CSL Period
+    /** Format: `S`
+     * Required capability: `SPINEL_CAP_THREAD_CSL_RECEIVER`
+     *
+     * The CSL period in units of 10 symbols. Value of 0 indicates that CSL should be disabled.
+     */
+    SPINEL_PROP_THREAD_CSL_PERIOD = SPINEL_PROP_THREAD_EXT__BEGIN + 41,
+
+    /// MAC CSL Timeout
+    /** Format: `L`
+     * Required capability: `SPINEL_CAP_THREAD_CSL_RECEIVER`
+     *
+     * The CSL timeout in seconds.
+     */
+    SPINEL_PROP_THREAD_CSL_TIMEOUT = SPINEL_PROP_THREAD_EXT__BEGIN + 42,
+
+    /// MAC CSL Channel
+    /** Format: `C`
+     * Required capability: `SPINEL_CAP_THREAD_CSL_RECEIVER`
+     *
+     * The CSL channel as described in chapter 4.6.5.1.2 of the Thread v1.2.0 Specification.
+     * Value of 0 means that CSL reception (if enabled) occurs on the Thread Network channel.
+     * Value from range [11,26] is an alternative channel on which a CSL reception occurs.
+     */
+    SPINEL_PROP_THREAD_CSL_CHANNEL = SPINEL_PROP_THREAD_EXT__BEGIN + 43,
+
+    /// Thread Domain Name
+    /** Format `U` - Read-write
+     * Required capability: `SPINEL_CAP_NET_THREAD_1_2`
+     *
+     * This property is available since Thread 1.2.0.
+     * Write to this property succeeds only when Thread protocols are disabled.
+     *
+     */
+    SPINEL_PROP_THREAD_DOMAIN_NAME = SPINEL_PROP_THREAD_EXT__BEGIN + 44,
+
+    /// Multicast Listeners Register Request
+    /** Format `t(A(6))A(t(CD))` - Write-only
+     * Required capability: `SPINEL_CAP_NET_THREAD_1_2`
+     *
+     * `t(A(6))`: Array of IPv6 multicast addresses
+     * `A(t(CD))`: Array of structs holding optional parameters as follows
+     *   `C`: Parameter id
+     *   `D`: Parameter value
+     *
+     *   +----------------------------------------------------------------+
+     *   | Id:   SPINEL_THREAD_MLR_PARAMID_TIMEOUT                        |
+     *   | Type: `L`                                                      |
+     *   | Description: Timeout in seconds. If this optional parameter is |
+     *   |   omitted, the default value of the BBR will be used.          |
+     *   | Special values:                                                |
+     *   |   0 causes given addresses to be removed                       |
+     *   |   0xFFFFFFFF is permanent and persistent registration          |
+     *   +----------------------------------------------------------------+
+     *
+     * Write to this property initiates update of Multicast Listeners Table on the primary BBR.
+     * If the write succeeded, the result of network operation will be notified later by the
+     * SPINEL_PROP_THREAD_MLR_RESPONSE property. If the write fails, no MLR.req is issued and
+     * notifiaction through the SPINEL_PROP_THREAD_MLR_RESPONSE property will not occur.
+     *
+     */
+    SPINEL_PROP_THREAD_MLR_REQUEST = SPINEL_PROP_THREAD_EXT__BEGIN + 52,
+
+    /// Multicast Listeners Register Response
+    /** Format `CCt(A(6))` - Unsolicited notifications only
+     * Required capability: `SPINEL_CAP_NET_THREAD_1_2`
+     *
+     * `C`: Status
+     * `C`: MlrStatus (The Multicast Listener Registration Status)
+     * `A(6)`: Array of IPv6 addresses that failed to be updated on the primary BBR
+     *
+     * This property is notified asynchronously when the NCP receives MLR.rsp following
+     * previous write to the SPINEL_PROP_THREAD_MLR_REQUEST property.
+     */
+    SPINEL_PROP_THREAD_MLR_RESPONSE = SPINEL_PROP_THREAD_EXT__BEGIN + 53,
+
+    /// Interface Identifier specified for Thread Domain Unicast Address.
+    /** Format: `A(C)` - Read-write
+     *
+     *   `A(C)`: Interface Identifier (8 bytes).
+     *
+     * Required capability: SPINEL_CAP_DUA
+     *
+     * If write to this property is performed without specified parameter
+     * the Interface Identifier of the Thread Domain Unicast Address will be cleared.
+     * If the DUA Interface Identifier is cleared on the NCP device,
+     * the get spinel property command will be returned successfully without specified parameter.
+     *
+     */
+    SPINEL_PROP_THREAD_DUA_ID = SPINEL_PROP_THREAD_EXT__BEGIN + 54,
+
+    /// Thread 1.2 Primary Backbone Router information in the Thread Network.
+    /** Format: `SSLC` - Read-Only
+     *
+     * Required capability: `SPINEL_CAP_NET_THREAD_1_2`
+     *
+     * `S`: Server.
+     * `S`: Reregistration Delay (in seconds).
+     * `L`: Multicast Listener Registration Timeout (in seconds).
+     * `C`: Sequence Number.
+     *
+     */
+    SPINEL_PROP_THREAD_BACKBONE_ROUTER_PRIMARY = SPINEL_PROP_THREAD_EXT__BEGIN + 55,
+
+    /// Thread 1.2 Backbone Router local state.
+    /** Format: `C` - Read-Write
+     *
+     * Required capability: `SPINEL_CAP_THREAD_BACKBONE_ROUTER`
+     *
+     * The valid values are specified by SPINEL_THREAD_BACKBONE_ROUTER_STATE_<state> enumeration.
+     * Backbone functionality will be disabled if SPINEL_THREAD_BACKBONE_ROUTER_STATE_DISABLED
+     * is writted to this property, enabled otherwise.
+     *
+     */
+    SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_STATE = SPINEL_PROP_THREAD_EXT__BEGIN + 56,
+
+    /// Local Thread 1.2 Backbone Router configuration.
+    /** Format: SLC - Read-Write
+     *
+     * Required capability: `SPINEL_CAP_THREAD_BACKBONE_ROUTER`
+     *
+     * `S`: Reregistration Delay (in seconds).
+     * `L`: Multicast Listener Registration Timeout (in seconds).
+     * `C`: Sequence Number.
+     *
+     */
+    SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_CONFIG = SPINEL_PROP_THREAD_EXT__BEGIN + 57,
+
+    /// Register local Thread 1.2 Backbone Router configuration.
+    /** Format: Empty (Write only).
+     *
+     * Required capability: `SPINEL_CAP_THREAD_BACKBONE_ROUTER`
+     *
+     * Writing to this property (with any value) will register local Backbone Router configuration.
+     *
+     */
+    SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_REGISTER = SPINEL_PROP_THREAD_EXT__BEGIN + 58,
+
+    /// Thread 1.2 Backbone Router registration jitter.
+    /** Format: `C` - Read-Write
+     *
+     * Required capability: `SPINEL_CAP_THREAD_BACKBONE_ROUTER`
+     *
+     * `C`: Backbone Router registration jitter.
+     *
+     */
+    SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_REGISTRATION_JITTER = SPINEL_PROP_THREAD_EXT__BEGIN + 59,
+
     SPINEL_PROP_THREAD_EXT__END = 0x1600,
 
     SPINEL_PROP_IPV6__BEGIN = 0x60,
@@ -3627,6 +3842,143 @@
      */
     SPINEL_PROP_NEIGHBOR_TABLE_MULTI_RADIO_INFO = SPINEL_PROP_OPENTHREAD__BEGIN + 16,
 
+    /// SRP Client Start
+    /** Format: `b(6Sb)` - Write only
+     * Required capability: `SPINEL_CAP_SRP_CLIENT`.
+     *
+     * Writing to this property allows user to start or stop the SRP client operation with a given SRP server.
+     *
+     * Written value format is:
+     *
+     *   `b` : TRUE to start the client, FALSE to stop the client.
+     *
+     * When used to start the SRP client, the following fields should also be included:
+     *
+     *   `6` : SRP server IPv6 address.
+     *   `U` : SRP server port number.
+     *   `b` : Boolean to indicate whether or not to emit SRP client events (using `SPINEL_PROP_SRP_CLIENT_EVENT`).
+     *
+     */
+    SPINEL_PROP_SRP_CLIENT_START = SPINEL_PROP_OPENTHREAD__BEGIN + 17,
+
+    /// SRP Client Lease Interval
+    /** Format: `L` - Read/Write
+     * Required capability: `SPINEL_CAP_SRP_CLIENT`.
+     *
+     * The lease interval used in SRP update requests (in seconds).
+     *
+     */
+    SPINEL_PROP_SRP_CLIENT_LEASE_INTERVAL = SPINEL_PROP_OPENTHREAD__BEGIN + 18,
+
+    /// SRP Client Key Lease Interval
+    /** Format: `L` - Read/Write
+     * Required capability: `SPINEL_CAP_SRP_CLIENT`.
+     *
+     * The key lease interval used in SRP update requests (in seconds).
+     *
+     */
+    SPINEL_PROP_SRP_CLIENT_KEY_LEASE_INTERVAL = SPINEL_PROP_OPENTHREAD__BEGIN + 19,
+
+    /// SRP Client Host Info
+    /** Format: `UCt(A(6))` - Read only
+     * Required capability: `SPINEL_CAP_SRP_CLIENT`.
+     *
+     * Format is:
+     *
+     *   `U`       : The host name.
+     *   `C`       : The host state (values from `spinel_srp_client_item_state_t`).
+     *   `t(A(6))` : Structure containing array of host IPv6 addresses.
+     *
+     */
+    SPINEL_PROP_SRP_CLIENT_HOST_INFO = SPINEL_PROP_OPENTHREAD__BEGIN + 20,
+
+    /// SRP Client Host Name (label).
+    /** Format: `U` - Read/Write
+     * Required capability: `SPINEL_CAP_SRP_CLIENT`.
+     *
+     */
+    SPINEL_PROP_SRP_CLIENT_HOST_NAME = SPINEL_PROP_OPENTHREAD__BEGIN + 21,
+
+    /// SRP Client Host Addresses
+    /** Format: `A(6)` - Read/Write
+     * Required capability: `SPINEL_CAP_SRP_CLIENT`.
+     *
+     */
+    SPINEL_PROP_SRP_CLIENT_HOST_ADDRESSES = SPINEL_PROP_OPENTHREAD__BEGIN + 22,
+
+    /// SRP Client Services
+    /** Format: `A(t(UUSSSd))` - Read/Insert/Remove
+     * Required capability: `SPINEL_CAP_SRP_CLIENT`.
+     *
+     * This property provide a list/array of services. Data per item is
+     *
+     *   `U` : The service name labels (e.g., "_chip._udp", not the full domain name.
+     *   `U` : The service instance name label (not the full name).
+     *   `S` : The service port number.
+     *   `S` : The service priority.
+     *   `S` : The service weight.
+     *
+     * During remove operation, only service name and service instance name would be used.
+     *
+     */
+    SPINEL_PROP_SRP_CLIENT_SERVICES = SPINEL_PROP_OPENTHREAD__BEGIN + 23,
+
+    /// SRP Client Host And Services Remove
+    /** Format: `b` : Write only
+     * Required capability: `SPINEL_CAP_SRP_CLIENT`.
+     *
+     * Writing to this property starts the remove process of the host info and all services.
+     * Please see `otSrpClientRemoveHostAndServices()` for more details.
+     *
+     * Format is:
+     *
+     *    `b` : A boolean indicating whether or not the host key lease should also be removed.
+     *
+     */
+    SPINEL_PROP_SRP_CLIENT_HOST_SERVICES_REMOVE = SPINEL_PROP_OPENTHREAD__BEGIN + 24,
+
+    /// SRP Client Host And Services Clear
+    /** Format: Empty : Write only
+     * Required capability: `SPINEL_CAP_SRP_CLIENT`.
+     *
+     * Writing to this property clears all host info and all the services.
+     * Please see `otSrpClientClearHostAndServices()` for more details.
+     *
+     */
+    SPINEL_PROP_SRP_CLIENT_HOST_SERVICES_CLEAR = SPINEL_PROP_OPENTHREAD__BEGIN + 25,
+
+    /// SRP Client Event
+    /** Format: t() : Asynchronous event only
+     * Required capability: `SPINEL_CAP_SRP_CLIENT`.
+     *
+     * This property is asynchronously emitted when there is an event from SRP client notifying some state changes or
+     * errors.
+     *
+     * The general format of this property is as follows:
+     *
+     *    `S` : Error code (see `spinel_srp_client_error_t` enumeration).
+     *    `d` : Host info data.
+     *    `d` : Active services.
+     *    `d` : Removed services.
+     *
+     * The host info data contains:
+     *
+     *   `U`       : The host name.
+     *   `C`       : The host state (values from `spinel_srp_client_item_state_t`).
+     *   `t(A(6))` : Structure containing array of host IPv6 addresses.
+     *
+     * The active or removed services data is an array of services `A(t(UUSSSd))` with each service format:
+     *
+     *   `U` : The service name labels (e.g., "_chip._udp", not the full domain name.
+     *   `U` : The service instance name label (not the full name).
+     *   `S` : The service port number.
+     *   `S` : The service priority.
+     *   `S` : The service weight.
+     *   `d` : The encoded TXT-DATA.
+     *
+     */
+    SPINEL_PROP_SRP_CLIENT_EVENT = SPINEL_PROP_OPENTHREAD__BEGIN + 26,
+
     SPINEL_PROP_OPENTHREAD__END = 0x2000,
 
     SPINEL_PROP_SERVER__BEGIN = 0xA0,
diff --git a/src/lib/spinel/spinel_buffer.cpp b/src/lib/spinel/spinel_buffer.cpp
index 93b198b..abaa2af 100644
--- a/src/lib/spinel/spinel_buffer.cpp
+++ b/src/lib/spinel/spinel_buffer.cpp
@@ -717,8 +717,7 @@
     switch (mReadState)
     {
     case kReadStateNotActive:
-
-        // Fall through
+        OT_FALL_THROUGH;
 
     case kReadStateDone:
 
diff --git a/src/lib/url/url.cpp b/src/lib/url/url.cpp
index f15a7c8..659c525 100644
--- a/src/lib/url/url.cpp
+++ b/src/lib/url/url.cpp
@@ -98,7 +98,7 @@
             }
             else if (start[len] == '\0')
             {
-                ExitNow(rval = "");
+                ExitNow(rval = &start[len]);
             }
         }
         last  = start;
@@ -153,6 +153,24 @@
     printf("PASS %s\r\n", __func__);
 }
 
+void TestEmptyValue(void)
+{
+    char         url[] = "spinel:///dev/ttyUSB0?rtscts&baudrate=115200&verbose&verbose&verbose";
+    ot::Url::Url args;
+    const char * arg = nullptr;
+
+    assert(!args.Init(url));
+    assert(!strcmp(args.GetPath(), "/dev/ttyUSB0"));
+    assert((arg = args.GetValue("rtscts")) != nullptr);
+    assert(args.GetValue("rtscts", arg) == nullptr);
+    assert((arg = args.GetValue("verbose", arg)) != nullptr);
+    assert((arg = args.GetValue("verbose", arg)) != nullptr);
+    assert((arg = args.GetValue("verbose", arg)) != nullptr);
+    assert((arg = args.GetValue("verbose", arg)) == nullptr);
+
+    printf("PASS %s\r\n", __func__);
+}
+
 void TestMultipleProtocols(void)
 {
     char         url[] = "spinel+spi:///dev/ttyUSB0?baudrate=115200";
@@ -196,6 +214,7 @@
 {
     TestSimple();
     TestSimpleNoQueryString();
+    TestEmptyValue();
     TestMultipleProtocols();
     TestMultipleProtocolsAndDuplicateParameters();
 
diff --git a/src/ncp/BUILD.gn b/src/ncp/BUILD.gn
index 5e25ecc..2adfdec 100644
--- a/src/ncp/BUILD.gn
+++ b/src/ncp/BUILD.gn
@@ -38,10 +38,10 @@
   "ncp_base_mtd.cpp",
   "ncp_base_radio.cpp",
   "ncp_config.h",
+  "ncp_hdlc.cpp",
+  "ncp_hdlc.hpp",
   "ncp_spi.cpp",
   "ncp_spi.hpp",
-  "ncp_uart.cpp",
-  "ncp_uart.hpp",
 ]
 
 config("ncp_config") {
diff --git a/src/ncp/CMakeLists.txt b/src/ncp/CMakeLists.txt
index 8c0134a..76bf8d3 100644
--- a/src/ncp/CMakeLists.txt
+++ b/src/ncp/CMakeLists.txt
@@ -37,7 +37,7 @@
     ncp_base_dispatcher.cpp
     ncp_base_radio.cpp
     ncp_spi.cpp
-    ncp_uart.cpp
+    ncp_hdlc.cpp
 )
 
 set(OT_NCP_VENDOR_HOOK_SOURCE "" CACHE STRING "set vendor hook source file for NCP")
@@ -51,6 +51,12 @@
     ncp_base_ftd.cpp
     ncp_base_mtd.cpp
 )
+option(OT_NCP_SPI "enable NCP SPI support")
+if(OT_NCP_SPI)
+    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_NCP_SPI_ENABLE=1")
+else()
+    target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1")
+endif()
 
 if(OT_FTD)
     include(ftd.cmake)
diff --git a/src/ncp/Makefile.am b/src/ncp/Makefile.am
index 7733e9f..fff99af 100644
--- a/src/ncp/Makefile.am
+++ b/src/ncp/Makefile.am
@@ -37,6 +37,7 @@
 
 lib_LIBRARIES                                     = $(NULL)
 
+if OPENTHREAD_ENABLE_NCP
 if OPENTHREAD_ENABLE_FTD
 lib_LIBRARIES                                    += libopenthread-ncp-ftd.a
 endif
@@ -44,6 +45,7 @@
 if OPENTHREAD_ENABLE_MTD
 lib_LIBRARIES                                    += libopenthread-ncp-mtd.a
 endif
+endif
 
 if OPENTHREAD_ENABLE_RADIO_ONLY
 lib_LIBRARIES                                    += libopenthread-rcp.a
@@ -110,8 +112,8 @@
     ncp_config.h                                    \
     ncp_spi.cpp                                     \
     ncp_spi.hpp                                     \
-    ncp_uart.cpp                                    \
-    ncp_uart.hpp                                    \
+    ncp_hdlc.cpp                                    \
+    ncp_hdlc.hpp                                    \
     $(NULL)
 
 if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK
diff --git a/src/ncp/example_vendor_hook.cpp b/src/ncp/example_vendor_hook.cpp
index 195e084..4c0b72d 100644
--- a/src/ncp/example_vendor_hook.cpp
+++ b/src/ncp/example_vendor_hook.cpp
@@ -118,20 +118,27 @@
 
 //-------------------------------------------------------------------------------------------------------------------
 // When OPENTHREAD_ENABLE_NCP_VENDOR_HOOK is enabled, vendor code is
-// expected to provide the `otNcpInit()` function. The reason behind
+// expected to provide the `otAppNcpInit()` function. The reason behind
 // this is to enable vendor code to define its own sub-class of
-// `NcpBase` or `NcpUart`/`NcpSpi`.
+// `NcpBase` or `NcpHdlc`/`NcpSpi`.
 //
-// Example below show how to add a vendor sub-class over `NcpUart`.
+// Example below show how to add a vendor sub-class over `NcpHdlc`.
 
-#include "ncp_uart.hpp"
+#include "ncp_hdlc.hpp"
 #include "common/new.hpp"
 
-class NcpVendorUart : public ot::Ncp::NcpUart
+class NcpVendorUart : public ot::Ncp::NcpHdlc
 {
+    static int SendHdlc(const uint8_t *aBuf, uint16_t aBufLength)
+    {
+        OT_UNUSED_VARIABLE(aBuf);
+        OT_UNUSED_VARIABLE(aBufLength);
+        return 0;
+    }
+
 public:
     NcpVendorUart(ot::Instance *aInstance)
-        : ot::Ncp::NcpUart(aInstance)
+        : ot::Ncp::NcpHdlc(aInstance, &NcpVendorUart::SendHdlc)
     {
     }
 
@@ -140,7 +147,7 @@
 
 static OT_DEFINE_ALIGNED_VAR(sNcpVendorRaw, sizeof(NcpVendorUart), uint64_t);
 
-extern "C" void otNcpInit(otInstance *aInstance)
+extern "C" void otAppNcpInit(otInstance *aInstance)
 {
     NcpVendorUart *ncpVendor = nullptr;
     ot::Instance * instance  = static_cast<ot::Instance *>(aInstance);
diff --git a/src/ncp/ftd.cmake b/src/ncp/ftd.cmake
index afa685c..020c4d2 100644
--- a/src/ncp/ftd.cmake
+++ b/src/ncp/ftd.cmake
@@ -37,7 +37,7 @@
 
 target_compile_definitions(openthread-ncp-ftd PRIVATE
     OPENTHREAD_FTD=1
-    OPENTHREAD_CONFIG_NCP_UART_ENABLE=1
+    OPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1
 )
 
 target_compile_options(openthread-ncp-ftd PRIVATE
diff --git a/src/ncp/mtd.cmake b/src/ncp/mtd.cmake
index aa4f274..81a377c 100644
--- a/src/ncp/mtd.cmake
+++ b/src/ncp/mtd.cmake
@@ -37,7 +37,7 @@
 
 target_compile_definitions(openthread-ncp-mtd PRIVATE
     OPENTHREAD_MTD=1
-    OPENTHREAD_CONFIG_NCP_UART_ENABLE=1
+    OPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1
 )
 
 target_compile_options(openthread-ncp-mtd PRIVATE
diff --git a/src/ncp/ncp_base.cpp b/src/ncp/ncp_base.cpp
index 3ef0445..4fbb8f6 100644
--- a/src/ncp/ncp_base.cpp
+++ b/src/ncp/ncp_base.cpp
@@ -135,6 +135,10 @@
         ret = SPINEL_STATUS_ITEM_NOT_FOUND;
         break;
 
+    case OT_ERROR_RESPONSE_TIMEOUT:
+        ret = SPINEL_STATUS_RESPONSE_TIMEOUT;
+        break;
+
     default:
         // Unknown error code. Wrap it as a Spinel status and return that.
         ret = static_cast<spinel_status_t>(SPINEL_STATUS_STACK_NATIVE__BEGIN + static_cast<uint32_t>(aError));
@@ -207,7 +211,7 @@
     , mDiscoveryScanJoinerFlag(false)
     , mDiscoveryScanEnableFiltering(false)
     , mDiscoveryScanPanId(0xffff)
-    , mUpdateChangedPropsTask(*aInstance, NcpBase::UpdateChangedProps, this)
+    , mUpdateChangedPropsTask(*aInstance, NcpBase::UpdateChangedProps)
     , mThreadChangedFlags(0)
     , mHostPowerState(SPINEL_HOST_POWER_STATE_ONLINE)
     , mHostPowerReplyFrameTag(Spinel::Buffer::kInvalidTag)
@@ -243,6 +247,10 @@
     , mOutboundInsecureIpFrameCounter(0)
     , mDroppedOutboundIpFrameCounter(0)
     , mDroppedInboundIpFrameCounter(0)
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+    , mSrpClientNumHostAddresses(0)
+    , mSrpClientCallbackEnabled(false)
+#endif
 #endif // OPENTHREAD_MTD || OPENTHREAD_FTD
     , mFramingErrorCounter(0)
     , mRxSpinelFrameCounter(0)
@@ -281,6 +289,9 @@
 #endif
     otThreadRegisterParentResponseCallback(mInstance, &NcpBase::HandleParentResponseInfo, static_cast<void *>(this));
 #endif // OPENTHREAD_FTD
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+    otSrpClientSetCallback(mInstance, HandleSrpClientCallback, this);
+#endif
 #if OPENTHREAD_CONFIG_LEGACY_ENABLE
     mLegacyNodeDidJoin = false;
     mLegacyHandlers    = nullptr;
@@ -624,6 +635,18 @@
     case OT_LOG_REGION_DUA:
         spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_DUA;
         break;
+
+    case OT_LOG_REGION_BR:
+        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_BR;
+        break;
+
+    case OT_LOG_REGION_SRP:
+        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_SRP;
+        break;
+
+    case OT_LOG_REGION_DNS:
+        spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_DNS;
+        break;
     }
 
     return spinelLogRegion;
@@ -1195,14 +1218,6 @@
     IgnoreError(otIp6SetEnabled(mInstance, false));
 #endif
 
-    error = WriteLastStatusFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_STATUS_RESET_SOFTWARE);
-
-    if (error != OT_ERROR_NONE)
-    {
-        mChangedPropsSet.AddLastStatus(SPINEL_STATUS_RESET_UNKNOWN);
-        mUpdateChangedPropsTask.Post();
-    }
-
     sNcpInstance = nullptr;
 
     return error;
@@ -1817,6 +1832,11 @@
 #if OPENTHREAD_MTD || OPENTHREAD_FTD
 
     SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_NET_THREAD_1_1));
+
+#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_NET_THREAD_1_2));
+#endif
+
     SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_PCAP));
 
 #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
@@ -1895,10 +1915,26 @@
     SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_SERVICE));
 #endif
 
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_CSL_RECEIVER));
+#endif
+
 #if OPENTHREAD_CONFIG_MULTI_RADIO
     SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_MULTI_RADIO));
 #endif
 
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_SRP_CLIENT));
+#endif
+
+#if OPENTHREAD_CONFIG_DUA_ENABLE
+    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_DUA));
+#endif
+
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
+    SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_BACKBONE_ROUTER));
+#endif
+
 #endif // OPENTHREAD_MTD || OPENTHREAD_FTD
 
 exit:
@@ -2262,6 +2298,36 @@
     return error;
 }
 
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_REGION_CODE>(void)
+{
+    uint16_t regionCode;
+    otError  error = OT_ERROR_NONE;
+
+    error = otPlatRadioGetRegion(mInstance, &regionCode);
+    if (error == OT_ERROR_NONE)
+    {
+        error = mEncoder.WriteUint16(regionCode);
+    }
+    else
+    {
+        error = mEncoder.OverwriteWithLastStatusError(ThreadErrorToSpinelStatus(error));
+    }
+
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_REGION_CODE>(void)
+{
+    uint16_t regionCode;
+    otError  error = OT_ERROR_NONE;
+
+    SuccessOrExit(error = mDecoder.ReadUint16(regionCode));
+    error = otPlatRadioSetRegion(mInstance, regionCode);
+
+exit:
+    return error;
+}
+
 template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_DEBUG_TEST_ASSERT>(void)
 {
 #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
diff --git a/src/ncp/ncp_base.hpp b/src/ncp/ncp_base.hpp
index acb2ca4..990c514 100644
--- a/src/ncp/ncp_base.hpp
+++ b/src/ncp/ncp_base.hpp
@@ -50,6 +50,9 @@
 #if OPENTHREAD_CONFIG_MULTI_RADIO
 #include <openthread/multi_radio.h>
 #endif
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+#include <openthread/srp_client.h>
+#endif
 
 #include "changed_props_set.hpp"
 #include "common/instance.hpp"
@@ -334,6 +337,16 @@
     void        HandleJoinerCallback(otError aError);
 #endif
 
+    static void HandleMlrRegResult_Jump(void *              aContext,
+                                        otError             aError,
+                                        uint8_t             aMlrStatus,
+                                        const otIp6Address *aFailedAddresses,
+                                        uint8_t             aFailedAddressNum);
+    void        HandleMlrRegResult(otError             aError,
+                                   uint8_t             aMlrStatus,
+                                   const otIp6Address *aFailedAddresses,
+                                   uint8_t             aFailedAddressNum);
+
     otError EncodeOperationalDataset(const otOperationalDataset &aDataset);
 
     otError DecodeOperationalDataset(otOperationalDataset &aDataset,
@@ -423,7 +436,7 @@
 #if OPENTHREAD_CONFIG_DIAG_ENABLE
     static_assert(OPENTHREAD_CONFIG_DIAG_OUTPUT_BUFFER_SIZE <=
                       OPENTHREAD_CONFIG_NCP_TX_BUFFER_SIZE - kSpinelCmdHeaderSize - kSpinelPropIdSize,
-                  "diag output buffer should be smaller than NCP UART tx buffer");
+                  "diag output buffer should be smaller than NCP HDLC tx buffer");
 
     otError HandlePropertySet_SPINEL_PROP_NEST_STREAM_MFG(uint8_t aHeader);
 #endif
@@ -594,6 +607,45 @@
     uint32_t mOutboundInsecureIpFrameCounter; // Number of insecure outbound data/IP frames.
     uint32_t mDroppedOutboundIpFrameCounter;  // Number of dropped outbound data/IP frames.
     uint32_t mDroppedInboundIpFrameCounter;   // Number of dropped inbound data/IP frames.
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+    enum : uint8_t
+    {
+        kSrpClientMaxServices      = OPENTHREAD_CONFIG_NCP_SRP_CLIENT_MAX_SERVICES,
+        kSrpClientMaxHostAddresses = OPENTHREAD_CONFIG_NCP_SRP_CLIENT_MAX_HOST_ADDRESSES,
+        kSrpClientNameSize         = 64,
+    };
+
+    struct SrpClientService
+    {
+        void MarkAsNotInUse(void) { mService.mNext = &mService; }
+        bool IsInUse(void) const { return (mService.mNext != &mService); }
+
+        otSrpClientService mService;
+        char               mInstanceName[kSrpClientNameSize];
+        char               mServiceName[kSrpClientNameSize];
+    };
+
+    otError EncodeSrpClientHostInfo(const otSrpClientHostInfo &aHostInfo);
+    otError EncodeSrpClientServices(const otSrpClientService *aServices);
+
+    static void HandleSrpClientCallback(otError                    aError,
+                                        const otSrpClientHostInfo *aHostInfo,
+                                        const otSrpClientService * aServices,
+                                        const otSrpClientService * aRemovedServices,
+                                        void *                     aContext);
+    void        HandleSrpClientCallback(otError                    aError,
+                                        const otSrpClientHostInfo *aHostInfo,
+                                        const otSrpClientService * aServices,
+                                        const otSrpClientService * aRemovedServices);
+
+    char             mSrpClientHostName[kSrpClientNameSize];
+    SrpClientService mSrpClientServicePool[kSrpClientMaxServices];
+    otIp6Address     mSrpClientHostAddresses[kSrpClientMaxHostAddresses];
+    uint8_t          mSrpClientNumHostAddresses;
+    bool             mSrpClientCallbackEnabled;
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+
 #if OPENTHREAD_CONFIG_LEGACY_ENABLE
     const otNcpLegacyHandlers *mLegacyHandlers;
     uint8_t                    mLegacyUlaPrefix[OT_NCP_LEGACY_ULA_PREFIX_LENGTH];
diff --git a/src/ncp/ncp_base_dispatcher.cpp b/src/ncp/ncp_base_dispatcher.cpp
index 01dec49..313a6a3 100644
--- a/src/ncp/ncp_base_dispatcher.cpp
+++ b/src/ncp/ncp_base_dispatcher.cpp
@@ -75,6 +75,7 @@
 #endif
         OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_PHY_CHAN_PREFERRED),
         OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_PHY_FEM_LNA_GAIN),
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_PHY_REGION_CODE),
         OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_MAC_SCAN_STATE),
         OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_MAC_SCAN_MASK),
         OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_MAC_SCAN_PERIOD),
@@ -299,6 +300,29 @@
 #if OPENTHREAD_FTD
         OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_THREAD_ADDRESS_CACHE_TABLE),
         OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_THREAD_NEW_DATASET),
+#endif
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_THREAD_CSL_PERIOD),
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_THREAD_CSL_TIMEOUT),
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_THREAD_CSL_CHANNEL),
+#endif
+#if OPENTHREAD_FTD
+#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_THREAD_DOMAIN_NAME),
+#endif
+#if OPENTHREAD_CONFIG_DUA_ENABLE
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_THREAD_DUA_ID),
+#endif
+#endif // OPENTHREAD_FTD
+#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_THREAD_BACKBONE_ROUTER_PRIMARY),
+#endif
+#if OPENTHREAD_FTD
+#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_STATE),
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_CONFIG),
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_REGISTRATION_JITTER),
+#endif
 #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE
         OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_CHANNEL_MANAGER_NEW_CHANNEL),
         OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_CHANNEL_MANAGER_DELAY),
@@ -328,6 +352,15 @@
 #if OPENTHREAD_CONFIG_MULTI_RADIO
         OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_NEIGHBOR_TABLE_MULTI_RADIO_INFO),
 #endif
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_SRP_CLIENT_LEASE_INTERVAL),
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_SRP_CLIENT_KEY_LEASE_INTERVAL),
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_SRP_CLIENT_HOST_INFO),
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_SRP_CLIENT_HOST_NAME),
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_SRP_CLIENT_HOST_ADDRESSES),
+        OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_SRP_CLIENT_SERVICES),
+#endif
+
 #if OPENTHREAD_CONFIG_LEGACY_ENABLE
         OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_NEST_LEGACY_ULA_PREFIX),
         OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_NEST_LEGACY_LAST_NODE_JOINED),
@@ -376,6 +409,7 @@
 #endif
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_PHY_FEM_LNA_GAIN),
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_PHY_CHAN_MAX_POWER),
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_PHY_REGION_CODE),
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_MAC_SCAN_STATE),
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_MAC_SCAN_MASK),
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_MAC_SCAN_PERIOD),
@@ -511,7 +545,27 @@
 #endif
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_MGMT_GET_ACTIVE_DATASET),
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_MGMT_GET_PENDING_DATASET),
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_CSL_PERIOD),
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_CSL_TIMEOUT),
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_CSL_CHANNEL),
+#endif
 #if OPENTHREAD_FTD
+#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_DOMAIN_NAME),
+#endif
+#if OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_MLR_REQUEST),
+#endif
+#if OPENTHREAD_CONFIG_DUA_ENABLE
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_DUA_ID),
+#endif
+#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_STATE),
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_CONFIG),
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_REGISTER),
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_REGISTRATION_JITTER),
+#endif
 #if OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_MESHCOP_COMMISSIONER_ANNOUNCE_BEGIN),
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_MESHCOP_COMMISSIONER_ENERGY_SCAN),
@@ -542,6 +596,15 @@
 #if OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_SLAAC_ENABLED),
 #endif
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_SRP_CLIENT_START),
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_SRP_CLIENT_LEASE_INTERVAL),
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_SRP_CLIENT_KEY_LEASE_INTERVAL),
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_SRP_CLIENT_HOST_NAME),
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_SRP_CLIENT_HOST_ADDRESSES),
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_SRP_CLIENT_HOST_SERVICES_REMOVE),
+        OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_SRP_CLIENT_HOST_SERVICES_CLEAR),
+#endif
 #if OPENTHREAD_CONFIG_LEGACY_ENABLE
         OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_NEST_LEGACY_ULA_PREFIX),
 #endif
@@ -601,6 +664,11 @@
 #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
         OT_NCP_INSERT_HANDLER_ENTRY(SPINEL_PROP_THREAD_JOINERS),
 #endif
+#if OPENTHREAD_FTD || OPENTHREAD_MTD
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+        OT_NCP_INSERT_HANDLER_ENTRY(SPINEL_PROP_SRP_CLIENT_SERVICES),
+#endif
+#endif
     };
 
 #undef OT_NCP_INSERT_HANDLER_ENTRY
@@ -649,6 +717,11 @@
 #if OPENTHREAD_FTD
         OT_NCP_REMOVE_HANDLER_ENTRY(SPINEL_PROP_THREAD_ACTIVE_ROUTER_IDS),
 #endif
+#if OPENTHREAD_FTD || OPENTHREAD_MTD
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+        OT_NCP_REMOVE_HANDLER_ENTRY(SPINEL_PROP_SRP_CLIENT_SERVICES),
+#endif
+#endif
     };
 
 #undef OT_NCP_REMOVE_HANDLER_ENTRY
diff --git a/src/ncp/ncp_base_ftd.cpp b/src/ncp/ncp_base_ftd.cpp
index 72f6718..a62ae8b 100644
--- a/src/ncp/ncp_base_ftd.cpp
+++ b/src/ncp/ncp_base_ftd.cpp
@@ -33,6 +33,9 @@
 #include "ncp_base.hpp"
 #include <openthread/config.h>
 
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
+#include <openthread/backbone_router_ftd.h>
+#endif
 #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE
 #include <openthread/channel_manager.h>
 #endif
@@ -131,7 +134,9 @@
     {
     case OT_NEIGHBOR_TABLE_EVENT_CHILD_ADDED:
         command = SPINEL_CMD_PROP_VALUE_INSERTED;
-        // Fall through
+
+        OT_FALL_THROUGH;
+
     case OT_NEIGHBOR_TABLE_EVENT_CHILD_REMOVED:
         property = SPINEL_PROP_THREAD_CHILD_TABLE;
         VerifyOrExit(!aEntry.mInfo.mChild.mIsStateRestoring);
@@ -139,7 +144,9 @@
 
     case OT_NEIGHBOR_TABLE_EVENT_ROUTER_ADDED:
         command = SPINEL_CMD_PROP_VALUE_INSERTED;
-        // Fall through
+
+        OT_FALL_THROUGH;
+
     case OT_NEIGHBOR_TABLE_EVENT_ROUTER_REMOVED:
         property = SPINEL_PROP_THREAD_NEIGHBOR_TABLE;
         break;
@@ -327,6 +334,172 @@
     return error;
 }
 
+#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_DOMAIN_NAME>(void)
+{
+    return mEncoder.WriteUtf8(otThreadGetDomainName(mInstance));
+}
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_DOMAIN_NAME>(void)
+{
+    otError     error = OT_ERROR_NONE;
+    const char *domainName;
+
+    SuccessOrExit(error = mDecoder.ReadUtf8(domainName));
+
+    error = otThreadSetDomainName(mInstance, domainName);
+
+exit:
+    return error;
+}
+#endif
+
+#if OPENTHREAD_CONFIG_DUA_ENABLE
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_DUA_ID>(void)
+{
+    const otIp6InterfaceIdentifier *iid   = otThreadGetFixedDuaInterfaceIdentifier(mInstance);
+    otError                         error = OT_ERROR_NONE;
+
+    if (iid == nullptr)
+    {
+        // send empty response
+    }
+    else
+    {
+        for (size_t i = 0; i < sizeof(otIp6InterfaceIdentifier); i++)
+        {
+            SuccessOrExit(error = mEncoder.WriteUint8(iid->mFields.m8[i]));
+        }
+    }
+
+exit:
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_DUA_ID>(void)
+{
+    otError error = OT_ERROR_NONE;
+
+    if (mDecoder.GetRemainingLength() == 0)
+    {
+        SuccessOrExit(error = otThreadSetFixedDuaInterfaceIdentifier(mInstance, nullptr));
+    }
+    else
+    {
+        otIp6InterfaceIdentifier iid;
+
+        for (size_t i = 0; i < sizeof(otIp6InterfaceIdentifier); i++)
+        {
+            SuccessOrExit(error = mDecoder.ReadUint8(iid.mFields.m8[i]));
+        }
+
+        SuccessOrExit(error = otThreadSetFixedDuaInterfaceIdentifier(mInstance, &iid));
+    }
+
+exit:
+    return error;
+}
+#endif // OPENTHREAD_CONFIG_DUA_ENABLE
+
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_STATE>(void)
+{
+    uint8_t state = SPINEL_THREAD_BACKBONE_ROUTER_STATE_DISABLED;
+
+    switch (otBackboneRouterGetState(mInstance))
+    {
+    case OT_BACKBONE_ROUTER_STATE_DISABLED:
+        state = SPINEL_THREAD_BACKBONE_ROUTER_STATE_DISABLED;
+        break;
+
+    case OT_BACKBONE_ROUTER_STATE_SECONDARY:
+        state = SPINEL_THREAD_BACKBONE_ROUTER_STATE_SECONDARY;
+        break;
+
+    case OT_BACKBONE_ROUTER_STATE_PRIMARY:
+        state = SPINEL_THREAD_BACKBONE_ROUTER_STATE_PRIMARY;
+        break;
+    }
+
+    return mEncoder.WriteUint8(state);
+}
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_STATE>(void)
+{
+    uint8_t state;
+    otError error = OT_ERROR_NONE;
+
+    SuccessOrExit(error = mDecoder.ReadUint8(state));
+
+    if (state)
+    {
+        otBackboneRouterSetEnabled(mInstance, true);
+    }
+    else
+    {
+        otBackboneRouterSetEnabled(mInstance, false);
+    }
+
+exit:
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_CONFIG>(void)
+{
+    otError                error = OT_ERROR_NONE;
+    otBackboneRouterConfig bbrConfig;
+
+    otBackboneRouterGetConfig(mInstance, &bbrConfig);
+
+    SuccessOrExit(error = mEncoder.WriteUint16(bbrConfig.mReregistrationDelay));
+    SuccessOrExit(error = mEncoder.WriteUint32(bbrConfig.mMlrTimeout));
+    SuccessOrExit(error = mEncoder.WriteUint8(bbrConfig.mSequenceNumber));
+
+exit:
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_CONFIG>(void)
+{
+    otError                error = OT_ERROR_NONE;
+    otBackboneRouterConfig bbrConfig;
+
+    SuccessOrExit(error = mDecoder.ReadUint16(bbrConfig.mReregistrationDelay));
+    SuccessOrExit(error = mDecoder.ReadUint32(bbrConfig.mMlrTimeout));
+    SuccessOrExit(error = mDecoder.ReadUint8(bbrConfig.mSequenceNumber));
+
+    SuccessOrExit(error = otBackboneRouterSetConfig(mInstance, &bbrConfig));
+
+exit:
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_REGISTER>(void)
+{
+    return otBackboneRouterRegister(mInstance);
+}
+
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_REGISTRATION_JITTER>(void)
+{
+    uint8_t jitter = otBackboneRouterGetRegistrationJitter(mInstance);
+
+    return mEncoder.WriteUint8(jitter);
+}
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_BACKBONE_ROUTER_LOCAL_REGISTRATION_JITTER>(void)
+{
+    otError error = OT_ERROR_NONE;
+    uint8_t jitter;
+
+    SuccessOrExit(error = mDecoder.ReadUint8(jitter));
+
+    otBackboneRouterSetRegistrationJitter(mInstance, jitter);
+
+exit:
+    return error;
+}
+#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
+
 template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_NET_PSKC>(void)
 {
     return mEncoder.WriteData(otThreadGetPskc(mInstance)->m8, sizeof(spinel_net_pskc_t));
diff --git a/src/ncp/ncp_base_mtd.cpp b/src/ncp/ncp_base_mtd.cpp
index 77d0b05..3c0faab 100644
--- a/src/ncp/ncp_base_mtd.cpp
+++ b/src/ncp/ncp_base_mtd.cpp
@@ -60,10 +60,14 @@
 #if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
 #include <openthread/server.h>
 #endif
+#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+#include "openthread/backbone_router.h"
+#endif
 
 #include "common/code_utils.hpp"
 #include "common/debug.hpp"
 #include "common/instance.hpp"
+#include "common/string.hpp"
 #include "net/ip6.hpp"
 
 #if OPENTHREAD_MTD || OPENTHREAD_FTD
@@ -110,6 +114,23 @@
     return flags;
 }
 
+static uint8_t BorderRouterConfigToFlagByteExtended(const otBorderRouterConfig &aConfig)
+{
+    uint8_t flags(0);
+
+    if (aConfig.mNdDns)
+    {
+        flags |= SPINEL_NET_FLAG_EXT_DNS;
+    }
+
+    if (aConfig.mDp)
+    {
+        flags |= SPINEL_NET_FLAG_EXT_DP;
+    }
+
+    return flags;
+}
+
 static uint8_t ExternalRoutePreferenceToFlagByte(int aPreference)
 {
     uint8_t flags;
@@ -125,7 +146,8 @@
         break;
 
     case OT_ROUTE_PREFERENCE_MED:
-        // fall through
+
+        OT_FALL_THROUGH;
 
     default:
         flags = SPINEL_ROUTE_PREFERENCE_MEDIUM;
@@ -184,6 +206,168 @@
     return error;
 }
 
+#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_CSL_PERIOD>(void)
+{
+    uint16_t cslPeriod;
+    otError  error = OT_ERROR_NONE;
+
+    SuccessOrExit(error = mDecoder.ReadUint16(cslPeriod));
+
+    error = otLinkCslSetPeriod(mInstance, cslPeriod);
+
+exit:
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_CSL_PERIOD>(void)
+{
+    return mEncoder.WriteUint16(otLinkCslGetPeriod(mInstance));
+}
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_CSL_TIMEOUT>(void)
+{
+    uint32_t cslTimeout;
+    otError  error = OT_ERROR_NONE;
+
+    SuccessOrExit(error = mDecoder.ReadUint32(cslTimeout));
+
+    error = otLinkCslSetTimeout(mInstance, cslTimeout);
+
+exit:
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_CSL_TIMEOUT>(void)
+{
+    return mEncoder.WriteUint32(otLinkCslGetTimeout(mInstance));
+}
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_CSL_CHANNEL>(void)
+{
+    uint8_t cslChannel;
+    otError error = OT_ERROR_NONE;
+
+    SuccessOrExit(error = mDecoder.ReadUint8(cslChannel));
+
+    error = otLinkCslSetChannel(mInstance, cslChannel);
+
+exit:
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_CSL_CHANNEL>(void)
+{
+    return mEncoder.WriteUint8(otLinkCslGetChannel(mInstance));
+}
+#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
+
+#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_THREAD_MLR_REQUEST>(void)
+{
+    otError      error = OT_ERROR_NONE;
+    otIp6Address addresses[kIp6AddressesNumMax];
+    uint8_t      addressesCount = 0U;
+    bool         timeoutPresent = false;
+    uint32_t     timeout;
+
+    SuccessOrExit(error = mDecoder.OpenStruct());
+
+    while (mDecoder.GetRemainingLengthInStruct())
+    {
+        VerifyOrExit(addressesCount < kIp6AddressesNumMax, error = OT_ERROR_NO_BUFS);
+        SuccessOrExit(error = mDecoder.ReadIp6Address(addresses[addressesCount]));
+        ++addressesCount;
+    }
+
+    SuccessOrExit(error = mDecoder.CloseStruct());
+
+    while (mDecoder.GetRemainingLengthInStruct())
+    {
+        uint8_t paramId;
+
+        SuccessOrExit(error = mDecoder.OpenStruct());
+
+        SuccessOrExit(error = mDecoder.ReadUint8(paramId));
+
+        switch (paramId)
+        {
+        case SPINEL_THREAD_MLR_PARAMID_TIMEOUT:
+            SuccessOrExit(error = mDecoder.ReadUint32(timeout));
+            timeoutPresent = true;
+            break;
+
+        default:
+            ExitNow(error = OT_ERROR_INVALID_ARGS);
+        }
+
+        SuccessOrExit(error = mDecoder.CloseStruct());
+    }
+
+    SuccessOrExit(error = otIp6RegisterMulticastListeners(mInstance, addresses, addressesCount,
+                                                          timeoutPresent ? &timeout : nullptr,
+                                                          &NcpBase::HandleMlrRegResult_Jump, this));
+exit:
+    return error;
+}
+
+void NcpBase::HandleMlrRegResult_Jump(void *              aContext,
+                                      otError             aError,
+                                      uint8_t             aMlrStatus,
+                                      const otIp6Address *aFailedAddresses,
+                                      uint8_t             aFailedAddressNum)
+{
+    static_cast<NcpBase *>(aContext)->HandleMlrRegResult(aError, aMlrStatus, aFailedAddresses, aFailedAddressNum);
+}
+
+void NcpBase::HandleMlrRegResult(otError             aError,
+                                 uint8_t             aMlrStatus,
+                                 const otIp6Address *aFailedAddresses,
+                                 uint8_t             aFailedAddressNum)
+{
+    SuccessOrExit(mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_PROP_VALUE_IS,
+                                      SPINEL_PROP_THREAD_MLR_RESPONSE));
+
+    SuccessOrExit(mEncoder.WriteUint8(static_cast<uint8_t>(ThreadErrorToSpinelStatus(aError))));
+    SuccessOrExit(mEncoder.WriteUint8(aMlrStatus));
+
+    SuccessOrExit(mEncoder.OpenStruct());
+
+    if (aError == OT_ERROR_NONE)
+    {
+        for (size_t i = 0U; i < aFailedAddressNum; ++i)
+        {
+            SuccessOrExit(mEncoder.WriteIp6Address(aFailedAddresses[i]));
+        }
+    }
+
+    SuccessOrExit(mEncoder.CloseStruct());
+
+    SuccessOrExit(mEncoder.EndFrame());
+
+exit:
+    return;
+}
+#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
+
+#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_THREAD_BACKBONE_ROUTER_PRIMARY>(void)
+{
+    otError                error = OT_ERROR_NONE;
+    otBackboneRouterConfig bbrConfig;
+
+    SuccessOrExit(error = otBackboneRouterGetPrimary(mInstance, &bbrConfig));
+
+    SuccessOrExit(error = mEncoder.WriteUint16(bbrConfig.mServer16));
+    SuccessOrExit(error = mEncoder.WriteUint16(bbrConfig.mReregistrationDelay));
+    SuccessOrExit(error = mEncoder.WriteUint32(bbrConfig.mMlrTimeout));
+    SuccessOrExit(error = mEncoder.WriteUint8(bbrConfig.mSequenceNumber));
+
+exit:
+    return error;
+}
+#endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
+
 template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_DATA_POLL_PERIOD>(void)
 {
     return mEncoder.WriteUint32(otLinkGetPollPeriod(mInstance));
@@ -710,6 +894,7 @@
         SuccessOrExit(error = mEncoder.WriteUint8(BorderRouterConfigToFlagByte(borderRouterConfig)));
         SuccessOrExit(error = mEncoder.WriteBool(false)); // isLocal
         SuccessOrExit(error = mEncoder.WriteUint16(borderRouterConfig.mRloc16));
+        SuccessOrExit(error = mEncoder.WriteUint8(BorderRouterConfigToFlagByteExtended(borderRouterConfig)));
 
         SuccessOrExit(error = mEncoder.CloseStruct());
     }
@@ -729,6 +914,7 @@
         SuccessOrExit(error = mEncoder.WriteUint8(BorderRouterConfigToFlagByte(borderRouterConfig)));
         SuccessOrExit(error = mEncoder.WriteBool(true)); // isLocal
         SuccessOrExit(error = mEncoder.WriteUint16(borderRouterConfig.mRloc16));
+        SuccessOrExit(error = mEncoder.WriteUint8(BorderRouterConfigToFlagByteExtended(borderRouterConfig)));
 
         SuccessOrExit(error = mEncoder.CloseStruct());
     }
@@ -744,8 +930,11 @@
     otError              error = OT_ERROR_NONE;
     otBorderRouterConfig borderRouterConfig;
     bool                 stable = false;
-    uint8_t              flags  = 0;
+    bool                 isLocal;
+    uint8_t              flags         = 0;
+    uint8_t              flagsExtended = 0;
     uint8_t              prefixLength;
+    uint16_t             rloc16;
 
     memset(&borderRouterConfig, 0, sizeof(otBorderRouterConfig));
 
@@ -766,6 +955,16 @@
     borderRouterConfig.mDefaultRoute = ((flags & SPINEL_NET_FLAG_DEFAULT_ROUTE) != 0);
     borderRouterConfig.mOnMesh       = ((flags & SPINEL_NET_FLAG_ON_MESH) != 0);
 
+    // A new field 'TLV flags extended' has been added to the SPINEL_PROP_THREAD_ON_MESH_NETS property.
+    // To correctly handle a new field for INSERT command, the additional fields 'isLocal' and 'rloc16' are read and
+    // ignored.
+    if ((mDecoder.ReadBool(isLocal) == OT_ERROR_NONE) && (mDecoder.ReadUint16(rloc16) == OT_ERROR_NONE) &&
+        (mDecoder.ReadUint8(flagsExtended) == OT_ERROR_NONE))
+    {
+        borderRouterConfig.mNdDns = ((flagsExtended & SPINEL_NET_FLAG_EXT_DNS) != 0);
+        borderRouterConfig.mDp    = ((flagsExtended & SPINEL_NET_FLAG_EXT_DP) != 0);
+    }
+
     error = otBorderRouterAddOnMeshPrefix(mInstance, &borderRouterConfig);
 
 exit:
@@ -2749,7 +2948,7 @@
         SuccessOrExit(error = mDecoder.ReadEui64(extAddress));
         SuccessOrExit(error = mDecoder.CloseStruct());
 
-        otLinkFilterRemoveAddress(mInstance, extAddress);
+        SuccessOrExit(error = otLinkFilterAddAddress(mInstance, extAddress));
     }
 
 exit:
@@ -3212,6 +3411,432 @@
 }
 #endif // OPENTHREAD_CONFIG_MULTI_RADIO
 
+// ----------------------------------------------------------------------------
+// SRP Client
+// ----------------------------------------------------------------------------
+
+#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_SRP_CLIENT_START>(void)
+{
+    otError    error = OT_ERROR_NONE;
+    bool       start;
+    bool       callbackEnabled;
+    otSockAddr serverAddr;
+
+    SuccessOrExit(error = mDecoder.ReadBool(start));
+
+    if (!start)
+    {
+        otSrpClientStop(mInstance);
+        ExitNow();
+    }
+
+    SuccessOrExit(error = mDecoder.ReadIp6Address(serverAddr.mAddress));
+    SuccessOrExit(error = mDecoder.ReadUint16(serverAddr.mPort));
+    SuccessOrExit(error = mDecoder.ReadBool(callbackEnabled));
+
+    SuccessOrExit(error = otSrpClientStart(mInstance, &serverAddr));
+    mSrpClientCallbackEnabled = callbackEnabled;
+
+exit:
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_SRP_CLIENT_LEASE_INTERVAL>(void)
+{
+    return mEncoder.WriteUint32(otSrpClientGetLeaseInterval(mInstance));
+}
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_SRP_CLIENT_LEASE_INTERVAL>(void)
+{
+    otError  error;
+    uint32_t interval;
+
+    SuccessOrExit(error = mDecoder.ReadUint32(interval));
+    otSrpClientSetLeaseInterval(mInstance, interval);
+
+exit:
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_SRP_CLIENT_KEY_LEASE_INTERVAL>(void)
+{
+    return mEncoder.WriteUint32(otSrpClientGetKeyLeaseInterval(mInstance));
+}
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_SRP_CLIENT_KEY_LEASE_INTERVAL>(void)
+{
+    otError  error;
+    uint32_t interval;
+
+    SuccessOrExit(error = mDecoder.ReadUint32(interval));
+    otSrpClientSetKeyLeaseInterval(mInstance, interval);
+
+exit:
+    return error;
+}
+
+static spinel_srp_client_item_state_t SrpClientItemStatetoSpinel(otSrpClientItemState aItemState)
+{
+    spinel_srp_client_item_state_t state = SPINEL_SRP_CLIENT_ITEM_STATE_REMOVED;
+
+    switch (aItemState)
+    {
+    case OT_SRP_CLIENT_ITEM_STATE_TO_ADD:
+        state = SPINEL_SRP_CLIENT_ITEM_STATE_TO_ADD;
+        break;
+    case OT_SRP_CLIENT_ITEM_STATE_ADDING:
+        state = SPINEL_SRP_CLIENT_ITEM_STATE_ADDING;
+        break;
+    case OT_SRP_CLIENT_ITEM_STATE_TO_REFRESH:
+        state = SPINEL_SRP_CLIENT_ITEM_STATE_TO_REFRESH;
+        break;
+    case OT_SRP_CLIENT_ITEM_STATE_REFRESHING:
+        state = SPINEL_SRP_CLIENT_ITEM_STATE_REFRESHING;
+        break;
+    case OT_SRP_CLIENT_ITEM_STATE_TO_REMOVE:
+        state = SPINEL_SRP_CLIENT_ITEM_STATE_TO_REMOVE;
+        break;
+    case OT_SRP_CLIENT_ITEM_STATE_REMOVING:
+        state = SPINEL_SRP_CLIENT_ITEM_STATE_REMOVING;
+        break;
+    case OT_SRP_CLIENT_ITEM_STATE_REGISTERED:
+        state = SPINEL_SRP_CLIENT_ITEM_STATE_REGISTERED;
+        break;
+    case OT_SRP_CLIENT_ITEM_STATE_REMOVED:
+        state = SPINEL_SRP_CLIENT_ITEM_STATE_REMOVED;
+        break;
+    }
+
+    return state;
+}
+
+otError NcpBase::EncodeSrpClientHostInfo(const otSrpClientHostInfo &aHostInfo)
+{
+    otError error;
+
+    SuccessOrExit(error = mEncoder.WriteUtf8(aHostInfo.mName != nullptr ? aHostInfo.mName : ""));
+    SuccessOrExit(error = mEncoder.WriteUint8(SrpClientItemStatetoSpinel(aHostInfo.mState)));
+
+    SuccessOrExit(error = mEncoder.OpenStruct());
+
+    for (uint8_t index = 0; index < aHostInfo.mNumAddresses; index++)
+    {
+        SuccessOrExit(error = mEncoder.WriteIp6Address(aHostInfo.mAddresses[index]));
+    }
+
+    SuccessOrExit(error = mEncoder.CloseStruct());
+
+exit:
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_SRP_CLIENT_HOST_INFO>(void)
+{
+    return EncodeSrpClientHostInfo(*otSrpClientGetHostInfo(mInstance));
+}
+
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_SRP_CLIENT_HOST_NAME>(void)
+{
+    const char *name = otSrpClientGetHostInfo(mInstance)->mName;
+
+    return mEncoder.WriteUtf8(name != nullptr ? name : "");
+}
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_SRP_CLIENT_HOST_NAME>(void)
+{
+    otError     error;
+    const char *name;
+
+    SuccessOrExit(error = mDecoder.ReadUtf8(name));
+
+    VerifyOrExit(StringLength(name, kSrpClientNameSize) < kSrpClientNameSize, error = OT_ERROR_INVALID_ARGS);
+
+    // We first make sure we can set the name, and if so
+    // we copy it to the `mSrpClientHostName` buffer and set
+    // the host name again now with the persisted buffer
+    // This ensures that we do not overwrite a previous
+    // `mSrpClientHostName` when host name cannot be set.
+
+    SuccessOrExit(error = otSrpClientSetHostName(mInstance, name));
+
+    strcpy(mSrpClientHostName, name);
+    error = otSrpClientSetHostName(mInstance, mSrpClientHostName);
+    OT_ASSERT(error == OT_ERROR_NONE);
+
+exit:
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_SRP_CLIENT_HOST_ADDRESSES>(void)
+{
+    otError                    error    = OT_ERROR_NONE;
+    const otSrpClientHostInfo *hostInfo = otSrpClientGetHostInfo(mInstance);
+
+    for (uint8_t index = 0; index < hostInfo->mNumAddresses; index++)
+    {
+        SuccessOrExit(error = mEncoder.WriteIp6Address(hostInfo->mAddresses[index]));
+    }
+
+exit:
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_SRP_CLIENT_HOST_ADDRESSES>(void)
+{
+    otError      error;
+    otIp6Address addresses[kSrpClientMaxHostAddresses];
+    uint8_t      numAddresses = 0;
+
+    while (!mDecoder.IsAllReadInStruct())
+    {
+        VerifyOrExit(numAddresses < kSrpClientMaxHostAddresses, error = OT_ERROR_NO_BUFS);
+
+        SuccessOrExit(error = mDecoder.ReadIp6Address(addresses[numAddresses]));
+        numAddresses++;
+    }
+
+    // We first make sure we can set the addresses, and if so
+    // we copy the address list into `mSrpClientHostAddresses`
+    // and set it again. This ensures that we do not overwrite
+    // a previous list before we know it is safe to set/change
+    // the address list.
+
+    SuccessOrExit(error = otSrpClientSetHostAddresses(mInstance, addresses, numAddresses));
+
+    memcpy(mSrpClientHostAddresses, addresses, sizeof(addresses));
+    mSrpClientNumHostAddresses = numAddresses;
+
+    error = otSrpClientSetHostAddresses(mInstance, mSrpClientHostAddresses, numAddresses);
+    OT_ASSERT(error == OT_ERROR_NONE);
+
+exit:
+    return error;
+}
+
+otError NcpBase::EncodeSrpClientServices(const otSrpClientService *aServices)
+{
+    otError error = OT_ERROR_NONE;
+
+    for (; aServices != nullptr; aServices = aServices->mNext)
+    {
+        SuccessOrExit(error = mEncoder.OpenStruct());
+
+        SuccessOrExit(error = mEncoder.WriteUtf8(aServices->mName));
+        SuccessOrExit(error = mEncoder.WriteUtf8(aServices->mInstanceName));
+        SuccessOrExit(error = mEncoder.WriteUint16(aServices->mPort));
+        SuccessOrExit(error = mEncoder.WriteUint16(aServices->mPriority));
+        SuccessOrExit(error = mEncoder.WriteUint16(aServices->mWeight));
+
+        SuccessOrExit(error = mEncoder.CloseStruct());
+    }
+
+exit:
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_SRP_CLIENT_SERVICES>(void)
+{
+    return EncodeSrpClientServices(otSrpClientGetServices(mInstance));
+}
+
+template <> otError NcpBase::HandlePropertyInsert<SPINEL_PROP_SRP_CLIENT_SERVICES>(void)
+{
+    otError           error = OT_ERROR_NONE;
+    SrpClientService *entry = nullptr;
+    const char *      serviceName;
+    const char *      instanceName;
+
+    for (SrpClientService &poolEntry : mSrpClientServicePool)
+    {
+        if (!poolEntry.IsInUse())
+        {
+            entry = &poolEntry;
+            break;
+        }
+    }
+
+    VerifyOrExit(entry != nullptr, error = OT_ERROR_NO_BUFS);
+
+    SuccessOrExit(error = mDecoder.ReadUtf8(serviceName));
+    VerifyOrExit(StringLength(serviceName, kSrpClientNameSize) < kSrpClientNameSize, error = OT_ERROR_INVALID_ARGS);
+    strcpy(entry->mServiceName, serviceName);
+    entry->mService.mName = entry->mServiceName;
+
+    SuccessOrExit(error = mDecoder.ReadUtf8(instanceName));
+    VerifyOrExit(StringLength(instanceName, kSrpClientNameSize) < kSrpClientNameSize, error = OT_ERROR_INVALID_ARGS);
+    strcpy(entry->mInstanceName, instanceName);
+    entry->mService.mInstanceName = entry->mInstanceName;
+
+    SuccessOrExit(error = mDecoder.ReadUint16(entry->mService.mPort));
+    SuccessOrExit(error = mDecoder.ReadUint16(entry->mService.mPriority));
+    SuccessOrExit(error = mDecoder.ReadUint16(entry->mService.mWeight));
+
+    error = otSrpClientAddService(mInstance, &entry->mService);
+
+    if (error != OT_ERROR_NONE)
+    {
+        entry->MarkAsNotInUse();
+    }
+
+exit:
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertyRemove<SPINEL_PROP_SRP_CLIENT_SERVICES>(void)
+{
+    otError     error = OT_ERROR_NONE;
+    const char *serviceName;
+    const char *instanceName;
+
+    SuccessOrExit(error = mDecoder.ReadUtf8(serviceName));
+    SuccessOrExit(error = mDecoder.ReadUtf8(instanceName));
+
+    for (SrpClientService &poolEntry : mSrpClientServicePool)
+    {
+        if (!poolEntry.IsInUse() || (strcmp(serviceName, poolEntry.mServiceName) != 0) ||
+            (strcmp(instanceName, poolEntry.mInstanceName) != 0))
+        {
+            continue;
+        }
+
+        error = otSrpClientRemoveService(mInstance, &poolEntry.mService);
+        ExitNow();
+    }
+
+    error = OT_ERROR_NOT_FOUND;
+
+exit:
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_SRP_CLIENT_HOST_SERVICES_REMOVE>(void)
+{
+    otError error = OT_ERROR_NONE;
+    bool    removeKeyLease;
+
+    SuccessOrExit(error = mDecoder.ReadBool(removeKeyLease));
+
+    error = otSrpClientRemoveHostAndServices(mInstance, removeKeyLease);
+
+exit:
+    return error;
+}
+
+template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_SRP_CLIENT_HOST_SERVICES_CLEAR>(void)
+{
+    otSrpClientClearHostAndServices(mInstance);
+
+    return OT_ERROR_NONE;
+}
+
+static spinel_srp_client_error_t SrpClientErrorToSpinelError(otError aError)
+{
+    spinel_srp_client_error_t error = SPINEL_SRP_CLIENT_ERROR_FAILED;
+
+    switch (aError)
+    {
+    case OT_ERROR_NONE:
+        error = SPINEL_SRP_CLIENT_ERROR_NONE;
+        break;
+    case OT_ERROR_PARSE:
+        error = SPINEL_SRP_CLIENT_ERROR_PARSE;
+        break;
+    case OT_ERROR_NOT_FOUND:
+        error = SPINEL_SRP_CLIENT_ERROR_NOT_FOUND;
+        break;
+    case OT_ERROR_NOT_IMPLEMENTED:
+        error = SPINEL_SRP_CLIENT_ERROR_NOT_IMPLEMENTED;
+        break;
+    case OT_ERROR_SECURITY:
+        error = SPINEL_SRP_CLIENT_ERROR_SECURITY;
+        break;
+    case OT_ERROR_DUPLICATED:
+        error = SPINEL_SRP_CLIENT_ERROR_DUPLICATED;
+        break;
+    case OT_ERROR_RESPONSE_TIMEOUT:
+        error = SPINEL_SRP_CLIENT_ERROR_RESPONSE_TIMEOUT;
+        break;
+    case OT_ERROR_INVALID_ARGS:
+        error = SPINEL_SRP_CLIENT_ERROR_INVALID_ARGS;
+        break;
+    case OT_ERROR_NO_BUFS:
+        error = SPINEL_SRP_CLIENT_ERROR_NO_BUFS;
+        break;
+    case OT_ERROR_FAILED:
+    default:
+        error = SPINEL_SRP_CLIENT_ERROR_FAILED;
+        break;
+    }
+
+    return error;
+}
+
+void NcpBase::HandleSrpClientCallback(otError                    aError,
+                                      const otSrpClientHostInfo *aHostInfo,
+                                      const otSrpClientService * aServices,
+                                      const otSrpClientService * aRemovedServices,
+                                      void *                     aContext)
+{
+    static_cast<NcpBase *>(aContext)->HandleSrpClientCallback(aError, aHostInfo, aServices, aRemovedServices);
+}
+
+void NcpBase::HandleSrpClientCallback(otError                    aError,
+                                      const otSrpClientHostInfo *aHostInfo,
+                                      const otSrpClientService * aServices,
+                                      const otSrpClientService * aRemovedServices)
+{
+    otError                   error = OT_ERROR_NONE;
+    const otSrpClientService *service;
+    const otSrpClientService *next;
+
+    VerifyOrExit(mSrpClientCallbackEnabled);
+
+    SuccessOrExit(error = mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_PROP_VALUE_IS,
+                                              SPINEL_PROP_SRP_CLIENT_EVENT));
+
+    SuccessOrExit(error = mEncoder.WriteUint16(SrpClientErrorToSpinelError(aError)));
+
+    SuccessOrExit(error = mEncoder.OpenStruct());
+    SuccessOrExit(error = EncodeSrpClientHostInfo(*aHostInfo));
+    SuccessOrExit(error = mEncoder.CloseStruct());
+
+    SuccessOrExit(error = mEncoder.OpenStruct());
+    SuccessOrExit(error = EncodeSrpClientServices(aServices));
+    SuccessOrExit(error = mEncoder.CloseStruct());
+
+    SuccessOrExit(error = mEncoder.OpenStruct());
+    SuccessOrExit(error = EncodeSrpClientServices(aRemovedServices));
+    SuccessOrExit(error = mEncoder.CloseStruct());
+
+    SuccessOrExit(error = mEncoder.EndFrame());
+
+exit:
+
+    if (error != OT_ERROR_NONE)
+    {
+        // Emit a NONMEM status if we fail to send the event.
+        mChangedPropsSet.AddLastStatus(SPINEL_STATUS_NOMEM);
+        mUpdateChangedPropsTask.Post();
+    }
+
+    for (service = aRemovedServices; service != nullptr; service = next)
+    {
+        next = service->mNext;
+
+        for (SrpClientService &poolEntry : mSrpClientServicePool)
+        {
+            if (&poolEntry.mService == service)
+            {
+                poolEntry.MarkAsNotInUse();
+            }
+        }
+    }
+}
+
+#endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+
 #if OPENTHREAD_CONFIG_LEGACY_ENABLE
 
 void NcpBase::RegisterLegacyHandlers(const otNcpLegacyHandlers *aHandlers)
diff --git a/src/ncp/ncp_config.h b/src/ncp/ncp_config.h
index cc584c1..60ab9c6 100644
--- a/src/ncp/ncp_config.h
+++ b/src/ncp/ncp_config.h
@@ -50,13 +50,13 @@
 #endif
 
 /**
- * @def OPENTHREAD_CONFIG_NCP_UART_ENABLE
+ * @def OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
  *
- * Define to 1 to enable NCP UART support.
+ * Define to 1 to enable NCP HDLC support.
  *
  */
-#ifndef OPENTHREAD_CONFIG_NCP_UART_ENABLE
-#define OPENTHREAD_CONFIG_NCP_UART_ENABLE 0
+#ifndef OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
+#define OPENTHREAD_CONFIG_NCP_HDLC_ENABLE 0
 #endif
 
 /**
@@ -66,32 +66,32 @@
  *
  */
 #ifndef OPENTHREAD_CONFIG_NCP_TX_BUFFER_SIZE
-#define OPENTHREAD_CONFIG_NCP_TX_BUFFER_SIZE 512
+#define OPENTHREAD_CONFIG_NCP_TX_BUFFER_SIZE 2048
 #endif
 
 /**
- * @def OPENTHREAD_CONFIG_NCP_UART_TX_CHUNK_SIZE
+ * @def OPENTHREAD_CONFIG_NCP_HDLC_TX_CHUNK_SIZE
  *
- * The size of NCP UART TX chunk in bytes.
+ * The size of NCP HDLC TX chunk in bytes.
  *
  */
-#ifndef OPENTHREAD_CONFIG_NCP_UART_TX_CHUNK_SIZE
-#define OPENTHREAD_CONFIG_NCP_UART_TX_CHUNK_SIZE 128
+#ifndef OPENTHREAD_CONFIG_NCP_HDLC_TX_CHUNK_SIZE
+#define OPENTHREAD_CONFIG_NCP_HDLC_TX_CHUNK_SIZE 2048
 #endif
 
 /**
- * @def OPENTHREAD_CONFIG_NCP_UART_RX_BUFFER_SIZE
+ * @def OPENTHREAD_CONFIG_NCP_HDLC_RX_BUFFER_SIZE
  *
- * The size of NCP UART RX buffer in bytes.
+ * The size of NCP HDLC RX buffer in bytes.
  *
  */
-#ifndef OPENTHREAD_CONFIG_NCP_UART_RX_BUFFER_SIZE
+#ifndef OPENTHREAD_CONFIG_NCP_HDLC_RX_BUFFER_SIZE
 #if OPENTHREAD_RADIO
-#define OPENTHREAD_CONFIG_NCP_UART_RX_BUFFER_SIZE 512
+#define OPENTHREAD_CONFIG_NCP_HDLC_RX_BUFFER_SIZE 512
 #else
-#define OPENTHREAD_CONFIG_NCP_UART_RX_BUFFER_SIZE 1300
+#define OPENTHREAD_CONFIG_NCP_HDLC_RX_BUFFER_SIZE 1300
 #endif
-#endif // OPENTHREAD_CONFIG_NCP_UART_RX_BUFFER_SIZE
+#endif // OPENTHREAD_CONFIG_NCP_HDLC_RX_BUFFER_SIZE
 
 /**
  * @def OPENTHREAD_CONFIG_NCP_SPI_BUFFER_SIZE
@@ -189,4 +189,28 @@
 #define OPENTHREAD_ENABLE_NCP_VENDOR_HOOK 0
 #endif
 
+/**
+ * @def OPENTHREAD_CONFIG_NCP_SRP_CLIENT_MAX_SERVICES
+ *
+ * The maximum number of service entries supported by SRP client.
+ *
+ * This is only applicable when SRP client is enabled, i.e., OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE is set.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_NCP_SRP_CLIENT_MAX_SERVICES
+#define OPENTHREAD_CONFIG_NCP_SRP_CLIENT_MAX_SERVICES 2
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_NCP_SRP_CLIENT_MAX_HOST_ADDRESSES
+ *
+ * The maximum number of host IPv6 address entries supported by SRP client.
+ *
+ * This is only applicable when SRP client is enabled, i.e., OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE is set.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_NCP_SRP_CLIENT_MAX_HOST_ADDRESSES
+#define OPENTHREAD_CONFIG_NCP_SRP_CLIENT_MAX_HOST_ADDRESSES 2
+#endif
+
 #endif // CONFIG_NCP_H_
diff --git a/src/ncp/ncp_uart.cpp b/src/ncp/ncp_hdlc.cpp
similarity index 73%
rename from src/ncp/ncp_uart.cpp
rename to src/ncp/ncp_hdlc.cpp
index e5d0e1c..b1a56c3 100644
--- a/src/ncp/ncp_uart.cpp
+++ b/src/ncp/ncp_hdlc.cpp
@@ -27,17 +27,16 @@
 
 /**
  * @file
- *   This file contains definitions for a UART based NCP interface to the OpenThread stack.
+ *   This file contains definitions for a HDLC based NCP interface to the OpenThread stack.
  */
 
-#include "ncp_uart.hpp"
+#include "ncp_hdlc.hpp"
 
 #include <stdio.h>
 
 #include <openthread/ncp.h>
 #include <openthread/platform/logging.h>
 #include <openthread/platform/misc.h>
-#include <openthread/platform/uart.h>
 
 #include "openthread-core-config.h"
 #include "common/code_utils.hpp"
@@ -46,16 +45,16 @@
 #include "common/new.hpp"
 #include "net/ip6.hpp"
 
-#if OPENTHREAD_CONFIG_NCP_UART_ENABLE
+#if OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
 
 #if OPENTHREAD_CONFIG_DIAG_ENABLE
-static_assert(OPENTHREAD_CONFIG_DIAG_OUTPUT_BUFFER_SIZE <= OPENTHREAD_CONFIG_NCP_UART_RX_BUFFER_SIZE -
+static_assert(OPENTHREAD_CONFIG_DIAG_OUTPUT_BUFFER_SIZE <= OPENTHREAD_CONFIG_NCP_HDLC_RX_BUFFER_SIZE -
                                                                ot::Ncp::NcpBase::kSpinelCmdHeaderSize -
                                                                ot::Ncp::NcpBase::kSpinelPropIdSize,
-              "diag output should be smaller than NCP UART rx buffer");
+              "diag output should be smaller than NCP HDLC rx buffer");
 
-static_assert(OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE <= OPENTHREAD_CONFIG_NCP_UART_RX_BUFFER_SIZE,
-              "diag command line should be smaller than NCP UART rx buffer");
+static_assert(OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE <= OPENTHREAD_CONFIG_NCP_HDLC_RX_BUFFER_SIZE,
+              "diag command line should be smaller than NCP HDLC rx buffer");
 #endif
 
 namespace ot {
@@ -63,16 +62,16 @@
 
 #if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK == 0
 
-static OT_DEFINE_ALIGNED_VAR(sNcpRaw, sizeof(NcpUart), uint64_t);
+static OT_DEFINE_ALIGNED_VAR(sNcpRaw, sizeof(NcpHdlc), uint64_t);
 
-extern "C" void otNcpInit(otInstance *aInstance)
+extern "C" void otNcpHdlcInit(otInstance *aInstance, otNcpHdlcSendCallback aSendCallback)
 {
-    NcpUart * ncpUart  = nullptr;
+    NcpHdlc * ncpHdlc  = nullptr;
     Instance *instance = static_cast<Instance *>(aInstance);
 
-    ncpUart = new (&sNcpRaw) NcpUart(instance);
+    ncpHdlc = new (&sNcpRaw) NcpHdlc(instance, aSendCallback);
 
-    if (ncpUart == nullptr || ncpUart != NcpBase::GetNcpInstance())
+    if (ncpHdlc == nullptr || ncpHdlc != NcpBase::GetNcpInstance())
     {
         OT_ASSERT(false);
     }
@@ -80,24 +79,23 @@
 
 #endif // OPENTHREAD_ENABLE_NCP_VENDOR_HOOK == 0
 
-NcpUart::NcpUart(Instance *aInstance)
+NcpHdlc::NcpHdlc(Instance *aInstance, otNcpHdlcSendCallback aSendCallback)
     : NcpBase(aInstance)
-    , mFrameEncoder(mUartBuffer)
-    , mFrameDecoder(mRxBuffer, &NcpUart::HandleFrame, this)
+    , mSendCallback(aSendCallback)
+    , mFrameEncoder(mHdlcBuffer)
+    , mFrameDecoder(mRxBuffer, &NcpHdlc::HandleFrame, this)
     , mState(kStartingFrame)
     , mByte(0)
-    , mUartSendImmediate(false)
-    , mUartSendTask(*aInstance, EncodeAndSendToUart, this)
+    , mHdlcSendImmediate(false)
+    , mHdlcSendTask(*aInstance, EncodeAndSend)
 #if OPENTHREAD_ENABLE_NCP_SPINEL_ENCRYPTER
     , mTxFrameBufferEncrypterReader(mTxFrameBuffer)
 #endif // OPENTHREAD_ENABLE_NCP_SPINEL_ENCRYPTER
 {
     mTxFrameBuffer.SetFrameAddedCallback(HandleFrameAddedToNcpBuffer, this);
-
-    IgnoreError(otPlatUartEnable());
 }
 
-void NcpUart::HandleFrameAddedToNcpBuffer(void *                   aContext,
+void NcpHdlc::HandleFrameAddedToNcpBuffer(void *                   aContext,
                                           Spinel::Buffer::FrameTag aTag,
                                           Spinel::Buffer::Priority aPriority,
                                           Spinel::Buffer *         aBuffer)
@@ -106,27 +104,27 @@
     OT_UNUSED_VARIABLE(aTag);
     OT_UNUSED_VARIABLE(aPriority);
 
-    static_cast<NcpUart *>(aContext)->HandleFrameAddedToNcpBuffer();
+    static_cast<NcpHdlc *>(aContext)->HandleFrameAddedToNcpBuffer();
 }
 
-void NcpUart::HandleFrameAddedToNcpBuffer(void)
+void NcpHdlc::HandleFrameAddedToNcpBuffer(void)
 {
-    if (mUartBuffer.IsEmpty())
+    if (mHdlcBuffer.IsEmpty())
     {
-        mUartSendTask.Post();
+        mHdlcSendTask.Post();
     }
 }
 
-void NcpUart::EncodeAndSendToUart(Tasklet &aTasklet)
+void NcpHdlc::EncodeAndSend(Tasklet &aTasklet)
 {
     OT_UNUSED_VARIABLE(aTasklet);
-    static_cast<NcpUart *>(GetNcpInstance())->EncodeAndSendToUart();
+    static_cast<NcpHdlc *>(GetNcpInstance())->EncodeAndSend();
 }
 
 // This method encodes a frame from the tx frame buffer (mTxFrameBuffer) into the uart buffer and sends it over uart.
 // If the uart buffer gets full, it sends the current encoded portion. This method remembers current state, so on
 // sub-sequent calls, it restarts encoding the bytes from where it left of in the frame .
-void NcpUart::EncodeAndSendToUart(void)
+void NcpHdlc::EncodeAndSend(void)
 {
     uint16_t len;
     bool     prevHostPowerState;
@@ -158,6 +156,8 @@
             {
                 mByte = txFrameBuffer.OutFrameReadByte();
 
+                OT_FALL_THROUGH;
+
             case kEncodingFrame:
 
                 SuccessOrExit(mFrameEncoder.Encode(mByte));
@@ -173,15 +173,15 @@
             {
                 // If mHostPowerStateInProgress transitioned from true -> false
                 // in the call to OutFrameRemove, then the frame should be sent
-                // out the UART without attempting to push any new frames into
-                // the mUartBuffer. This is necessary to avoid prematurely calling
+                // out without attempting to push any new frames into the
+                // mHdlcBuffer. This is necessary to avoid prematurely calling
                 // otPlatWakeHost.
-                mUartSendImmediate = true;
+                mHdlcSendImmediate = true;
             }
 
             mState = kFinalizingFrame;
 
-            // fall through
+            OT_FALL_THROUGH;
 
         case kFinalizingFrame:
 
@@ -189,64 +189,63 @@
 
             mState = kStartingFrame;
 
-            if (mUartSendImmediate)
+            if (mHdlcSendImmediate)
             {
                 // clear state and break;
-                mUartSendImmediate = false;
+                mHdlcSendImmediate = false;
                 break;
             }
         }
     }
 
 exit:
-    len = mUartBuffer.GetLength();
+    len = mHdlcBuffer.GetLength();
 
     if (len > 0)
     {
-        if (otPlatUartSend(mUartBuffer.GetFrame(), len) != OT_ERROR_NONE)
-        {
-            OT_ASSERT(false);
-        }
+        int rval = mSendCallback(mHdlcBuffer.GetFrame(), len);
+        OT_UNUSED_VARIABLE(rval);
+        OT_ASSERT(rval == static_cast<int>(len));
     }
 }
 
-extern "C" void otPlatUartSendDone(void)
+extern "C" void otNcpHdlcSendDone(void)
 {
-    NcpUart *ncpUart = static_cast<NcpUart *>(NcpBase::GetNcpInstance());
+    NcpHdlc *ncpHdlc = static_cast<NcpHdlc *>(NcpBase::GetNcpInstance());
 
-    if (ncpUart != nullptr)
+    if (ncpHdlc != nullptr)
     {
-        ncpUart->HandleUartSendDone();
+        ncpHdlc->HandleHdlcSendDone();
     }
 }
 
-void NcpUart::HandleUartSendDone(void)
+void NcpHdlc::HandleHdlcSendDone(void)
 {
-    mUartBuffer.Clear();
-    mUartSendTask.Post();
+    mHdlcBuffer.Clear();
+    mHdlcSendTask.Post();
 }
 
-extern "C" void otPlatUartReceived(const uint8_t *aBuf, uint16_t aBufLength)
+extern "C" void otNcpHdlcReceive(const uint8_t *aBuf, uint16_t aBufLength)
 {
-    NcpUart *ncpUart = static_cast<NcpUart *>(NcpBase::GetNcpInstance());
+    NcpHdlc *ncpHdlc = static_cast<NcpHdlc *>(NcpBase::GetNcpInstance());
 
-    if (ncpUart != nullptr)
+    if (ncpHdlc != nullptr)
     {
-        ncpUart->HandleUartReceiveDone(aBuf, aBufLength);
+        ncpHdlc->HandleHdlcReceiveDone(aBuf, aBufLength);
     }
 }
 
-void NcpUart::HandleUartReceiveDone(const uint8_t *aBuf, uint16_t aBufLength)
+void NcpHdlc::HandleHdlcReceiveDone(const uint8_t *aBuf, uint16_t aBufLength)
 {
     mFrameDecoder.Decode(aBuf, aBufLength);
 }
 
-void NcpUart::HandleFrame(void *aContext, otError aError)
+void NcpHdlc::HandleFrame(void *aContext, otError aError)
 {
-    static_cast<NcpUart *>(aContext)->HandleFrame(aError);
+    static_cast<NcpHdlc *>(aContext)->HandleFrame(aError);
 }
 
-void NcpUart::HandleFrame(otError aError)
+void NcpHdlc::HandleFrame(otError aError)
 {
     uint8_t *buf       = mRxBuffer.GetFrame();
     uint16_t bufLength = mRxBuffer.GetLength();
@@ -271,7 +270,7 @@
     mRxBuffer.Clear();
 }
 
-void NcpUart::HandleError(otError aError, uint8_t *aBuf, uint16_t aBufLength)
+void NcpHdlc::HandleError(otError aError, uint8_t *aBuf, uint16_t aBufLength)
 {
     char     hexbuf[128];
     uint16_t i = 0;
@@ -306,19 +305,19 @@
 
 #if OPENTHREAD_ENABLE_NCP_SPINEL_ENCRYPTER
 
-NcpUart::BufferEncrypterReader::BufferEncrypterReader(Spinel::Buffer &aTxFrameBuffer)
+NcpHdlc::BufferEncrypterReader::BufferEncrypterReader(Spinel::Buffer &aTxFrameBuffer)
     : mTxFrameBuffer(aTxFrameBuffer)
     , mDataBufferReadIndex(0)
     , mOutputDataLength(0)
 {
 }
 
-bool NcpUart::BufferEncrypterReader::IsEmpty(void) const
+bool NcpHdlc::BufferEncrypterReader::IsEmpty(void) const
 {
     return mTxFrameBuffer.IsEmpty() && !mOutputDataLength;
 }
 
-otError NcpUart::BufferEncrypterReader::OutFrameBegin(void)
+otError NcpHdlc::BufferEncrypterReader::OutFrameBegin(void)
 {
     otError status = OT_ERROR_FAILED;
 
@@ -348,22 +347,22 @@
     return status;
 }
 
-bool NcpUart::BufferEncrypterReader::OutFrameHasEnded(void)
+bool NcpHdlc::BufferEncrypterReader::OutFrameHasEnded(void)
 {
     return (mDataBufferReadIndex >= mOutputDataLength);
 }
 
-uint8_t NcpUart::BufferEncrypterReader::OutFrameReadByte(void)
+uint8_t NcpHdlc::BufferEncrypterReader::OutFrameReadByte(void)
 {
     return mDataBuffer[mDataBufferReadIndex++];
 }
 
-otError NcpUart::BufferEncrypterReader::OutFrameRemove(void)
+otError NcpHdlc::BufferEncrypterReader::OutFrameRemove(void)
 {
     return mTxFrameBuffer.OutFrameRemove();
 }
 
-void NcpUart::BufferEncrypterReader::Reset(void)
+void NcpHdlc::BufferEncrypterReader::Reset(void)
 {
     mOutputDataLength    = 0;
     mDataBufferReadIndex = 0;
@@ -374,4 +373,4 @@
 } // namespace Ncp
 } // namespace ot
 
-#endif // OPENTHREAD_CONFIG_NCP_UART_ENABLE
+#endif // OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
diff --git a/src/ncp/ncp_uart.hpp b/src/ncp/ncp_hdlc.hpp
similarity index 76%
rename from src/ncp/ncp_uart.hpp
rename to src/ncp/ncp_hdlc.hpp
index 5892c2f..e3c489f 100644
--- a/src/ncp/ncp_uart.hpp
+++ b/src/ncp/ncp_hdlc.hpp
@@ -27,11 +27,11 @@
 
 /**
  * @file
- *   This file contains definitions for a UART based NCP interface to the OpenThread stack.
+ *   This file contains definitions for a HDLC based NCP interface to the OpenThread stack.
  */
 
-#ifndef NCP_UART_HPP_
-#define NCP_UART_HPP_
+#ifndef NCP_HDLC_HPP_
+#define NCP_HDLC_HPP_
 
 #include "openthread-core-config.h"
 
@@ -45,7 +45,7 @@
 namespace ot {
 namespace Ncp {
 
-class NcpUart : public NcpBase
+class NcpHdlc : public NcpBase
 {
     typedef NcpBase super_t;
 
@@ -56,29 +56,29 @@
      * @param[in]  aInstance  The OpenThread instance structure.
      *
      */
-    explicit NcpUart(Instance *aInstance);
+    explicit NcpHdlc(Instance *aInstance, otNcpHdlcSendCallback aSendCallback);
 
     /**
      * This method is called when uart tx is finished. It prepares and sends the next data chunk (if any) to uart.
      *
      */
-    void HandleUartSendDone(void);
+    void HandleHdlcSendDone(void);
 
     /**
      * This method is called when uart received a data buffer.
      *
      */
-    void HandleUartReceiveDone(const uint8_t *aBuf, uint16_t aBufLength);
+    void HandleHdlcReceiveDone(const uint8_t *aBuf, uint16_t aBufLength);
 
 private:
     enum
     {
-        kUartTxBufferSize = OPENTHREAD_CONFIG_NCP_UART_TX_CHUNK_SIZE,   // Uart tx buffer size.
-        kRxBufferSize     = OPENTHREAD_CONFIG_NCP_UART_RX_BUFFER_SIZE + // Rx buffer size (should be large enough to fit
+        kHdlcTxBufferSize = OPENTHREAD_CONFIG_NCP_HDLC_TX_CHUNK_SIZE,   // HDLC tx buffer size.
+        kRxBufferSize     = OPENTHREAD_CONFIG_NCP_HDLC_RX_BUFFER_SIZE + // Rx buffer size (should be large enough to fit
                         OPENTHREAD_CONFIG_NCP_SPINEL_ENCRYPTER_EXTRA_DATA_SIZE, // one whole (decoded) received frame).
     };
 
-    enum UartTxState
+    enum HdlcTxState
     {
         kStartingFrame,   // Starting a new frame.
         kEncodingFrame,   // In middle of encoding a frame.
@@ -114,27 +114,28 @@
     };
 #endif // OPENTHREAD_ENABLE_NCP_SPINEL_ENCRYPTER
 
-    void EncodeAndSendToUart(void);
+    void EncodeAndSend(void);
     void HandleFrame(otError aError);
     void HandleError(otError aError, uint8_t *aBuf, uint16_t aBufLength);
     void TxFrameBufferHasData(void);
     void HandleFrameAddedToNcpBuffer(void);
 
-    static void EncodeAndSendToUart(Tasklet &aTasklet);
-    static void HandleFrame(void *aContext, otError aError);
-    static void HandleFrameAddedToNcpBuffer(void *                   aContext,
-                                            Spinel::Buffer::FrameTag aTag,
-                                            Spinel::Buffer::Priority aPriority,
-                                            Spinel::Buffer *         aBuffer);
+    static void           EncodeAndSend(Tasklet &aTasklet);
+    static void           HandleFrame(void *aContext, otError aError);
+    static void           HandleFrameAddedToNcpBuffer(void *                   aContext,
+                                                      Spinel::Buffer::FrameTag aTag,
+                                                      Spinel::Buffer::Priority aPriority,
+                                                      Spinel::Buffer *         aBuffer);
+    otNcpHdlcSendCallback mSendCallback;
 
     Hdlc::Encoder                        mFrameEncoder;
     Hdlc::Decoder                        mFrameDecoder;
-    Hdlc::FrameBuffer<kUartTxBufferSize> mUartBuffer;
-    UartTxState                          mState;
+    Hdlc::FrameBuffer<kHdlcTxBufferSize> mHdlcBuffer;
+    HdlcTxState                          mState;
     uint8_t                              mByte;
     Hdlc::FrameBuffer<kRxBufferSize>     mRxBuffer;
-    bool                                 mUartSendImmediate;
-    Tasklet                              mUartSendTask;
+    bool                                 mHdlcSendImmediate;
+    Tasklet                              mHdlcSendTask;
 
 #if OPENTHREAD_ENABLE_NCP_SPINEL_ENCRYPTER
     BufferEncrypterReader mTxFrameBufferEncrypterReader;
@@ -144,4 +145,4 @@
 } // namespace Ncp
 } // namespace ot
 
-#endif // NCP_UART_HPP_
+#endif // NCP_HDLC_HPP_
diff --git a/src/ncp/ncp_spi.cpp b/src/ncp/ncp_spi.cpp
index aa044c5..618769b 100644
--- a/src/ncp/ncp_spi.cpp
+++ b/src/ncp/ncp_spi.cpp
@@ -62,7 +62,7 @@
 
 static OT_DEFINE_ALIGNED_VAR(sNcpRaw, sizeof(NcpSpi), uint64_t);
 
-extern "C" void otNcpInit(otInstance *aInstance)
+extern "C" void otNcpSpiInit(otInstance *aInstance)
 {
     NcpSpi *  ncpSpi   = nullptr;
     Instance *instance = static_cast<Instance *>(aInstance);
@@ -82,7 +82,7 @@
     , mTxState(kTxStateIdle)
     , mHandlingRxFrame(false)
     , mResetFlag(true)
-    , mPrepareTxFrameTask(*aInstance, NcpSpi::PrepareTxFrame, this)
+    , mPrepareTxFrameTask(*aInstance, NcpSpi::PrepareTxFrame)
     , mSendFrameLength(0)
 {
     SpiFrame sendFrame(mSendFrame);
@@ -326,7 +326,7 @@
     case kTxStateHandlingSendDone:
         mTxState = kTxStateIdle;
 
-        // Fall through
+        OT_FALL_THROUGH;
         // to next case to prepare the next frame (if any).
 
     case kTxStateIdle:
diff --git a/src/ncp/radio.cmake b/src/ncp/radio.cmake
index 212a8c0..502961a 100644
--- a/src/ncp/radio.cmake
+++ b/src/ncp/radio.cmake
@@ -37,7 +37,7 @@
 
 target_compile_definitions(openthread-rcp PRIVATE
     OPENTHREAD_RADIO=1
-    OPENTHREAD_CONFIG_NCP_UART_ENABLE=1
+    OPENTHREAD_CONFIG_NCP_HDLC_ENABLE=1
 )
 
 target_compile_options(openthread-rcp PRIVATE
diff --git a/src/posix/CMakeLists.txt b/src/posix/CMakeLists.txt
index 978bb9f..575271a 100644
--- a/src/posix/CMakeLists.txt
+++ b/src/posix/CMakeLists.txt
@@ -26,10 +26,6 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
-target_compile_definitions(ot-config INTERFACE
-    OPENTHREAD_CONFIG_UART_CLI_RAW=1
-)
-
 set(COMMON_INCLUDES
     ${OT_PUBLIC_INCLUDES}
     ${PROJECT_SOURCE_DIR}/src
@@ -38,15 +34,32 @@
     ${PROJECT_SOURCE_DIR}/src/posix/platform/include
 )
 
-set(OT_READLINE "readline" CACHE STRING "set readline library name")
-set_property(CACHE OT_READLINE PROPERTY STRINGS "readline" "edit")
+set(OT_READLINE_VALUES
+    "readline"
+    "edit"
+)
 
-if(OT_READLINE)
+set(OT_READLINE "" CACHE STRING "set readline library name")
+set_property(CACHE OT_READLINE PROPERTY STRINGS ${OT_READLINE_VALUES})
+
+if(OT_READLINE STREQUAL "")
+    foreach(X IN LISTS OT_READLINE_VALUES)
+        find_library(READLINE ${X})
+        if (READLINE)
+            set(OT_READLINE ${X})
+            break()
+        endif()
+    endforeach()
+elseif(OT_READLINE)
     find_library(READLINE ${OT_READLINE})
 
     if (NOT READLINE)
         message(FATAL_ERROR "Failed to find ${OT_READLINE}")
     endif()
+endif()
+
+if (READLINE)
+    message(STATUS "Readline: ${OT_READLINE}")
 
     find_library(NCURSES ncurses)
     if (NOT NCURSES)
diff --git a/src/posix/Makefile-posix b/src/posix/Makefile-posix
index 31a071a..b4c9783 100644
--- a/src/posix/Makefile-posix
+++ b/src/posix/Makefile-posix
@@ -45,10 +45,12 @@
 CHANNEL_MONITOR                      ?= 1
 CHILD_SUPERVISION                    ?= 1
 DAEMON                               ?= 0
+DATASET_UPDATER                      ?= 1
 DHCP6_CLIENT                         ?= 1
 DHCP6_SERVER                         ?= 1
 DIAGNOSTIC                           ?= 1
 DNS_CLIENT                           ?= 1
+DNSSD_SERVER                         ?= 1
 DYNAMIC_LOG_LEVEL                    ?= 1
 ECDSA                                ?= 1
 IP6_FRAGM                            ?= 1
@@ -60,17 +62,19 @@
 MAC_FILTER                           ?= 1
 MAX_POWER_TABLE                      ?= 1
 MTD_NETDIAG                          ?= 1
+PING_SENDER                          ?= 1
 READLINE                             ?= readline
 REFERENCE_DEVICE                     ?= 1
 SERVICE                              ?= 1
 SNTP_CLIENT                          ?= 1
+SRP_CLIENT                           ?= 1
+SRP_SERVER                           ?= 1
 ifneq ($(DAEMON),1)
 UDP_FORWARD                          ?= 1
 endif
 
 COMMONCFLAGS                         := \
     -g                                  \
-    -DOPENTHREAD_CONFIG_UART_CLI_RAW=1  \
     $(NULL)
 
 # If the user has asserted COVERAGE, alter the configuration options
@@ -109,7 +113,6 @@
 
 ifneq ($(READLINE),)
 configure_OPTIONS              += --with-readline=$(READLINE)
-CLI_TRANSPORT                   = $(if $(and $(filter-out no,$(READLINE)),$(filter 0,$(DAEMON))),CONSOLE,UART)
 endif
 
 ifeq ($(RCP_BUS),spi)
diff --git a/src/posix/Makefile.am b/src/posix/Makefile.am
index d9e17d4..e20aa6c 100644
--- a/src/posix/Makefile.am
+++ b/src/posix/Makefile.am
@@ -82,6 +82,7 @@
 
 ot_ncp_SOURCES                                                         = \
     main.c                                                               \
+    ncp.cpp                                                              \
     $(NULL)
 
 ot_ncp_LDADD                                                           = \
@@ -162,7 +163,7 @@
 
 ot_cli_SOURCES                                                         = \
     main.c                                                               \
-    console_cli.cpp                                                      \
+    cli.cpp                                                              \
     $(NULL)
 
 ot_cli_LDADD                                                           = \
@@ -198,8 +199,4 @@
 endif # OPENTHREAD_BUILD_COVERAGE
 endif # OPENTHREAD_ENABLE_EXECUTABLE
 
-noinst_HEADERS                                                         = \
-    console_cli.h                                                        \
-    $(NULL)
-
 include $(abs_top_nlbuild_autotools_dir)/automake/post.am
diff --git a/src/posix/cli.cmake b/src/posix/cli.cmake
index eef034e..cfb5dc3 100644
--- a/src/posix/cli.cmake
+++ b/src/posix/cli.cmake
@@ -28,7 +28,7 @@
 
 add_executable(ot-cli
     main.c
-    $<$<BOOL:${READLINE}>:console_cli.cpp>
+    cli.cpp
 )
 
 set_target_properties(
@@ -40,10 +40,6 @@
 
 target_include_directories(ot-cli PRIVATE ${COMMON_INCLUDES})
 
-if(OT_READLINE)
-    set(OT_CLI_TRANSPORT "CONSOLE" CACHE STRING "set CLI to use console interpreter" FORCE)
-endif()
-
 target_compile_definitions(ot-cli PRIVATE
     $<$<BOOL:${READLINE}>:HAVE_LIB$<UPPER_CASE:${OT_READLINE}>=1>
     OPENTHREAD_POSIX_APP_TYPE=OT_POSIX_APP_TYPE_CLI
diff --git a/src/posix/console_cli.cpp b/src/posix/cli.cpp
similarity index 61%
rename from src/posix/console_cli.cpp
rename to src/posix/cli.cpp
index 735e983..4949de5 100644
--- a/src/posix/console_cli.cpp
+++ b/src/posix/cli.cpp
@@ -26,13 +26,15 @@
  *  POSSIBILITY OF SUCH DAMAGE.
  */
 
-#include "console_cli.h"
+#include "platform/openthread-posix-config.h"
 
-#if OPENTHREAD_CONFIG_CLI_TRANSPORT == OT_CLI_TRANSPORT_CONSOLE
+#if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
 
 #include <openthread/config.h>
+#include <openthread/openthread-system.h>
 
 #include <assert.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <string.h>
 #include <unistd.h>
@@ -46,34 +48,36 @@
 #endif
 
 #if HAVE_LIBEDIT || HAVE_LIBREADLINE
+#define OPENTHREAD_USE_READLINE 1
 #if HAVE_LIBEDIT
 #include <editline/readline.h>
 #elif HAVE_LIBREADLINE
 #include <readline/history.h>
 #include <readline/readline.h>
+#endif
 #else
-#error "Missing readline library"
+#define OPENTHREAD_USE_READLINE 0
 #endif
 
 #include <openthread/cli.h>
 
+#include "cli/cli_config.h"
+#include "common/code_utils.hpp"
+
 #include "openthread-core-config.h"
 #include "platform-posix.h"
 
 static const char sPrompt[] = "> ";
 
-static int sReadFd;
-
+#if OPENTHREAD_USE_READLINE
 static void InputCallback(char *aLine)
 {
     if (aLine != nullptr)
     {
-        size_t len;
-
-        if ((len = strlen(aLine)) > 0)
+        if (aLine[0] != '\0')
         {
             add_history(aLine);
-            otCliConsoleInputLine(aLine, (uint16_t)len);
+            otCliInputLine(aLine);
         }
         free(aLine);
     }
@@ -82,54 +86,72 @@
         exit(OT_EXIT_SUCCESS);
     }
 }
+#endif
 
-static int OutputCallback(const char *aBuffer, uint16_t aLength, void *aContext)
+static int OutputCallback(void *aContext, const char *aFormat, va_list aArguments)
 {
-    (void)aContext;
+    OT_UNUSED_VARIABLE(aContext);
 
-    return (int)write(STDOUT_FILENO, aBuffer, aLength);
+    return vdprintf(STDOUT_FILENO, aFormat, aArguments);
 }
 
-void otxConsoleInit(otInstance *aInstance)
+extern "C" void otAppCliInit(otInstance *aInstance)
 {
+#if OPENTHREAD_USE_READLINE
     rl_instream           = stdin;
     rl_outstream          = stdout;
     rl_inhibit_completion = true;
-    sReadFd               = fileno(rl_instream);
+
+    rl_set_screen_size(0, OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH);
+
     rl_callback_handler_install(sPrompt, InputCallback);
-    otCliConsoleInit(aInstance, OutputCallback, nullptr);
+#endif
+    otCliInit(aInstance, OutputCallback, nullptr);
 }
 
-void otxConsoleDeinit(void)
+extern "C" void otAppCliDeinit(void)
 {
+#if OPENTHREAD_USE_READLINE
     rl_callback_handler_remove();
+#endif
 }
 
-void otxConsoleUpdate(otSysMainloopContext *aMainloop)
+extern "C" void otAppCliUpdate(otSysMainloopContext *aMainloop)
 {
-    FD_SET(sReadFd, &aMainloop->mReadFdSet);
-    FD_SET(sReadFd, &aMainloop->mErrorFdSet);
+    FD_SET(STDIN_FILENO, &aMainloop->mReadFdSet);
+    FD_SET(STDIN_FILENO, &aMainloop->mErrorFdSet);
 
-    if (aMainloop->mMaxFd < sReadFd)
+    if (aMainloop->mMaxFd < STDIN_FILENO)
     {
-        aMainloop->mMaxFd = sReadFd;
+        aMainloop->mMaxFd = STDIN_FILENO;
     }
 }
 
-void otxConsoleProcess(const otSysMainloopContext *aMainloop)
+extern "C" void otAppCliProcess(const otSysMainloopContext *aMainloop)
 {
-    if (FD_ISSET(sReadFd, &aMainloop->mErrorFdSet))
+    if (FD_ISSET(STDIN_FILENO, &aMainloop->mErrorFdSet))
     {
-        perror("console error");
         exit(OT_EXIT_FAILURE);
     }
 
-    if (FD_ISSET(sReadFd, &aMainloop->mReadFdSet))
+    if (FD_ISSET(STDIN_FILENO, &aMainloop->mReadFdSet))
     {
+#if OPENTHREAD_USE_READLINE
         rl_callback_read_char();
+#else
+        char buffer[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH];
+
+        if (fgets(buffer, sizeof(buffer), stdin) != nullptr)
+        {
+            otCliInputLine(buffer);
+            dprintf(STDOUT_FILENO, "%s", sPrompt);
+        }
+        else
+        {
+            exit(OT_EXIT_SUCCESS);
+        }
+#endif
     }
 }
 
-#endif // HAVE_LIBEDIT || HAVE_LIBREADLINE
-
-#endif // OPENTHREAD_CONFIG_CLI_TRANSPORT == OT_CLI_TRANSPORT_CONSOLE
+#endif // !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
diff --git a/src/posix/client.cpp b/src/posix/client.cpp
index 860b228..1491b0b 100644
--- a/src/posix/client.cpp
+++ b/src/posix/client.cpp
@@ -28,6 +28,8 @@
 
 #include "platform/openthread-posix-config.h"
 
+#include "cli/cli_config.h"
+
 #include <openthread/platform/toolchain.h>
 
 #ifndef HAVE_LIBEDIT
@@ -62,7 +64,7 @@
 
 enum
 {
-    kLineBufferSize = 256,
+    kLineBufferSize = OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH,
 };
 
 static_assert(kLineBufferSize >= sizeof("> "), "kLineBufferSize is too small");
@@ -161,23 +163,28 @@
 
 int main(int argc, char *argv[])
 {
-    int    ret;
-    bool   isInteractive = true;
-    bool   isFinished    = false;
-    char   lineBuffer[kLineBufferSize];
-    size_t lineBufferWritePos = 0;
-    bool   isBeginOfLine      = true;
+    int  ret;
+    bool isInteractive = true;
+    bool isFinished    = false;
 
     VerifyOrExit(ConnectSession() != -1, perror("connect session failed"); ret = OT_EXIT_FAILURE);
 
     if (argc > 1)
     {
+        char   buffer[kLineBufferSize];
+        size_t count = 0;
+
         for (int i = 1; i < argc; i++)
         {
-            VerifyOrExit(DoWrite(sSessionFd, argv[i], strlen(argv[i])), ret = OT_EXIT_FAILURE);
-            VerifyOrExit(DoWrite(sSessionFd, " ", 1), ret = OT_EXIT_FAILURE);
+            int rval = snprintf(&buffer[count], (sizeof(buffer) - count), "%s ", argv[i]);
+
+            VerifyOrExit(rval > 0 && static_cast<size_t>(rval) < (sizeof(buffer) - count), ret = OT_EXIT_FAILURE);
+            count += static_cast<size_t>(rval);
         }
-        VerifyOrExit(DoWrite(sSessionFd, "\n", 1), ret = OT_EXIT_FAILURE);
+
+        // replace the trailing space with newline
+        buffer[count - 1] = '\n';
+        VerifyOrExit(DoWrite(sSessionFd, buffer, count), ret = OT_EXIT_FAILURE);
 
         isInteractive = false;
     }
@@ -195,8 +202,11 @@
     while (!isFinished)
     {
         fd_set readFdSet;
-        char   buffer[OPENTHREAD_CONFIG_DIAG_CMD_LINE_BUFFER_SIZE];
-        int    maxFd = sSessionFd;
+        char   lineBuffer[kLineBufferSize];
+        char   buffer[kLineBufferSize];
+        size_t lineBufferWritePos = 0;
+        bool   isBeginOfLine      = true;
+        int    maxFd              = sSessionFd;
 
         FD_ZERO(&readFdSet);
 
diff --git a/src/posix/console_cli.h b/src/posix/console_cli.h
deleted file mode 100644
index 16682d5..0000000
--- a/src/posix/console_cli.h
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- *  Copyright (c) 2019, The OpenThread Authors.
- *  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.
- */
-
-#ifndef OPENTHREAD_CONSOLE_CLI_H_
-#define OPENTHREAD_CONSOLE_CLI_H_
-
-#include "platform/openthread-posix-config.h"
-
-#include <stdint.h>
-
-#include <openthread/openthread-system.h>
-
-#include "cli/cli_config.h"
-
-#ifndef HAVE_LIBEDIT
-#define HAVE_LIBEDIT 0
-#endif
-
-#ifndef HAVE_LIBREADLINE
-#define HAVE_LIBREADLINE 0
-#endif
-
-#if OPENTHREAD_CONFIG_CLI_TRANSPORT == OT_CLI_TRANSPORT_CONSOLE
-#define OPENTHREAD_USE_CONSOLE 1
-#if !(HAVE_LIBEDIT || HAVE_LIBREADLINE) || OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-#error \
-    "When OPENTHREAD_CONFIG_CLI_TRANSPORT=OT_CLI_TRANSPORT_CONSOLE, HAVE_LIBEDIT or HAVE_LIBREADLINE MUST be defined and OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE MUST be 0"
-#endif
-#endif
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * This function initializes CLI console.
- *
- * @param[in]  aInstance    A pointer to the OpenThread instance.
- *
- */
-void otxConsoleInit(otInstance *aInstance);
-
-/**
- * This function deinitializes CLI console
- *
- */
-void otxConsoleDeinit(void);
-
-/**
- * This function updates the file descriptor sets with file descriptors used by console.
- *
- * @param[inout]    aMainloop   A pointer to the mainloop context.
- *
- */
-void otxConsoleUpdate(otSysMainloopContext *aMainloop);
-
-/**
- * This function performs console driver processing.
- *
- * @param[in]    aMainloop      A pointer to the mainloop context.
- *
- */
-void otxConsoleProcess(const otSysMainloopContext *aMainloop);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // OPENTHREAD_CONSOLE_CLI_H_
diff --git a/src/posix/main.c b/src/posix/main.c
index dc61485..da5006a 100644
--- a/src/posix/main.c
+++ b/src/posix/main.c
@@ -67,7 +67,6 @@
 #elif OPENTHREAD_POSIX_APP_TYPE == OT_POSIX_APP_TYPE_CLI
 #include <openthread/cli.h>
 
-#include "console_cli.h"
 #include "cli/cli_config.h"
 #else
 #error "Unknown posix app type!"
@@ -82,6 +81,58 @@
 #define OPENTHREAD_ENABLE_COVERAGE 0
 #endif
 
+/**
+ * This function initializes NCP app.
+ *
+ * @param[in]  aInstance    A pointer to the OpenThread instance.
+ *
+ */
+void otAppNcpInit(otInstance *aInstance);
+
+/**
+ * This function deinitializes NCP app.
+ *
+ */
+void otAppNcpUpdate(otSysMainloopContext *aContext);
+
+/**
+ * This function updates the file descriptor sets with file descriptors used by console.
+ *
+ * @param[inout]    aMainloop   A pointer to the mainloop context.
+ *
+ */
+void otAppNcpProcess(const otSysMainloopContext *aContext);
+
+/**
+ * This function initializes CLI app.
+ *
+ * @param[in]  aInstance    A pointer to the OpenThread instance.
+ *
+ */
+void otAppCliInit(otInstance *aInstance);
+
+/**
+ * This function deinitializes CLI app.
+ *
+ */
+void otAppCliDeinit(void);
+
+/**
+ * This function updates the file descriptor sets with file descriptors used by console.
+ *
+ * @param[inout]    aMainloop   A pointer to the mainloop context.
+ *
+ */
+void otAppCliUpdate(otSysMainloopContext *aMainloop);
+
+/**
+ * This function performs console driver processing.
+ *
+ * @param[in]    aMainloop      A pointer to the mainloop context.
+ *
+ */
+void otAppCliProcess(const otSysMainloopContext *aMainloop);
+
 typedef struct PosixConfig
 {
     otPlatformConfig mPlatformConfig;    ///< Platform configuration.
@@ -330,12 +381,10 @@
     instance = InitInstance(&config);
 
 #if OPENTHREAD_POSIX_APP_TYPE == OT_POSIX_APP_TYPE_NCP
-    otNcpInit(instance);
+    otAppNcpInit(instance);
 #elif OPENTHREAD_POSIX_APP_TYPE == OT_POSIX_APP_TYPE_CLI
-#ifdef OPENTHREAD_USE_CONSOLE
-    otxConsoleInit(instance);
-#else
-    otCliUartInit(instance);
+#if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
+    otAppCliInit(instance);
 #endif
     otCliSetUserCommands(&radioUrlCommand, 1, &config.mPlatformConfig);
 #endif
@@ -354,8 +403,12 @@
         mainloop.mTimeout.tv_sec  = 10;
         mainloop.mTimeout.tv_usec = 0;
 
-#ifdef OPENTHREAD_USE_CONSOLE
-        otxConsoleUpdate(&mainloop);
+#if OPENTHREAD_POSIX_APP_TYPE == OT_POSIX_APP_TYPE_NCP
+        otAppNcpUpdate(&mainloop);
+#elif OPENTHREAD_POSIX_APP_TYPE == OT_POSIX_APP_TYPE_CLI
+#if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
+        otAppCliUpdate(&mainloop);
+#endif
 #endif
 
         otSysMainloopUpdate(instance, &mainloop);
@@ -363,8 +416,12 @@
         if (otSysMainloopPoll(&mainloop) >= 0)
         {
             otSysMainloopProcess(instance, &mainloop);
-#ifdef OPENTHREAD_USE_CONSOLE
-            otxConsoleProcess(&mainloop);
+#if OPENTHREAD_POSIX_APP_TYPE == OT_POSIX_APP_TYPE_NCP
+            otAppNcpProcess(&mainloop);
+#elif OPENTHREAD_POSIX_APP_TYPE == OT_POSIX_APP_TYPE_CLI
+#if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
+            otAppCliProcess(&mainloop);
+#endif
 #endif
         }
         else if (errno != EINTR)
@@ -374,8 +431,12 @@
         }
     }
 
-#ifdef OPENTHREAD_USE_CONSOLE
-    otxConsoleDeinit();
+#if OPENTHREAD_POSIX_APP_TYPE == OT_POSIX_APP_TYPE_NCP
+    // disable ncp
+#elif OPENTHREAD_POSIX_APP_TYPE == OT_POSIX_APP_TYPE_CLI
+#if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
+    otAppCliDeinit();
+#endif
 #endif
 
 exit:
diff --git a/src/posix/ncp.cmake b/src/posix/ncp.cmake
index 651b80b..e313059 100644
--- a/src/posix/ncp.cmake
+++ b/src/posix/ncp.cmake
@@ -28,6 +28,7 @@
 
 add_executable(ot-ncp
     main.c
+    ncp.cpp
 )
 
 set_target_properties(
diff --git a/src/posix/ncp.cpp b/src/posix/ncp.cpp
new file mode 100644
index 0000000..868b71c
--- /dev/null
+++ b/src/posix/ncp.cpp
@@ -0,0 +1,132 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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.
+ */
+
+#include "openthread-posix-config.h"
+#include "platform-posix.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <openthread/ncp.h>
+#include <openthread/platform/misc.h>
+
+#include "common/code_utils.hpp"
+
+#if OPENTHREAD_POSIX_APP_TYPE == OT_POSIX_APP_TYPE_NCP
+static const uint8_t *sWriteBuffer = nullptr;
+static uint16_t       sWriteLength = 0;
+
+static int ncpHdlcSend(const uint8_t *aBuf, uint16_t aBufLength)
+{
+    sWriteBuffer = aBuf;
+    sWriteLength = aBufLength;
+
+    return aBufLength;
+}
+
+extern "C" void otAppNcpInit(otInstance *aInstance)
+{
+    otNcpHdlcInit(aInstance, ncpHdlcSend);
+}
+
+extern "C" void otAppNcpUpdate(otSysMainloopContext *aContext)
+{
+    FD_SET(STDIN_FILENO, &aContext->mReadFdSet);
+    FD_SET(STDIN_FILENO, &aContext->mErrorFdSet);
+
+    if (aContext->mMaxFd < STDIN_FILENO)
+    {
+        aContext->mMaxFd = STDIN_FILENO;
+    }
+
+    if (sWriteLength > 0)
+    {
+        FD_SET(STDOUT_FILENO, &aContext->mWriteFdSet);
+        FD_SET(STDOUT_FILENO, &aContext->mErrorFdSet);
+
+        if (aContext->mMaxFd < STDOUT_FILENO)
+        {
+            aContext->mMaxFd = STDOUT_FILENO;
+        }
+    }
+}
+
+extern "C" void otAppNcpProcess(const otSysMainloopContext *aContext)
+{
+    ssize_t rval;
+
+    if (FD_ISSET(STDIN_FILENO, &aContext->mErrorFdSet))
+    {
+        DieNowWithMessage("stdin", OT_EXIT_FAILURE);
+    }
+
+    if (FD_ISSET(STDOUT_FILENO, &aContext->mErrorFdSet))
+    {
+        DieNowWithMessage("stdout", OT_EXIT_FAILURE);
+    }
+
+    if (FD_ISSET(STDIN_FILENO, &aContext->mReadFdSet))
+    {
+        uint8_t buffer[256];
+
+        rval = read(STDIN_FILENO, buffer, sizeof(buffer));
+
+        if (rval > 0)
+        {
+            otNcpHdlcReceive(buffer, static_cast<uint16_t>(rval));
+        }
+        else if (rval <= 0)
+        {
+            DieNowWithMessage("UART read", (rval < 0) ? OT_EXIT_ERROR_ERRNO : OT_EXIT_FAILURE);
+        }
+    }
+
+    if ((FD_ISSET(STDOUT_FILENO, &aContext->mWriteFdSet)))
+    {
+        if (sWriteLength > 0)
+        {
+            rval = write(STDOUT_FILENO, sWriteBuffer, sWriteLength);
+
+            if (rval < 0)
+            {
+                DieNow(OT_EXIT_ERROR_ERRNO);
+            }
+
+            sWriteBuffer += rval;
+            sWriteLength -= static_cast<uint16_t>(rval);
+        }
+
+        if (sWriteLength == 0)
+        {
+            otNcpHdlcSendDone();
+        }
+    }
+}
+#endif // OPENTHREAD_POSIX_APP_TYPE == OT_POSIX_APP_TYPE_NCP
diff --git a/src/posix/platform/CMakeLists.txt b/src/posix/platform/CMakeLists.txt
index c90f340..33dbebf 100644
--- a/src/posix/platform/CMakeLists.txt
+++ b/src/posix/platform/CMakeLists.txt
@@ -53,8 +53,6 @@
     set(OT_CONFIG ${OT_CONFIG} PARENT_SCOPE)
 endif()
 
-list(APPEND OT_PLATFORM_DEFINES "OPENTHREAD_POSIX=1")
-
 set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE)
 
 list(APPEND OT_PLATFORM_DEFINES "OPENTHREAD_PROJECT_CORE_CONFIG_FILE=\"${OT_CONFIG}\"")
@@ -62,9 +60,12 @@
 add_library(openthread-posix
     alarm.cpp
     backbone.cpp
+    daemon.cpp
     entropy.cpp
     hdlc_interface.cpp
+    infra_if.cpp
     logging.cpp
+    memory.cpp
     misc.cpp
     multicast_routing.cpp
     netif.cpp
@@ -74,7 +75,6 @@
     spi_interface.cpp
     system.cpp
     trel_udp6.cpp
-    uart.cpp
     udp.cpp
     virtual_time.cpp
 )
diff --git a/src/posix/platform/Makefile.am b/src/posix/platform/Makefile.am
index 9285284..24c39a6 100644
--- a/src/posix/platform/Makefile.am
+++ b/src/posix/platform/Makefile.am
@@ -46,9 +46,12 @@
 libopenthread_posix_a_SOURCES             = \
     alarm.cpp                               \
     backbone.cpp                            \
+    daemon.cpp                              \
     entropy.cpp                             \
     hdlc_interface.cpp                      \
+    infra_if.cpp                            \
     logging.cpp                             \
+    memory.cpp                              \
     misc.cpp                                \
     multicast_routing.cpp                   \
     netif.cpp                               \
@@ -58,7 +61,6 @@
     spi_interface.cpp                       \
     system.cpp                              \
     trel_udp6.cpp                           \
-    uart.cpp                                \
     udp.cpp                                 \
     virtual_time.cpp                        \
     $(NULL)
diff --git a/src/posix/platform/daemon.cpp b/src/posix/platform/daemon.cpp
new file mode 100644
index 0000000..16576e2
--- /dev/null
+++ b/src/posix/platform/daemon.cpp
@@ -0,0 +1,297 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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.
+ */
+
+#include "openthread-posix-config.h"
+#include "platform-posix.h"
+
+#include <fcntl.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <string.h>
+#include <sys/file.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <openthread/cli.h>
+
+#include "cli/cli_config.h"
+#include "common/code_utils.hpp"
+
+#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
+
+#define OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME ".lock"
+static_assert(sizeof(OPENTHREAD_POSIX_DAEMON_SOCKET_NAME) < sizeof(sockaddr_un::sun_path),
+              "OpenThread daemon socket name too long!");
+
+static int sListenSocket  = -1;
+static int sDaemonLock    = -1;
+static int sSessionSocket = -1;
+
+static int OutputFormatV(void *aContext, const char *aFormat, va_list aArguments)
+{
+    OT_UNUSED_VARIABLE(aContext);
+
+    char buf[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH + 1];
+    int  rval;
+
+    buf[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH] = '\0';
+
+    rval = vsnprintf(buf, sizeof(buf) - 1, aFormat, aArguments);
+
+    VerifyOrExit(rval >= 0, otLogWarnPlat("Failed to format CLI output: %s", strerror(errno)));
+
+    VerifyOrExit(sSessionSocket != -1, otLogDebgPlat("%s", buf));
+
+#if defined(__linux__)
+    // Don't die on SIGPIPE
+    rval = send(sSessionSocket, buf, static_cast<size_t>(rval), MSG_NOSIGNAL);
+#else
+    rval = write(sSessionSocket, buf, static_cast<size_t>(rval));
+#endif
+
+    if (rval < 0)
+    {
+        otLogWarnPlat("Failed to write CLI output: %s", strerror(errno));
+        close(sSessionSocket);
+        sSessionSocket = -1;
+    }
+
+exit:
+    return rval;
+}
+
+static void InitializeSessionSocket(void)
+{
+    int newSessionSocket;
+    int rval;
+
+    VerifyOrExit((newSessionSocket = accept(sListenSocket, nullptr, nullptr)) != -1, rval = -1);
+
+    VerifyOrExit((rval = fcntl(newSessionSocket, F_GETFD, 0)) != -1);
+
+    rval |= FD_CLOEXEC;
+
+    VerifyOrExit((rval = fcntl(newSessionSocket, F_SETFD, rval)) != -1);
+
+#ifndef __linux__
+    // some platforms (macOS, Solaris) don't have MSG_NOSIGNAL
+    // SOME of those (macOS, but NOT Solaris) support SO_NOSIGPIPE
+    // if we have SO_NOSIGPIPE, then set it. Otherwise, we're going
+    // to simply ignore it.
+#if defined(SO_NOSIGPIPE)
+    rval = setsockopt(newSessionSocket, SOL_SOCKET, SO_NOSIGPIPE, &rval, sizeof(rval));
+    VerifyOrExit(rval != -1);
+#else
+#warning "no support for MSG_NOSIGNAL or SO_NOSIGPIPE"
+#endif
+#endif // __linux__
+
+    if (sSessionSocket != -1)
+    {
+        close(sSessionSocket);
+    }
+    sSessionSocket = newSessionSocket;
+
+exit:
+    if (rval == -1)
+    {
+        otLogWarnPlat("Failed to initialize session socket: %s", strerror(errno));
+        if (newSessionSocket != -1)
+        {
+            close(newSessionSocket);
+        }
+    }
+    else
+    {
+        otLogInfoPlat("Session socket is ready", strerror(errno));
+    }
+}
+
+void platformDaemonEnable(otInstance *aInstance)
+{
+    struct sockaddr_un sockname;
+    int                ret;
+
+    // This allows implementing pseudo reset.
+    VerifyOrExit(sListenSocket == -1);
+
+    sListenSocket = SocketWithCloseExec(AF_UNIX, SOCK_STREAM, 0, kSocketNonBlock);
+
+    if (sListenSocket == -1)
+    {
+        DieNow(OT_EXIT_FAILURE);
+    }
+
+    sDaemonLock = open(OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK, O_CREAT | O_RDONLY | O_CLOEXEC, 0600);
+
+    if (sDaemonLock == -1)
+    {
+        DieNowWithMessage("open", OT_EXIT_ERROR_ERRNO);
+    }
+
+    if (flock(sDaemonLock, LOCK_EX | LOCK_NB) == -1)
+    {
+        DieNowWithMessage("flock", OT_EXIT_ERROR_ERRNO);
+    }
+
+    memset(&sockname, 0, sizeof(struct sockaddr_un));
+
+    (void)unlink(OPENTHREAD_POSIX_DAEMON_SOCKET_NAME);
+
+    sockname.sun_family = AF_UNIX;
+    strncpy(sockname.sun_path, OPENTHREAD_POSIX_DAEMON_SOCKET_NAME, sizeof(sockname.sun_path) - 1);
+
+    ret = bind(sListenSocket, (const struct sockaddr *)&sockname, sizeof(struct sockaddr_un));
+
+    if (ret == -1)
+    {
+        DieNowWithMessage("bind", OT_EXIT_ERROR_ERRNO);
+    }
+
+    //
+    // only accept 1 connection.
+    //
+    ret = listen(sListenSocket, 1);
+    if (ret == -1)
+    {
+        DieNowWithMessage("listen", OT_EXIT_ERROR_ERRNO);
+    }
+
+    otCliInit(aInstance, OutputFormatV, aInstance);
+
+exit:
+    return;
+}
+
+void platformDaemonDisable(void)
+{
+    if (sSessionSocket != -1)
+    {
+        close(sSessionSocket);
+        sSessionSocket = -1;
+    }
+
+    if (sListenSocket != -1)
+    {
+        close(sListenSocket);
+        sListenSocket = -1;
+    }
+
+    if (gPlatResetReason != OT_PLAT_RESET_REASON_SOFTWARE)
+    {
+        otLogCritPlat("Removing daemon socket: %s", OPENTHREAD_POSIX_DAEMON_SOCKET_NAME);
+        (void)unlink(OPENTHREAD_POSIX_DAEMON_SOCKET_NAME);
+    }
+
+    if (sDaemonLock != -1)
+    {
+        (void)flock(sDaemonLock, LOCK_UN);
+        close(sDaemonLock);
+        sDaemonLock = -1;
+    }
+}
+
+void platformDaemonUpdate(otSysMainloopContext *aContext)
+{
+    if (sListenSocket != -1)
+    {
+        FD_SET(sListenSocket, &aContext->mReadFdSet);
+        FD_SET(sListenSocket, &aContext->mErrorFdSet);
+
+        if (aContext->mMaxFd < sListenSocket)
+        {
+            aContext->mMaxFd = sListenSocket;
+        }
+    }
+
+    if (sSessionSocket != -1)
+    {
+        FD_SET(sSessionSocket, &aContext->mReadFdSet);
+        FD_SET(sSessionSocket, &aContext->mErrorFdSet);
+
+        if (aContext->mMaxFd < sSessionSocket)
+        {
+            aContext->mMaxFd = sSessionSocket;
+        }
+    }
+
+    return;
+}
+
+void platformDaemonProcess(const otSysMainloopContext *aContext)
+{
+    ssize_t rval;
+
+    VerifyOrExit(sListenSocket != -1);
+
+    if (FD_ISSET(sListenSocket, &aContext->mErrorFdSet))
+    {
+        DieNowWithMessage("daemon socket error", OT_EXIT_FAILURE);
+    }
+    else if (FD_ISSET(sListenSocket, &aContext->mReadFdSet))
+    {
+        InitializeSessionSocket();
+    }
+
+    VerifyOrExit(sSessionSocket != -1);
+
+    if (FD_ISSET(sSessionSocket, &aContext->mErrorFdSet))
+    {
+        close(sSessionSocket);
+        sSessionSocket = -1;
+    }
+    else if (FD_ISSET(sSessionSocket, &aContext->mReadFdSet))
+    {
+        uint8_t buffer[OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH];
+
+        rval = read(sSessionSocket, buffer, sizeof(buffer));
+
+        if (rval > 0)
+        {
+            buffer[rval] = '\0';
+            otLogInfoPlat("> %s", reinterpret_cast<const char *>(buffer));
+            otCliInputLine(reinterpret_cast<char *>(buffer));
+            otCliOutputFormat("> ");
+        }
+        else
+        {
+            if (rval < 0)
+            {
+                otLogWarnPlat("Daemon read: %s", strerror(errno));
+            }
+            close(sSessionSocket);
+            sSessionSocket = -1;
+        }
+    }
+
+exit:
+    return;
+}
+
+#endif // OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
diff --git a/src/posix/platform/include/openthread/platform/secure_settings.h b/src/posix/platform/include/openthread/platform/secure_settings.h
new file mode 100644
index 0000000..90ce5c3
--- /dev/null
+++ b/src/posix/platform/include/openthread/platform/secure_settings.h
@@ -0,0 +1,178 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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
+ * @brief
+ *   This file includes platform abstraction for secure non-volatile storage of settings.
+ */
+
+#ifndef OPENTHREAD_POSIX_SECURE_SETTINGS_H_
+#define OPENTHREAD_POSIX_SECURE_SETTINGS_H_
+
+#include <openthread/instance.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @addtogroup plat-settings
+ *
+ * @brief
+ *   This module includes the platform abstraction for secure non-volatile storage of settings.
+ *
+ * @{
+ *
+ */
+
+/**
+ * This function performs any initialization for the secure settings subsystem, if necessary.
+ *
+ * @param[in]  aInstance The OpenThread instance structure.
+ *
+ */
+void otPosixSecureSettingsInit(otInstance *aInstance);
+
+/**
+ * This function performs any de-initialization for the secure settings subsystem, if necessary.
+ *
+ * @param[in]  aInstance The OpenThread instance structure.
+ *
+ */
+void otPosixSecureSettingsDeinit(otInstance *aInstance);
+
+/**
+ * This function fetches the value of the setting identified by aKey and write it to the memory pointed to by aValue.
+ * It then writes the length to the integer pointed to by aValueLength. The initial value of aValueLength is the
+ * maximum number of bytes to be written to aValue.
+ *
+ * This function can be used to check for the existence of a key without fetching the value by setting aValue and
+ * aValueLength to NULL. You can also check the length of the setting without fetching it by setting only aValue
+ * to NULL.
+ *
+ * Note that the underlying storage implementation is not required to maintain the order of settings with multiple
+ * values. The order of such values MAY change after ANY write operation to the store.
+ *
+ * @param[in]     aInstance     The OpenThread instance structure.
+ * @param[in]     aKey          The key associated with the requested setting.
+ * @param[in]     aIndex        The index of the specific item to get.
+ * @param[out]    aValue        A pointer to where the value of the setting should be written. May be set to NULL if
+ *                              just testing for the presence or length of a setting.
+ * @param[inout]  aValueLength  A pointer to the length of the value. When called, this pointer should point to an
+ *                              integer containing the maximum value size that can be written to aValue. At return,
+ *                              the actual length of the setting is written. This may be set to NULL if performing
+ *                              a presence check.
+ *
+ * @retval OT_ERROR_NONE             The given setting was found and fetched successfully.
+ * @retval OT_ERROR_NOT_FOUND        The given setting was not found in the setting store.
+ * @retval OT_ERROR_NOT_IMPLEMENTED  This function is not implemented on this platform.
+ *
+ */
+otError otPosixSecureSettingsGet(otInstance *aInstance,
+                                 uint16_t    aKey,
+                                 int         aIndex,
+                                 uint8_t *   aValue,
+                                 uint16_t *  aValueLength);
+
+/**
+ * This function sets or replaces the value of a setting identified by aKey. If there was more than one value
+ * previously associated with aKey, then they are all deleted and replaced with this single entry.
+ *
+ * Calling this function successfully may cause unrelated settings with multiple values to be reordered.
+ *
+ * @param[in]  aInstance     The OpenThread instance structure.
+ * @param[in]  aKey          The key associated with the setting to change.
+ * @param[in]  aValue        A pointer to where the new value of the setting should be read from. MUST NOT be NULL if
+ *                           aValueLength is non-zero.
+ * @param[in]  aValueLength  The length of the data pointed to by aValue. May be zero.
+ *
+ * @retval OT_ERROR_NONE             The given setting was changed or staged.
+ * @retval OT_ERROR_NOT_IMPLEMENTED  This function is not implemented on this platform.
+ * @retval OT_ERROR_NO_BUFS          No space remaining to store the given setting.
+ *
+ */
+otError otPosixSecureSettingsSet(otInstance *aInstance, uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength);
+
+/**
+ * This function adds the value to a setting identified by aKey, without replacing any existing values.
+ *
+ * Note that the underlying implementation is not required to maintain the order of the items associated with a
+ * specific key. The added value may be added to the end, the beginning, or even somewhere in the middle. The order
+ * of any pre-existing values may also change.
+ *
+ * Calling this function successfully may cause unrelated settings with multiple values to be reordered.
+ *
+ * @param[in]  aInstance     The OpenThread instance structure.
+ * @param[in]  aKey          The key associated with the setting to change.
+ * @param[in]  aValue        A pointer to where the new value of the setting should be read from. MUST NOT be NULL
+ *                           if aValueLength is non-zero.
+ * @param[in]  aValueLength  The length of the data pointed to by aValue. May be zero.
+ *
+ * @retval OT_ERROR_NONE             The given setting was added or staged to be added.
+ * @retval OT_ERROR_NOT_IMPLEMENTED  This function is not implemented on this platform.
+ * @retval OT_ERROR_NO_BUFS          No space remaining to store the given setting.
+ *
+ */
+otError otPosixSecureSettingsAdd(otInstance *aInstance, uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength);
+
+/**
+ * This function deletes a specific value from the setting identified by aKey from the secure settings store.
+ *
+ * Note that the underlying implementation is not required to maintain the order of the items associated with a
+ * specific key.
+ *
+ * @param[in] aInstance  The OpenThread instance structure.
+ * @param[in] aKey       The key associated with the requested setting.
+ * @param[in] aIndex     The index of the value to be removed. If set to -1, all values for this aKey will be removed.
+ *
+ * @retval OT_ERROR_NONE             The given key and index was found and removed successfully.
+ * @retval OT_ERROR_NOT_FOUND        The given key or index was not found in the setting store.
+ * @retval OT_ERROR_NOT_IMPLEMENTED  This function is not implemented on this platform.
+ *
+ */
+otError otPosixSecureSettingsDelete(otInstance *aInstance, uint16_t aKey, int aIndex);
+
+/**
+ * This function deletes all settings from the secure settings store, resetting it to its initial factory state.
+ *
+ * @param[in] aInstance  The OpenThread instance structure.
+ *
+ */
+void otPosixSecureSettingsWipe(otInstance *aInstance);
+
+/**
+ * @}
+ *
+ */
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // OPENTHREAD_POSIX_SECURE_SETTINGS_H_
diff --git a/src/posix/platform/infra_if.cpp b/src/posix/platform/infra_if.cpp
new file mode 100644
index 0000000..17bcaed
--- /dev/null
+++ b/src/posix/platform/infra_if.cpp
@@ -0,0 +1,467 @@
+/*
+ *  Copyright (c) 2020, The OpenThread Authors.
+ *  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
+ *   This file implements the infrastructure interface for posix.
+ */
+
+#include "platform-posix.h"
+
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+
+#ifdef __APPLE__
+#define __APPLE_USE_RFC_3542
+#endif
+
+#include <errno.h>
+#include <ifaddrs.h>
+// clang-format off
+#include <netinet/in.h>
+#include <netinet/icmp6.h>
+// clang-format on
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <unistd.h>
+#ifdef __linux__
+#include <linux/rtnetlink.h>
+#endif
+
+#include <openthread/border_router.h>
+#include <openthread/platform/infra_if.h>
+
+#include "common/code_utils.hpp"
+#include "common/debug.hpp"
+#include "lib/platform/exit_code.h"
+
+static char         sInfraIfName[IFNAMSIZ];
+static uint32_t     sInfraIfIndex       = 0;
+static int          sInfraIfIcmp6Socket = -1;
+static int          sNetLinkSocket      = -1;
+static otIp6Address sInfraIfLinkLocalAddr;
+
+static int  CreateIcmp6Socket(void);
+static int  CreateNetLinkSocket(void);
+static void ReceiveNetLinkMessage(otInstance *aInstance);
+static void ReceiveIcmp6Message(otInstance *aInstance);
+
+otError otPlatInfraIfSendIcmp6Nd(uint32_t            aInfraIfIndex,
+                                 const otIp6Address *aDestAddress,
+                                 const uint8_t *     aBuffer,
+                                 uint16_t            aBufferLength)
+{
+    otError error = OT_ERROR_NONE;
+
+    struct iovec        iov;
+    struct in6_pktinfo *packetInfo;
+
+    int                 hopLimit = 255;
+    uint8_t             cmsgBuffer[CMSG_SPACE(sizeof(*packetInfo)) + CMSG_SPACE(sizeof(hopLimit))];
+    struct msghdr       msgHeader;
+    struct cmsghdr *    cmsgPointer;
+    ssize_t             rval;
+    struct sockaddr_in6 dest;
+
+    VerifyOrExit(sInfraIfIcmp6Socket >= 0, error = OT_ERROR_FAILED);
+    VerifyOrExit(aInfraIfIndex == sInfraIfIndex, error = OT_ERROR_DROP);
+
+    memset(cmsgBuffer, 0, sizeof(cmsgBuffer));
+
+    // Send the message
+    memset(&dest, 0, sizeof(dest));
+    dest.sin6_family = AF_INET6;
+    memcpy(&dest.sin6_addr, aDestAddress, sizeof(*aDestAddress));
+    if (IN6_IS_ADDR_LINKLOCAL(&dest.sin6_addr) || IN6_IS_ADDR_MC_LINKLOCAL(&dest.sin6_addr))
+    {
+        dest.sin6_scope_id = sInfraIfIndex;
+    }
+
+    iov.iov_base = const_cast<uint8_t *>(aBuffer);
+    iov.iov_len  = aBufferLength;
+
+    msgHeader.msg_namelen    = sizeof(dest);
+    msgHeader.msg_name       = &dest;
+    msgHeader.msg_iov        = &iov;
+    msgHeader.msg_iovlen     = 1;
+    msgHeader.msg_control    = cmsgBuffer;
+    msgHeader.msg_controllen = sizeof(cmsgBuffer);
+
+    // Specify the interface.
+    cmsgPointer             = CMSG_FIRSTHDR(&msgHeader);
+    cmsgPointer->cmsg_level = IPPROTO_IPV6;
+    cmsgPointer->cmsg_type  = IPV6_PKTINFO;
+    cmsgPointer->cmsg_len   = CMSG_LEN(sizeof(*packetInfo));
+    packetInfo              = (struct in6_pktinfo *)CMSG_DATA(cmsgPointer);
+    memset(packetInfo, 0, sizeof(*packetInfo));
+    packetInfo->ipi6_ifindex = sInfraIfIndex;
+
+    // Per section 6.1.2 of RFC 4861, we need to send the ICMPv6 message with IP Hop Limit 255.
+    cmsgPointer             = CMSG_NXTHDR(&msgHeader, cmsgPointer);
+    cmsgPointer->cmsg_level = IPPROTO_IPV6;
+    cmsgPointer->cmsg_type  = IPV6_HOPLIMIT;
+    cmsgPointer->cmsg_len   = CMSG_LEN(sizeof(hopLimit));
+    memcpy(CMSG_DATA(cmsgPointer), &hopLimit, sizeof(hopLimit));
+
+    rval = sendmsg(sInfraIfIcmp6Socket, &msgHeader, 0);
+    if (rval < 0)
+    {
+        otLogWarnPlat("failed to send ICMPv6 message: %s", strerror(errno));
+        ExitNow(error = OT_ERROR_FAILED);
+    }
+
+    if (static_cast<size_t>(rval) != iov.iov_len)
+    {
+        otLogWarnPlat("failed to send ICMPv6 message: partially sent");
+        ExitNow(error = OT_ERROR_FAILED);
+    }
+
+exit:
+    return error;
+}
+
+const otIp6Address *platformInfraIfGetLinkLocalAddress(void)
+{
+    const otIp6Address *ret     = nullptr;
+    struct ifaddrs *    ifAddrs = nullptr;
+
+    VerifyOrDie(getifaddrs(&ifAddrs) != -1, OT_EXIT_ERROR_ERRNO);
+
+    for (struct ifaddrs *addr = ifAddrs; addr != nullptr; addr = addr->ifa_next)
+    {
+        struct sockaddr_in6 *ip6Addr;
+
+        if (strncmp(addr->ifa_name, sInfraIfName, sizeof(sInfraIfName)) != 0 || addr->ifa_addr->sa_family != AF_INET6)
+        {
+            continue;
+        }
+
+        ip6Addr = reinterpret_cast<sockaddr_in6 *>(addr->ifa_addr);
+        if (IN6_IS_ADDR_LINKLOCAL(&ip6Addr->sin6_addr))
+        {
+            memcpy(&sInfraIfLinkLocalAddr, &ip6Addr->sin6_addr, sizeof(sInfraIfLinkLocalAddr));
+            ExitNow(ret = &sInfraIfLinkLocalAddr);
+        }
+    }
+
+    otLogWarnPlat("cannot find IPv6 link-local address for interface %s", sInfraIfName);
+
+exit:
+    freeifaddrs(ifAddrs);
+    return ret;
+}
+
+bool platformInfraIfIsRunning(void)
+{
+    int          sock;
+    struct ifreq ifReq;
+
+    OT_ASSERT(sInfraIfIndex != 0);
+
+    sock = SocketWithCloseExec(AF_INET6, SOCK_DGRAM, IPPROTO_IP, kSocketBlock);
+    VerifyOrDie(sock != -1, OT_EXIT_ERROR_ERRNO);
+
+    memset(&ifReq, 0, sizeof(ifReq));
+    strncpy(ifReq.ifr_name, sInfraIfName, sizeof(ifReq.ifr_name));
+    VerifyOrDie(ioctl(sock, SIOCGIFFLAGS, &ifReq) != -1, OT_EXIT_ERROR_ERRNO);
+
+    close(sock);
+
+    return (ifReq.ifr_flags & IFF_RUNNING);
+}
+
+static int CreateIcmp6Socket(void)
+{
+    int                 sock;
+    int                 rval;
+    struct icmp6_filter filter;
+    const int           kEnable             = 1;
+    const int           kIpv6ChecksumOffset = 2;
+    const int           kHopLimit           = 255;
+
+    // Initializes the ICMPv6 socket.
+    sock = SocketWithCloseExec(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6, kSocketBlock);
+    VerifyOrDie(sock != -1, OT_EXIT_ERROR_ERRNO);
+
+    // Only accept router advertisements and solicitations.
+    ICMP6_FILTER_SETBLOCKALL(&filter);
+    ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
+    ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
+
+    rval = setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter));
+    VerifyOrDie(rval == 0, OT_EXIT_ERROR_ERRNO);
+
+    // We want a source address and interface index.
+    rval = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &kEnable, sizeof(kEnable));
+    VerifyOrDie(rval == 0, OT_EXIT_ERROR_ERRNO);
+
+#ifdef __linux__
+    rval = setsockopt(sock, IPPROTO_RAW, IPV6_CHECKSUM, &kIpv6ChecksumOffset, sizeof(kIpv6ChecksumOffset));
+#else
+    rval = setsockopt(sock, IPPROTO_IPV6, IPV6_CHECKSUM, &kIpv6ChecksumOffset, sizeof(kIpv6ChecksumOffset));
+#endif
+    VerifyOrDie(rval == 0, OT_EXIT_ERROR_ERRNO);
+
+    // We need to be able to reject RAs arriving from off-link.
+    rval = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &kEnable, sizeof(kEnable));
+    VerifyOrDie(rval == 0, OT_EXIT_ERROR_ERRNO);
+
+    rval = setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &kHopLimit, sizeof(kHopLimit));
+    VerifyOrDie(rval == 0, OT_EXIT_ERROR_ERRNO);
+
+    rval = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &kHopLimit, sizeof(kHopLimit));
+    VerifyOrDie(rval == 0, OT_EXIT_ERROR_ERRNO);
+
+    return sock;
+}
+
+uint32_t platformInfraIfInit(otInstance *aInstance, const char *aIfName)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+
+    ssize_t  rval;
+    uint32_t ifIndex = 0;
+
+    if (strlen(aIfName) >= sizeof(sInfraIfName))
+    {
+        otLogCritPlat("infra interface name '%s' is too long", aIfName);
+        DieNow(OT_EXIT_INVALID_ARGUMENTS);
+    }
+    strcpy(sInfraIfName, aIfName);
+
+    // Initializes the infra interface.
+    ifIndex = if_nametoindex(aIfName);
+    if (ifIndex == 0)
+    {
+        otLogCritPlat("failed to get the index for infra interface %s", aIfName);
+        DieNow(OT_EXIT_INVALID_ARGUMENTS);
+    }
+    sInfraIfIndex = ifIndex;
+
+    sInfraIfIcmp6Socket = CreateIcmp6Socket();
+#ifdef __linux__
+    rval = setsockopt(sInfraIfIcmp6Socket, SOL_SOCKET, SO_BINDTODEVICE, sInfraIfName, strlen(sInfraIfName));
+#else  // __NetBSD__ || __FreeBSD__ || __APPLE__
+    rval = setsockopt(sInfraIfIcmp6Socket, IPPROTO_IP, IP_BOUND_IF, &sInfraIfIndex, sizeof(sInfraIfIndex));
+#endif // __linux__
+    VerifyOrDie(rval == 0, OT_EXIT_ERROR_ERRNO);
+
+    sNetLinkSocket = CreateNetLinkSocket();
+
+    return sInfraIfIndex;
+}
+
+void platformInfraIfDeinit(void)
+{
+    if (sInfraIfIcmp6Socket != -1)
+    {
+        close(sInfraIfIcmp6Socket);
+        sInfraIfIcmp6Socket = -1;
+    }
+
+    if (sNetLinkSocket != -1)
+    {
+        close(sNetLinkSocket);
+        sNetLinkSocket = -1;
+    }
+
+    sInfraIfIndex = 0;
+}
+
+void platformInfraIfUpdateFdSet(fd_set &aReadFdSet, int &aMaxFd)
+{
+    VerifyOrExit(sInfraIfIcmp6Socket != -1);
+    VerifyOrExit(sNetLinkSocket != -1);
+
+    FD_SET(sInfraIfIcmp6Socket, &aReadFdSet);
+    aMaxFd = OT_MAX(aMaxFd, sInfraIfIcmp6Socket);
+
+    FD_SET(sNetLinkSocket, &aReadFdSet);
+    aMaxFd = OT_MAX(aMaxFd, sNetLinkSocket);
+
+exit:
+    return;
+}
+
+// Create a net-link socket that subscribes to link & addresses events.
+static int CreateNetLinkSocket(void)
+{
+    int                sock;
+    int                rval;
+    struct sockaddr_nl addr;
+
+    sock = SocketWithCloseExec(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE, kSocketBlock);
+    VerifyOrDie(sock != -1, OT_EXIT_ERROR_ERRNO);
+
+    memset(&addr, 0, sizeof(addr));
+    addr.nl_family = AF_NETLINK;
+    addr.nl_groups = RTMGRP_LINK | RTMGRP_IPV6_IFADDR;
+    addr.nl_pid    = getpid();
+
+    rval = bind(sock, reinterpret_cast<struct sockaddr *>(&addr), sizeof(addr));
+    VerifyOrDie(rval == 0, OT_EXIT_ERROR_ERRNO);
+
+    return sock;
+}
+
+static void ReceiveNetLinkMessage(otInstance *aInstance)
+{
+    const size_t kMaxNetLinkBufSize = 8192;
+    ssize_t      len;
+    union
+    {
+        nlmsghdr mHeader;
+        uint8_t  mBuffer[kMaxNetLinkBufSize];
+    } msgBuffer;
+
+    len = recv(sNetLinkSocket, msgBuffer.mBuffer, sizeof(msgBuffer.mBuffer), 0);
+    if (len < 0)
+    {
+        otLogCritPlat("failed to receive netlink message: %s", strerror(errno));
+        ExitNow();
+    }
+
+    for (struct nlmsghdr *header = &msgBuffer.mHeader; NLMSG_OK(header, static_cast<size_t>(len));
+         header                  = NLMSG_NEXT(header, len))
+    {
+        switch (header->nlmsg_type)
+        {
+        case RTM_NEWLINK:
+        case RTM_DELLINK:
+        case RTM_NEWADDR:
+        case RTM_DELADDR:
+            SuccessOrDie(otPlatInfraIfStateChanged(aInstance, sInfraIfIndex, platformInfraIfIsRunning(),
+                                                   platformInfraIfGetLinkLocalAddress()));
+            break;
+        case NLMSG_ERROR:
+        {
+            struct nlmsgerr *errMsg = reinterpret_cast<struct nlmsgerr *>(NLMSG_DATA(header));
+
+            OT_UNUSED_VARIABLE(errMsg);
+            otLogWarnPlat("netlink NLMSG_ERROR response: seq=%u, error=%d", header->nlmsg_seq, errMsg->error);
+        }
+        default:
+            break;
+        }
+    }
+
+exit:
+    return;
+}
+
+static void ReceiveIcmp6Message(otInstance *aInstance)
+{
+    otError  error = OT_ERROR_NONE;
+    uint8_t  buffer[1500];
+    uint16_t bufferLength;
+
+    ssize_t         rval;
+    struct msghdr   msg;
+    struct iovec    bufp;
+    char            cmsgbuf[128];
+    struct cmsghdr *cmh;
+    uint32_t        ifIndex  = 0;
+    int             hopLimit = -1;
+
+    struct sockaddr_in6 srcAddr;
+    struct in6_addr     dstAddr;
+
+    memset(&srcAddr, 0, sizeof(srcAddr));
+    memset(&dstAddr, 0, sizeof(dstAddr));
+
+    bufp.iov_base      = buffer;
+    bufp.iov_len       = sizeof(buffer);
+    msg.msg_iov        = &bufp;
+    msg.msg_iovlen     = 1;
+    msg.msg_name       = &srcAddr;
+    msg.msg_namelen    = sizeof(srcAddr);
+    msg.msg_control    = cmsgbuf;
+    msg.msg_controllen = sizeof(cmsgbuf);
+
+    rval = recvmsg(sInfraIfIcmp6Socket, &msg, 0);
+    if (rval < 0)
+    {
+        otLogWarnPlat("failed to receive ICMPv6 message: %s", strerror(errno));
+        ExitNow(error = OT_ERROR_DROP);
+    }
+
+    bufferLength = static_cast<uint16_t>(rval);
+
+    for (cmh = CMSG_FIRSTHDR(&msg); cmh; cmh = CMSG_NXTHDR(&msg, cmh))
+    {
+        if (cmh->cmsg_level == IPPROTO_IPV6 && cmh->cmsg_type == IPV6_PKTINFO &&
+            cmh->cmsg_len == CMSG_LEN(sizeof(struct in6_pktinfo)))
+        {
+            struct in6_pktinfo pktinfo;
+
+            memcpy(&pktinfo, CMSG_DATA(cmh), sizeof pktinfo);
+            ifIndex = pktinfo.ipi6_ifindex;
+            dstAddr = pktinfo.ipi6_addr;
+        }
+        else if (cmh->cmsg_level == IPPROTO_IPV6 && cmh->cmsg_type == IPV6_HOPLIMIT &&
+                 cmh->cmsg_len == CMSG_LEN(sizeof(int)))
+        {
+            hopLimit = *(int *)CMSG_DATA(cmh);
+        }
+    }
+
+    VerifyOrExit(ifIndex == sInfraIfIndex, error = OT_ERROR_DROP);
+
+    // We currently accept only RA & RS messages for the Border Router and it requires that
+    // the hoplimit must be 255 and the source address must be a link-local address.
+    VerifyOrExit(hopLimit == 255 && IN6_IS_ADDR_LINKLOCAL(&srcAddr.sin6_addr), error = OT_ERROR_DROP);
+
+    otPlatInfraIfRecvIcmp6Nd(aInstance, ifIndex, reinterpret_cast<otIp6Address *>(&srcAddr.sin6_addr), buffer,
+                             bufferLength);
+
+exit:
+    if (error != OT_ERROR_NONE)
+    {
+        otLogDebgPlat("failed to handle ICMPv6 message: %s", otThreadErrorToString(error));
+    }
+}
+
+void platformInfraIfProcess(otInstance *aInstance, const fd_set &aReadFdSet)
+{
+    VerifyOrExit(sInfraIfIcmp6Socket != -1);
+    VerifyOrExit(sNetLinkSocket != -1);
+
+    if (FD_ISSET(sInfraIfIcmp6Socket, &aReadFdSet))
+    {
+        ReceiveIcmp6Message(aInstance);
+    }
+
+    if (FD_ISSET(sNetLinkSocket, &aReadFdSet))
+    {
+        ReceiveNetLinkMessage(aInstance);
+    }
+
+exit:
+    return;
+}
+
+#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
diff --git a/src/posix/platform/ip6_utils.hpp b/src/posix/platform/ip6_utils.hpp
new file mode 100644
index 0000000..15d4ef6
--- /dev/null
+++ b/src/posix/platform/ip6_utils.hpp
@@ -0,0 +1,70 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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.
+ */
+
+#include "openthread-posix-config.h"
+#include "platform-posix.h"
+
+#include <arpa/inet.h>
+
+namespace ot {
+namespace Posix {
+namespace Ip6Utils {
+
+/**
+ * This utility class converts binary IPv6 address to text format.
+ *
+ */
+class Ip6AddressString
+{
+public:
+    /**
+     * The constructor of this converter.
+     *
+     * @param[in]   aAddress    A pointer to a buffer holding an IPv6 address.
+     *
+     */
+    Ip6AddressString(const void *aAddress)
+    {
+        VerifyOrDie(inet_ntop(AF_INET6, aAddress, mBuffer, sizeof(mBuffer)) != nullptr, OT_EXIT_ERROR_ERRNO);
+    }
+
+    /**
+     * This method returns the string as a null-terminated C string.
+     *
+     * @returns The null-terminated C string.
+     *
+     */
+    const char *AsCString(void) const { return mBuffer; }
+
+private:
+    char mBuffer[INET6_ADDRSTRLEN];
+};
+
+} // namespace Ip6Utils
+} // namespace Posix
+} // namespace ot
diff --git a/src/posix/platform/memory.cpp b/src/posix/platform/memory.cpp
new file mode 100644
index 0000000..5132c3f
--- /dev/null
+++ b/src/posix/platform/memory.cpp
@@ -0,0 +1,46 @@
+/*
+ *  Copyright (c) 2021, The OpenThread Authors.
+ *  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.
+ */
+
+#include "openthread-posix-config.h"
+#include "platform-posix.h"
+
+#include <stdlib.h>
+
+#include <openthread/platform/memory.h>
+
+#if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
+void *otPlatCAlloc(size_t aNum, size_t aSize)
+{
+    return calloc(aNum, aSize);
+}
+
+void otPlatFree(void *aPtr)
+{
+    free(aPtr);
+}
+#endif
diff --git a/src/posix/platform/multicast_routing.cpp b/src/posix/platform/multicast_routing.cpp
index 988aa05..ff9b79f 100644
--- a/src/posix/platform/multicast_routing.cpp
+++ b/src/posix/platform/multicast_routing.cpp
@@ -171,7 +171,7 @@
     struct mif6ctl      mif6ctl;
 
     // Create a Multicast Routing socket
-    mMulticastRouterSock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+    mMulticastRouterSock = SocketWithCloseExec(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6, kSocketBlock);
     VerifyOrDie(mMulticastRouterSock != -1, OT_EXIT_ERROR_ERRNO);
 
     // Enable Multicast Forwarding in Kernel
diff --git a/src/posix/platform/netif.cpp b/src/posix/platform/netif.cpp
index c84c21c..63c7c72 100644
--- a/src/posix/platform/netif.cpp
+++ b/src/posix/platform/netif.cpp
@@ -151,40 +151,9 @@
 char         gNetifName[IFNAMSIZ];
 
 #if OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE
+#include "posix/platform/ip6_utils.hpp"
 
-namespace {
-
-/**
- * This utility class converts binary IPv6 address to text format.
- *
- */
-class Ip6AddressString
-{
-public:
-    /**
-     * The constructor of this converter.
-     *
-     * @param[in]   aAddress    A pointer to a buffer holding an IPv6 address.
-     *
-     */
-    Ip6AddressString(const void *aAddress)
-    {
-        VerifyOrDie(inet_ntop(AF_INET6, aAddress, mBuffer, sizeof(mBuffer)) != nullptr, OT_EXIT_ERROR_ERRNO);
-    }
-
-    /**
-     * This method returns the string as a null-terminated C string.
-     *
-     * @returns The null-terminated C string.
-     *
-     */
-    const char *AsCString(void) const { return mBuffer; }
-
-private:
-    char mBuffer[INET6_ADDRSTRLEN];
-};
-
-} // namespace
+using namespace ot::Posix::Ip6Utils;
 
 #ifndef OPENTHREAD_POSIX_TUN_DEVICE
 
diff --git a/src/posix/platform/openthread-core-posix-config.h b/src/posix/platform/openthread-core-posix-config.h
index 55c11da..1a46017 100644
--- a/src/posix/platform/openthread-core-posix-config.h
+++ b/src/posix/platform/openthread-core-posix-config.h
@@ -35,6 +35,16 @@
 #define OPENTHREAD_CORE_POSIX_CONFIG_H_
 
 /**
+ * @def OPENTHREAD_CONFIG_NUM_MESSAGE_BUFFERS
+ *
+ * The number of message buffers in the buffer pool.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_NUM_MESSAGE_BUFFERS
+#define OPENTHREAD_CONFIG_NUM_MESSAGE_BUFFERS 256
+#endif
+
+/**
  * @def OPENTHREAD_CONFIG_LOG_PLATFORM
  *
  * Define to enable platform region logging.
@@ -87,12 +97,12 @@
 #endif
 
 /**
- * @def OPENTHREAD_CONFIG_NCP_UART_ENABLE
+ * @def OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
  *
- * Define to 1 to enable NCP UART support.
+ * Define to 1 to enable NCP HDLC support.
  *
  */
-#define OPENTHREAD_CONFIG_NCP_UART_ENABLE 1
+#define OPENTHREAD_CONFIG_NCP_HDLC_ENABLE 1
 
 /**
  * @def OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE
@@ -126,4 +136,84 @@
 #define OPENTHREAD_CONFIG_LOG_MAX_SIZE 1024
 #endif
 
+/**
+ * @def OPENTHREAD_CONFIG_COMMISSIONER_MAX_JOINER_ENTRIES
+ *
+ * The maximum number of Joiner entries maintained by the Commissioner.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_COMMISSIONER_MAX_JOINER_ENTRIES
+#define OPENTHREAD_CONFIG_COMMISSIONER_MAX_JOINER_ENTRIES 4
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_TMF_ADDRESS_CACHE_ENTRIES
+ *
+ * The number of EID-to-RLOC cache entries.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_TMF_ADDRESS_CACHE_ENTRIES
+#define OPENTHREAD_CONFIG_TMF_ADDRESS_CACHE_ENTRIES 32
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_MLE_MAX_CHILDREN
+ *
+ * The maximum number of children.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_MLE_MAX_CHILDREN
+#define OPENTHREAD_CONFIG_MLE_MAX_CHILDREN 64
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_MLE_IP_ADDRS_PER_CHILD
+ *
+ * The maximum number of supported IPv6 address registrations per child.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_MLE_IP_ADDRS_PER_CHILD
+#define OPENTHREAD_CONFIG_MLE_IP_ADDRS_PER_CHILD 16
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_IP6_MAX_EXT_UCAST_ADDRS
+ *
+ * The maximum number of supported IPv6 addresses allows to be externally added.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_IP6_MAX_EXT_UCAST_ADDRS
+#define OPENTHREAD_CONFIG_IP6_MAX_EXT_UCAST_ADDRS 8
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_IP6_MAX_EXT_MCAST_ADDRS
+ *
+ * The maximum number of supported IPv6 multicast addresses allows to be externally added.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_IP6_MAX_EXT_MCAST_ADDRS
+#define OPENTHREAD_CONFIG_IP6_MAX_EXT_MCAST_ADDRS 8
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE
+ *
+ * The size of heap buffer when DTLS is enabled.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE
+#define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE (63 * 1024)
+#endif
+
+/**
+ * @def OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS
+ *
+ * The size of heap buffer when DTLS is disabled.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS
+#define OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE_NO_DTLS (63 * 1024)
+#endif
+
 #endif // OPENTHREAD_CORE_POSIX_CONFIG_H_
diff --git a/src/posix/platform/openthread-posix-config.h b/src/posix/platform/openthread-posix-config.h
index 321dd2e..5d55527 100644
--- a/src/posix/platform/openthread-posix-config.h
+++ b/src/posix/platform/openthread-posix-config.h
@@ -58,6 +58,25 @@
 #endif
 
 /**
+ * @def OPENTHREAD_CONFIG_POSIX_TREL_USE_NETLINK_SOCKET
+ *
+ * Defines whether the TREL UDP6 platform uses netlink socket to add/remove addresses on the TREL netif or `ioctl()`
+ * command.
+ *
+ * When netlink is used Duplicate Address Detection (DAD) is disabled when a new address is added on the netif.
+ *
+ * Use of netlink is enabled by default on linux-based platforms.
+ *
+ */
+#ifndef OPENTHREAD_CONFIG_POSIX_TREL_USE_NETLINK_SOCKET
+#ifdef __linux__
+#define OPENTHREAD_CONFIG_POSIX_TREL_USE_NETLINK_SOCKET 1
+#else
+#define OPENTHREAD_CONFIG_POSIX_TREL_USE_NETLINK_SOCKET 0
+#endif
+#endif
+
+/**
  * @def OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME
  *
  * Define socket basename used by POSIX app daemon.
@@ -121,6 +140,17 @@
 #define OPENTHREAD_POSIX_CONFIG_MAX_MULTICAST_FORWARDING_CACHE_TABLE (OPENTHREAD_CONFIG_MAX_MULTICAST_LISTENERS * 10)
 #endif
 
+/**
+ * @def OPENTHREAD_POSIX_CONFIG_SECURE_SETTINGS_ENABLE
+ *
+ * Define as 1 to enable the secure settings. When defined to 1, the platform MUST implement the otPosixSecureSetting*
+ * APIs defined in 'src/posix/platform/include/openthread/platform/secure_settings.h'.
+ *
+ */
+#ifndef OPENTHREAD_POSIX_CONFIG_SECURE_SETTINGS_ENABLE
+#define OPENTHREAD_POSIX_CONFIG_SECURE_SETTINGS_ENABLE 0
+#endif
+
 #ifdef __APPLE__
 
 /**
diff --git a/src/posix/platform/platform-posix.h b/src/posix/platform/platform-posix.h
index ff01947..835bd0c 100644
--- a/src/posix/platform/platform-posix.h
+++ b/src/posix/platform/platform-posix.h
@@ -39,6 +39,7 @@
 
 #include <errno.h>
 #include <net/if.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <string.h>
 #include <sys/select.h>
@@ -46,6 +47,7 @@
 
 #include <openthread/error.h>
 #include <openthread/instance.h>
+#include <openthread/ip6.h>
 #include <openthread/openthread-system.h>
 #include <openthread/platform/time.h>
 
@@ -498,6 +500,87 @@
  */
 extern unsigned int gBackboneNetifIndex;
 
+/**
+ * This function initializes the infrastructure interface.
+ *
+ * @param[in]  aInstance  The OpenThread instance.
+ * @param[in]  aIfName    The name of the infrastructure interface.
+ *
+ * @returns  The index of the infrastructure interface.
+ *
+ */
+uint32_t platformInfraIfInit(otInstance *aInstance, const char *aIfName);
+
+/**
+ * This function deinitializes the infrastructure interface.
+ *
+ */
+void platformInfraIfDeinit(void);
+
+/**
+ * This function tells if the infrastructure interface is running.
+ *
+ * @returns TRUE if the infrastructure interface is running, FALSE if not.
+ *
+ */
+bool platformInfraIfIsRunning(void);
+
+/**
+ * this function returns the IPv6 link-local address of the infrastructure interface.
+ *
+ * @returns  A pointer to the link-local address; NULL if no link-local address is present.
+ *
+ */
+const otIp6Address *platformInfraIfGetLinkLocalAddress(void);
+
+/**
+ * This function updates the read fd set.
+ *
+ * @param[out]  aReadFdSet  The fd set to be updated.
+ * @param[out]  aMaxFd      The maximum fd to be updated.
+ *
+ */
+void platformInfraIfUpdateFdSet(fd_set &aReadFdSet, int &aMaxFd);
+
+/**
+ * This function processes possible events on the infrastructure interface.
+ *
+ * @param[in]  aInstance   The OpenThread instance.
+ * @param[in]  aReadFdSet  The fd set which may contain read vents.
+ *
+ */
+void platformInfraIfProcess(otInstance *aInstance, const fd_set &aReadFdSet);
+
+/**
+ * This function enables daemon.
+ *
+ * @param[in]       aInstance   The OpenThread instance structure.
+ *
+ */
+void platformDaemonEnable(otInstance *aInstance);
+
+/**
+ * This function disables daemon.
+ *
+ */
+void platformDaemonDisable(void);
+
+/**
+ * This function updates the file descriptor sets with file descriptors used by daemon.
+ *
+ * @param[inout]    aMainloop   A pointer to the mainloop context.
+ *
+ */
+void platformDaemonUpdate(otSysMainloopContext *aContext);
+
+/**
+ * This function performs daemon processing.
+ *
+ * @param[in]   aMainloop   A pointer to the mainloop context.
+ *
+ */
+void platformDaemonProcess(const otSysMainloopContext *aContext);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/posix/platform/radio.cpp b/src/posix/platform/radio.cpp
index 09d2b4f..0c2ea12 100644
--- a/src/posix/platform/radio.cpp
+++ b/src/posix/platform/radio.cpp
@@ -33,6 +33,8 @@
 
 #include "platform-posix.h"
 
+#include <string.h>
+
 #include "lib/spinel/radio_spinel.hpp"
 
 #if OPENTHREAD_POSIX_CONFIG_RCP_BUS == OT_POSIX_RCP_BUS_UART
@@ -90,16 +92,18 @@
 
 void platformRadioInit(otUrl *aRadioUrl)
 {
-    ot::Posix::RadioUrl &radioUrl       = *static_cast<ot::Posix::RadioUrl *>(aRadioUrl);
-    bool                 resetRadio     = (radioUrl.GetValue("no-reset") == nullptr);
-    bool                 restoreDataset = (radioUrl.GetValue("ncp-dataset") != nullptr);
+    ot::Posix::RadioUrl &radioUrl               = *static_cast<ot::Posix::RadioUrl *>(aRadioUrl);
+    bool                 resetRadio             = (radioUrl.GetValue("no-reset") == nullptr);
+    bool                 restoreDataset         = (radioUrl.GetValue("ncp-dataset") != nullptr);
+    bool                 skipCompatibilityCheck = (radioUrl.GetValue("skip-rcp-compatibility-check") != nullptr);
     const char *         parameterValue;
+    const char *         region;
 #if OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE
     const char *maxPowerTable;
 #endif
 
     SuccessOrDie(sRadioSpinel.GetSpinelInterface().Init(radioUrl));
-    sRadioSpinel.Init(resetRadio, restoreDataset);
+    sRadioSpinel.Init(resetRadio, restoreDataset, skipCompatibilityCheck);
 
     parameterValue = radioUrl.GetValue("fem-lnagain");
     if (parameterValue != nullptr)
@@ -119,6 +123,16 @@
         SuccessOrDie(sRadioSpinel.SetCcaEnergyDetectThreshold(static_cast<int8_t>(ccaThreshold)));
     }
 
+    region = radioUrl.GetValue("region");
+    if (region != nullptr)
+    {
+        uint16_t regionCode;
+
+        VerifyOrDie(strnlen(region, 3) == 2, OT_EXIT_INVALID_ARGUMENTS);
+        regionCode = static_cast<uint16_t>(static_cast<uint16_t>(region[0]) << 8) + static_cast<uint16_t>(region[1]);
+        SuccessOrDie(sRadioSpinel.SetRadioRegion(regionCode));
+    }
+
 #if OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE
     maxPowerTable = radioUrl.GetValue("max-power-table");
     if (maxPowerTable != nullptr)
@@ -546,3 +560,15 @@
     OT_UNUSED_VARIABLE(aInstance);
     return sRadioSpinel.SetChannelMaxTransmitPower(aChannel, aMaxPower);
 }
+
+otError otPlatRadioSetRegion(otInstance *aInstance, uint16_t aRegionCode)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+    return sRadioSpinel.SetRadioRegion(aRegionCode);
+}
+
+otError otPlatRadioGetRegion(otInstance *aInstance, uint16_t *aRegionCode)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+    return sRadioSpinel.GetRadioRegion(aRegionCode);
+}
diff --git a/src/posix/platform/radio_url.cpp b/src/posix/platform/radio_url.cpp
index 7c80f1e..ca0f160 100644
--- a/src/posix/platform/radio_url.cpp
+++ b/src/posix/platform/radio_url.cpp
@@ -88,10 +88,12 @@
 #endif
 
     return "RadioURL:\n" OT_RADIO_URL_HELP_BUS OT_RADIO_URL_HELP_MAX_POWER_TABLE
+           "    region[=region-code]          Set the radio's region code.\n"
            "    cca-threshold[=dbm]           Set the radio's CCA ED threshold in dBm measured at antenna connector.\n"
            "    fem-lnagain[=dbm]             Set the Rx LNA gain in dBm of the external FEM.\n"
            "    ncp-dataset                   Retrieve dataset from ncp.\n"
-           "    no-reset                      Do not send Spinel reset command to RCP on initialization.\n";
+           "    no-reset                      Do not send Spinel reset command to RCP on initialization.\n"
+           "    skip-rcp-compatibility-check  Skip checking RCP API version and capabilities during initialization.\n";
 }
 
 namespace ot {
diff --git a/src/posix/platform/settings.cpp b/src/posix/platform/settings.cpp
index ec6aa79..c2750ff 100644
--- a/src/posix/platform/settings.cpp
+++ b/src/posix/platform/settings.cpp
@@ -47,6 +47,9 @@
 #include <openthread/platform/misc.h>
 #include <openthread/platform/radio.h>
 #include <openthread/platform/settings.h>
+#if OPENTHREAD_POSIX_CONFIG_SECURE_SETTINGS_ENABLE
+#include <openthread/platform/secure_settings.h>
+#endif
 
 #include "common/code_utils.hpp"
 #include "common/encoding.hpp"
@@ -57,6 +60,34 @@
 
 static otError platformSettingsDelete(otInstance *aInstance, uint16_t aKey, int aIndex, int *aSwapFd);
 
+#if OPENTHREAD_POSIX_CONFIG_SECURE_SETTINGS_ENABLE
+static const uint16_t *sKeys       = nullptr;
+static uint16_t        sKeysLength = 0;
+
+void otPlatSettingsSetCriticalKeys(otInstance *aInstance, const uint16_t *aKeys, uint16_t aKeysLength)
+{
+    OT_UNUSED_VARIABLE(aInstance);
+
+    sKeys       = aKeys;
+    sKeysLength = aKeysLength;
+}
+
+static bool isCriticalKey(uint16_t aKey)
+{
+    bool ret = false;
+
+    VerifyOrExit(sKeys != nullptr);
+
+    for (uint16_t i = 0; i < sKeysLength; i++)
+    {
+        VerifyOrExit(aKey != sKeys[i], ret = true);
+    }
+
+exit:
+    return ret;
+}
+#endif
+
 static void getSettingsFileName(otInstance *aInstance, char aFileName[kMaxFileNameSize], bool aSwap)
 {
     const char *offset = getenv("PORT_OFFSET");
@@ -137,6 +168,10 @@
 {
     otError error = OT_ERROR_NONE;
 
+#if OPENTHREAD_POSIX_CONFIG_SECURE_SETTINGS_ENABLE
+    otPosixSecureSettingsInit(aInstance);
+#endif
+
     {
         struct stat st;
 
@@ -182,6 +217,10 @@
 {
     OT_UNUSED_VARIABLE(aInstance);
 
+#if OPENTHREAD_POSIX_CONFIG_SECURE_SETTINGS_ENABLE
+    otPosixSecureSettingsDeinit(aInstance);
+#endif
+
     assert(sSettingsFd != -1);
     VerifyOrDie(close(sSettingsFd) == 0, OT_EXIT_ERROR_ERRNO);
 }
@@ -194,6 +233,13 @@
     const off_t size   = lseek(sSettingsFd, 0, SEEK_END);
     off_t       offset = lseek(sSettingsFd, 0, SEEK_SET);
 
+#if OPENTHREAD_POSIX_CONFIG_SECURE_SETTINGS_ENABLE
+    if (isCriticalKey(aKey))
+    {
+        ExitNow(error = otPosixSecureSettingsGet(aInstance, aKey, aIndex, aValue, aValueLength));
+    }
+#endif
+
     VerifyOrExit(offset == 0 && size >= 0, error = OT_ERROR_PARSE);
 
     while (offset < size)
@@ -245,7 +291,15 @@
 
 otError otPlatSettingsSet(otInstance *aInstance, uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength)
 {
-    int swapFd = -1;
+    int     swapFd = -1;
+    otError error  = OT_ERROR_NONE;
+
+#if OPENTHREAD_POSIX_CONFIG_SECURE_SETTINGS_ENABLE
+    if (isCriticalKey(aKey))
+    {
+        ExitNow(error = otPosixSecureSettingsSet(aInstance, aKey, aValue, aValueLength));
+    }
+#endif
 
     switch (platformSettingsDelete(aInstance, aKey, -1, &swapFd))
     {
@@ -265,15 +319,26 @@
 
     swapPersist(aInstance, swapFd);
 
-    return OT_ERROR_NONE;
+#if OPENTHREAD_POSIX_CONFIG_SECURE_SETTINGS_ENABLE
+exit:
+#endif
+    return error;
 }
 
 otError otPlatSettingsAdd(otInstance *aInstance, uint16_t aKey, const uint8_t *aValue, uint16_t aValueLength)
 {
     OT_UNUSED_VARIABLE(aInstance);
 
-    off_t size   = lseek(sSettingsFd, 0, SEEK_END);
-    int   swapFd = swapOpen(aInstance);
+    otError error  = OT_ERROR_NONE;
+    off_t   size   = lseek(sSettingsFd, 0, SEEK_END);
+    int     swapFd = swapOpen(aInstance);
+
+#if OPENTHREAD_POSIX_CONFIG_SECURE_SETTINGS_ENABLE
+    if (isCriticalKey(aKey))
+    {
+        ExitNow(error = otPosixSecureSettingsAdd(aInstance, aKey, aValue, aValueLength));
+    }
+#endif
 
     if (size > 0)
     {
@@ -288,12 +353,28 @@
 
     swapPersist(aInstance, swapFd);
 
-    return OT_ERROR_NONE;
+#if OPENTHREAD_POSIX_CONFIG_SECURE_SETTINGS_ENABLE
+exit:
+#endif
+    return error;
 }
 
 otError otPlatSettingsDelete(otInstance *aInstance, uint16_t aKey, int aIndex)
 {
-    return platformSettingsDelete(aInstance, aKey, aIndex, nullptr);
+    otError error;
+
+#if OPENTHREAD_POSIX_CONFIG_SECURE_SETTINGS_ENABLE
+    if (isCriticalKey(aKey))
+    {
+        error = otPosixSecureSettingsDelete(aInstance, aKey, aIndex);
+    }
+    else
+#endif
+    {
+        error = platformSettingsDelete(aInstance, aKey, aIndex, nullptr);
+    }
+
+    return error;
 }
 
 /**
@@ -393,6 +474,10 @@
 void otPlatSettingsWipe(otInstance *aInstance)
 {
     OT_UNUSED_VARIABLE(aInstance);
+#if OPENTHREAD_POSIX_CONFIG_SECURE_SETTINGS_ENABLE
+    otPosixSecureSettingsWipe(aInstance);
+#endif
+
     VerifyOrDie(0 == ftruncate(sSettingsFd, 0), OT_EXIT_ERROR_ERRNO);
 }
 
diff --git a/src/posix/platform/system.cpp b/src/posix/platform/system.cpp
index 1824fe1..3bb55f9 100644
--- a/src/posix/platform/system.cpp
+++ b/src/posix/platform/system.cpp
@@ -38,11 +38,13 @@
 #include <assert.h>
 
 #include <openthread-core-config.h>
+#include <openthread/border_router.h>
+#include <openthread/heap.h>
 #include <openthread/tasklet.h>
 #include <openthread/platform/alarm-milli.h>
+#include <openthread/platform/infra_if.h>
 #include <openthread/platform/otns.h>
 #include <openthread/platform/radio.h>
-#include <openthread/platform/uart.h>
 
 #include "common/code_utils.hpp"
 
@@ -97,6 +99,24 @@
     platformBackboneInit(instance, aPlatformConfig->mBackboneInterfaceName);
 #endif
 
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+    {
+        uint32_t infraIfIndex;
+
+        // Reuse the backbone interface name.
+        if (aPlatformConfig->mBackboneInterfaceName == nullptr || strlen(aPlatformConfig->mBackboneInterfaceName) == 0)
+        {
+            DieNowWithMessage("no infra interface is specified", OT_EXIT_INVALID_ARGUMENTS);
+        }
+
+        infraIfIndex = platformInfraIfInit(instance, aPlatformConfig->mBackboneInterfaceName);
+
+        SuccessOrDie(otBorderRoutingInit(instance, infraIfIndex, platformInfraIfIsRunning(),
+                                         platformInfraIfGetLinkLocalAddress()));
+        SuccessOrDie(otBorderRoutingSetEnabled(instance, /* aEnabled */ true));
+    }
+#endif
+
 #if OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE
     platformNetifInit(instance, aPlatformConfig->mInterfaceName);
 #elif OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
@@ -107,11 +127,17 @@
     SuccessOrDie(otSetStateChangedCallback(instance, processStateChange, instance));
 #endif
 
+#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
+    platformDaemonEnable(instance);
+#endif
     return instance;
 }
 
 void otSysDeinit(void)
 {
+#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
+    platformDaemonDisable();
+#endif
 #if OPENTHREAD_POSIX_VIRTUAL_TIME
     virtualTimeDeinit();
 #endif
@@ -122,7 +148,10 @@
 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
     platformTrelDeinit();
 #endif
-    IgnoreError(otPlatUartDisable());
+
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+    platformInfraIfDeinit();
+#endif
 }
 
 #if OPENTHREAD_POSIX_VIRTUAL_TIME
@@ -161,8 +190,6 @@
 void otSysMainloopUpdate(otInstance *aInstance, otSysMainloopContext *aMainloop)
 {
     platformAlarmUpdateTimeout(&aMainloop->mTimeout);
-    platformUartUpdateFdSet(&aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mErrorFdSet,
-                            &aMainloop->mMaxFd);
 #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
     platformUdpUpdateFdSet(aInstance, &aMainloop->mReadFdSet, &aMainloop->mMaxFd);
 #endif
@@ -173,6 +200,9 @@
 #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
     platformBackboneUpdateFdSet(aMainloop->mReadFdSet, aMainloop->mMaxFd);
 #endif
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+    platformInfraIfUpdateFdSet(aMainloop->mReadFdSet, aMainloop->mMaxFd);
+#endif
 #if OPENTHREAD_POSIX_VIRTUAL_TIME
     virtualTimeUpdateFdSet(&aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mErrorFdSet, &aMainloop->mMaxFd,
                            &aMainloop->mTimeout);
@@ -183,6 +213,10 @@
     platformTrelUpdateFdSet(&aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mMaxFd, &aMainloop->mTimeout);
 #endif
 
+#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
+    platformDaemonUpdate(aMainloop);
+#endif
+
     if (otTaskletsArePending(aInstance))
     {
         aMainloop->mTimeout.tv_sec  = 0;
@@ -243,7 +277,6 @@
 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
     platformTrelProcess(aInstance, &aMainloop->mReadFdSet, &aMainloop->mWriteFdSet);
 #endif
-    platformUartProcess(&aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mErrorFdSet);
     platformAlarmProcess(aInstance);
 #if OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE
     platformNetifProcess(&aMainloop->mReadFdSet, &aMainloop->mWriteFdSet, &aMainloop->mErrorFdSet);
@@ -254,6 +287,12 @@
 #if OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
     platformBackboneProcess(aMainloop->mReadFdSet);
 #endif
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+    platformInfraIfProcess(aInstance, aMainloop->mReadFdSet);
+#endif
+#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
+    platformDaemonProcess(aMainloop);
+#endif
 }
 
 #if OPENTHREAD_CONFIG_OTNS_ENABLE
diff --git a/src/posix/platform/trel_udp6.cpp b/src/posix/platform/trel_udp6.cpp
index 1b3ff0c..5b224b6 100644
--- a/src/posix/platform/trel_udp6.cpp
+++ b/src/posix/platform/trel_udp6.cpp
@@ -35,6 +35,10 @@
 
 #include "platform-posix.h"
 
+#if OPENTHREAD_CONFIG_POSIX_TREL_USE_NETLINK_SOCKET && !defined(__linux__)
+#error "netlink socket use is only supported on linux platform"
+#endif
+
 #include <arpa/inet.h>
 #include <assert.h>
 #include <fcntl.h>
@@ -43,6 +47,11 @@
 #include <sys/ioctl.h>
 #include <sys/socket.h>
 #include <unistd.h>
+#if OPENTHREAD_CONFIG_POSIX_TREL_USE_NETLINK_SOCKET
+#include <linux/if_tun.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#endif
 
 #include <openthread/platform/trel-udp6.h>
 
@@ -57,6 +66,9 @@
 #define USEC_PER_MSEC 1000u
 #define TREL_SOCKET_BIND_MAX_WAIT_TIME_MSEC 4000u
 
+#define TREL_UNICAST_ADDRESS_PREFIX_LEN 64
+#define TREL_UNICAST_ADDRESS_SCOPE 2 // The unicast address is link-local
+
 typedef struct TxPacket
 {
     struct TxPacket *mNext;
@@ -126,6 +138,66 @@
 #endif // #if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_DEBG)
 #endif // OPENTHREAD_CONFIG_LOG_PLATFORM
 
+#if OPENTHREAD_CONFIG_POSIX_TREL_USE_NETLINK_SOCKET
+
+static void UpdateUnicastAddress(const otIp6Address *aUnicastAddress, bool aToAdd)
+{
+    int            netlinkSocket;
+    int            ret;
+    struct rtattr *rta;
+
+    struct
+    {
+        struct nlmsghdr  nh;
+        struct ifaddrmsg ifa;
+        char             buf[64];
+    } request;
+
+    netlinkSocket = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
+    VerifyOrDie(netlinkSocket >= 0, OT_EXIT_ERROR_ERRNO);
+
+    memset(&request, 0, sizeof(request));
+
+    request.nh.nlmsg_len   = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
+    request.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;
+    request.nh.nlmsg_type  = aToAdd ? RTM_NEWADDR : RTM_DELADDR;
+    request.nh.nlmsg_pid   = 0;
+    request.nh.nlmsg_seq   = 0;
+
+    request.ifa.ifa_family    = AF_INET6;
+    request.ifa.ifa_prefixlen = TREL_UNICAST_ADDRESS_PREFIX_LEN;
+    request.ifa.ifa_flags     = IFA_F_NODAD;
+    request.ifa.ifa_scope     = TREL_UNICAST_ADDRESS_SCOPE;
+    request.ifa.ifa_index     = sInterfaceIndex;
+
+    rta = reinterpret_cast<struct rtattr *>((reinterpret_cast<char *>(&request)) + NLMSG_ALIGN(request.nh.nlmsg_len));
+    rta->rta_type = IFA_LOCAL;
+    rta->rta_len  = RTA_LENGTH(sizeof(otIp6Address));
+
+    memcpy(RTA_DATA(rta), aUnicastAddress, sizeof(otIp6Address));
+
+    request.nh.nlmsg_len = NLMSG_ALIGN(request.nh.nlmsg_len) + rta->rta_len;
+
+    ret = send(netlinkSocket, &request, request.nh.nlmsg_len, 0);
+    VerifyOrDie(ret != -1, OT_EXIT_ERROR_ERRNO);
+
+    close(netlinkSocket);
+}
+
+static void AddUnicastAddress(const otIp6Address *aUnicastAddress)
+{
+    otLogDebgPlat("[trel] AddUnicastAddress(%s)", Ip6AddrToString(aUnicastAddress));
+    UpdateUnicastAddress(aUnicastAddress, /* aToAdd */ true);
+}
+
+static void RemoveUnicastAddress(const otIp6Address *aUnicastAddress)
+{
+    otLogDebgPlat("[trel] RemoveUnicastAddress(%s)", Ip6AddrToString(aUnicastAddress));
+    UpdateUnicastAddress(aUnicastAddress, /* aToAdd */ false);
+}
+
+#else // OPENTHREAD_CONFIG_POSIX_TREL_USE_NETLINK_SOCKET
+
 static void AddUnicastAddress(const otIp6Address *aUnicastAddress)
 {
     int mgmtFd;
@@ -185,6 +257,8 @@
     close(mgmtFd);
 }
 
+#endif // OPENTHREAD_CONFIG_POSIX_TREL_USE_NETLINK_SOCKET
+
 static void PrepareSocket(void)
 {
     int                 val;
diff --git a/src/posix/platform/uart.cpp b/src/posix/platform/uart.cpp
deleted file mode 100644
index d2b47fa..0000000
--- a/src/posix/platform/uart.cpp
+++ /dev/null
@@ -1,450 +0,0 @@
-/*
- *  Copyright (c) 2016, The OpenThread Authors.
- *  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.
- */
-
-#include "openthread-posix-config.h"
-#include "platform-posix.h"
-
-#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-#include <fcntl.h>
-#include <signal.h>
-#include <string.h>
-#include <sys/file.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#endif
-
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include <openthread/platform/misc.h>
-#include <openthread/platform/uart.h>
-
-#include "common/code_utils.hpp"
-
-#define OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK OPENTHREAD_POSIX_CONFIG_DAEMON_SOCKET_BASENAME ".lock"
-
-#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-static int sUartSocket    = -1;
-static int sUartLock      = -1;
-static int sSessionSocket = -1;
-#endif
-
-static bool           sEnabled     = false;
-static const uint8_t *sWriteBuffer = nullptr;
-static uint16_t       sWriteLength = 0;
-
-otError otPlatUartEnable(void)
-{
-    otError error = OT_ERROR_NONE;
-#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-    struct sockaddr_un sockname;
-    int                ret;
-
-    // This allows implementing pseudo reset.
-    VerifyOrExit(sUartSocket == -1);
-
-    sUartSocket = SocketWithCloseExec(AF_UNIX, SOCK_STREAM, 0, kSocketNonBlock);
-
-    if (sUartSocket == -1)
-    {
-        DieNow(OT_EXIT_FAILURE);
-    }
-
-    sUartLock = open(OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK, O_CREAT | O_RDONLY | O_CLOEXEC, 0600);
-
-    if (sUartLock == -1)
-    {
-        DieNowWithMessage("open", OT_EXIT_ERROR_ERRNO);
-    }
-
-    if (flock(sUartLock, LOCK_EX | LOCK_NB) == -1)
-    {
-        DieNowWithMessage("flock", OT_EXIT_ERROR_ERRNO);
-    }
-
-    memset(&sockname, 0, sizeof(struct sockaddr_un));
-
-    (void)unlink(OPENTHREAD_POSIX_DAEMON_SOCKET_NAME);
-
-    sockname.sun_family = AF_UNIX;
-    assert(sizeof(OPENTHREAD_POSIX_DAEMON_SOCKET_NAME) < sizeof(sockname.sun_path));
-    strncpy(sockname.sun_path, OPENTHREAD_POSIX_DAEMON_SOCKET_NAME, sizeof(sockname.sun_path) - 1);
-
-    ret = bind(sUartSocket, (const struct sockaddr *)&sockname, sizeof(struct sockaddr_un));
-
-    if (ret == -1)
-    {
-        DieNowWithMessage("bind", OT_EXIT_ERROR_ERRNO);
-    }
-
-    //
-    // only accept 1 connection.
-    //
-    ret = listen(sUartSocket, 1);
-    if (ret == -1)
-    {
-        DieNowWithMessage("listen", OT_EXIT_ERROR_ERRNO);
-    }
-
-exit:
-#endif // OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-
-    sEnabled = true;
-    return error;
-}
-
-otError otPlatUartDisable(void)
-{
-    otError error = OT_ERROR_NONE;
-    sEnabled      = false;
-
-#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-    if (sSessionSocket != -1)
-    {
-        close(sSessionSocket);
-        sSessionSocket = -1;
-    }
-
-    if (sUartSocket != -1)
-    {
-        close(sUartSocket);
-        sUartSocket = -1;
-    }
-
-    if (gPlatResetReason != OT_PLAT_RESET_REASON_SOFTWARE)
-    {
-        otLogCritPlat("Removing daemon socket: %s", OPENTHREAD_POSIX_DAEMON_SOCKET_NAME);
-        (void)unlink(OPENTHREAD_POSIX_DAEMON_SOCKET_NAME);
-    }
-
-    if (sUartLock != -1)
-    {
-        (void)flock(sUartLock, LOCK_UN);
-        close(sUartLock);
-        sUartLock = -1;
-    }
-#endif // OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-
-    return error;
-}
-
-otError otPlatUartSend(const uint8_t *aBuf, uint16_t aBufLength)
-{
-    otError error = OT_ERROR_NONE;
-
-    assert(sEnabled);
-    VerifyOrExit(sWriteLength == 0, error = OT_ERROR_BUSY);
-
-    sWriteBuffer = aBuf;
-    sWriteLength = aBufLength;
-
-exit:
-    return error;
-}
-
-void platformUartUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, fd_set *aErrorFdSet, int *aMaxFd)
-{
-    VerifyOrExit(sEnabled);
-
-    if (aReadFdSet != nullptr)
-    {
-#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-        int fd = (sSessionSocket == -1 ? sUartSocket : sSessionSocket);
-#else
-        int fd = STDIN_FILENO;
-#endif
-
-        FD_SET(fd, aReadFdSet);
-
-        if (aErrorFdSet != nullptr)
-        {
-            FD_SET(fd, aErrorFdSet);
-        }
-
-        if (aMaxFd != nullptr && *aMaxFd < fd)
-        {
-            *aMaxFd = fd;
-        }
-    }
-    if ((aWriteFdSet != nullptr) && (sWriteLength > 0))
-    {
-#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-        int fd = (sSessionSocket == -1 ? sUartSocket : sSessionSocket);
-#else
-        int fd = STDOUT_FILENO;
-#endif
-
-        FD_SET(fd, aWriteFdSet);
-
-        if (aErrorFdSet != nullptr)
-        {
-            FD_SET(fd, aErrorFdSet);
-        }
-
-        if (aMaxFd != nullptr && *aMaxFd < fd)
-        {
-            *aMaxFd = fd;
-        }
-    }
-
-exit:
-    return;
-}
-
-#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-static void InitializeSessionSocket(void)
-{
-    int newSessionSocket;
-    int rval;
-
-    VerifyOrExit((newSessionSocket = accept(sUartSocket, nullptr, nullptr)) != -1, rval = -1);
-
-    VerifyOrExit((rval = fcntl(newSessionSocket, F_GETFD, 0)) != -1);
-
-    rval |= FD_CLOEXEC;
-
-    VerifyOrExit((rval = fcntl(newSessionSocket, F_SETFD, rval)) != -1);
-
-#ifndef __linux__
-    // some platforms (macOS, Solaris) don't have MSG_NOSIGNAL
-    // SOME of those (macOS, but NOT Solaris) support SO_NOSIGPIPE
-    // if we have SO_NOSIGPIPE, then set it. Otherwise, we're going
-    // to simply ignore it.
-#if defined(SO_NOSIGPIPE)
-    rval = setsockopt(newSessionSocket, SOL_SOCKET, SO_NOSIGPIPE, &rval, sizeof(rval));
-    VerifyOrExit(rval != -1);
-#else
-#warning "no support for MSG_NOSIGNAL or SO_NOSIGPIPE"
-#endif
-#endif // __linux__
-
-    if (sSessionSocket != -1)
-    {
-        close(sSessionSocket);
-    }
-    sSessionSocket = newSessionSocket;
-
-exit:
-    if (rval == -1)
-    {
-        otLogWarnPlat("Failed to initialize session socket: %s", strerror(errno));
-        if (newSessionSocket != -1)
-        {
-            close(newSessionSocket);
-        }
-    }
-    else
-    {
-        otLogInfoPlat("Session socket is ready", strerror(errno));
-    }
-}
-#endif
-
-static otError UartWrite(int aFd)
-{
-    otError error = OT_ERROR_NONE;
-    ssize_t rval;
-
-    VerifyOrExit(sWriteLength > 0, error = OT_ERROR_INVALID_STATE);
-
-#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE && defined(__linux__)
-    if (aFd == sSessionSocket)
-    {
-        // Don't die on SIGPIPE
-        rval = send(aFd, sWriteBuffer, sWriteLength, MSG_NOSIGNAL);
-    }
-    else
-#endif // OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE && defined(__linux__)
-    {
-        rval = write(aFd, sWriteBuffer, sWriteLength);
-    }
-
-    if (rval < 0)
-    {
-#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-        otLogWarnPlat("UART write: %s", strerror(errno));
-        if (aFd == sSessionSocket)
-        {
-            close(sSessionSocket);
-            sSessionSocket = -1;
-        }
-        ExitNow();
-#else
-        DieNow(OT_EXIT_ERROR_ERRNO);
-#endif
-    }
-
-    sWriteBuffer += rval;
-    sWriteLength -= static_cast<uint16_t>(rval);
-
-exit:
-    return error;
-}
-
-void platformUartProcess(const fd_set *aReadFdSet, const fd_set *aWriteFdSet, const fd_set *aErrorFdSet)
-{
-    ssize_t rval;
-    int     fd;
-
-    VerifyOrExit(sEnabled);
-#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-    if (FD_ISSET(sUartSocket, aErrorFdSet))
-    {
-        DieNowWithMessage("socket", OT_EXIT_FAILURE);
-    }
-    else if (FD_ISSET(sUartSocket, aReadFdSet))
-    {
-        InitializeSessionSocket();
-    }
-
-    if (sSessionSocket == -1 && sWriteBuffer != nullptr)
-    {
-        IgnoreReturnValue(write(STDERR_FILENO, sWriteBuffer, sWriteLength));
-        sWriteBuffer = nullptr;
-        sWriteLength = 0;
-        otPlatUartSendDone();
-    }
-
-    VerifyOrExit(sSessionSocket != -1);
-
-    if (FD_ISSET(sSessionSocket, aErrorFdSet))
-    {
-        close(sSessionSocket);
-        sSessionSocket = -1;
-    }
-
-    VerifyOrExit(sSessionSocket != -1);
-
-    fd = sSessionSocket;
-#else  // OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-    if (FD_ISSET(STDIN_FILENO, aErrorFdSet))
-    {
-        DieNowWithMessage("stdin", OT_EXIT_FAILURE);
-    }
-
-    if (FD_ISSET(STDOUT_FILENO, aErrorFdSet))
-    {
-        DieNowWithMessage("stdout", OT_EXIT_FAILURE);
-    }
-
-    fd = STDIN_FILENO;
-#endif // OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-
-    if (FD_ISSET(fd, aReadFdSet))
-    {
-        uint8_t buffer[256];
-
-        rval = read(fd, buffer, sizeof(buffer));
-
-        if (rval > 0)
-        {
-            otPlatUartReceived(buffer, (uint16_t)rval);
-        }
-        else if (rval <= 0)
-        {
-#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-            if (rval < 0)
-            {
-                perror("UART read");
-            }
-            close(sSessionSocket);
-            sSessionSocket = -1;
-            ExitNow();
-#else
-            DieNowWithMessage("UART read", (rval < 0) ? OT_EXIT_ERROR_ERRNO : OT_EXIT_FAILURE);
-#endif
-        }
-    }
-
-#if !OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-    fd = STDOUT_FILENO;
-#endif
-
-    if ((FD_ISSET(fd, aWriteFdSet)))
-    {
-        otError error = UartWrite(fd);
-
-        VerifyOrExit(error == OT_ERROR_NONE, otLogWarnPlat("UART write: %s", otThreadErrorToString(error)));
-
-        if (sWriteLength == 0)
-        {
-            otPlatUartSendDone();
-        }
-    }
-
-exit:
-    return;
-}
-
-otError otPlatUartFlush(void)
-{
-    otError error = OT_ERROR_NONE;
-
-    while (sWriteLength > 0)
-    {
-        int fd =
-#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-            sSessionSocket != -1 ? sSessionSocket :
-#endif
-                                 STDOUT_FILENO;
-        int rval;
-
-        fd_set writeFdSet;
-        FD_ZERO(&writeFdSet);
-        FD_SET(fd, &writeFdSet);
-
-        rval = select(fd + 1, nullptr, &writeFdSet, nullptr, nullptr);
-
-        assert(rval != 0);
-
-        if (rval > 0)
-        {
-            assert(FD_ISSET(fd, &writeFdSet));
-            SuccessOrExit(error = UartWrite(fd));
-        }
-        else if (errno != EINTR)
-        {
-#if OPENTHREAD_POSIX_CONFIG_DAEMON_ENABLE
-            if (sSessionSocket == fd)
-            {
-                close(sSessionSocket);
-                sSessionSocket = -1;
-            }
-            else
-#endif
-            {
-                DieNow(OT_EXIT_ERROR_ERRNO);
-            }
-        }
-    }
-
-exit:
-    return error;
-}
diff --git a/src/posix/platform/udp.cpp b/src/posix/platform/udp.cpp
index 1d0307c..96b3b97 100644
--- a/src/posix/platform/udp.cpp
+++ b/src/posix/platform/udp.cpp
@@ -55,6 +55,10 @@
 
 #if OPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE
 
+#include "posix/platform/ip6_utils.hpp"
+
+using namespace ot::Posix::Ip6Utils;
+
 static const size_t kMaxUdpSize = 1280;
 
 static void *FdToHandle(int aFd)
@@ -319,7 +323,7 @@
         VerifyOrExit(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &gNetifName, strlen(gNetifName)) == 0,
                      error = OT_ERROR_FAILED);
 #else  // __NetBSD__ || __FreeBSD__ || __APPLE__
-        VerifyOrExit(setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &gNetifIndex, sizeof(gNetifIndex)) == 0,
+        VerifyOrExit(setsockopt(fd, IPPROTO_IPV6, IPV6_BOUND_IF, &gNetifIndex, sizeof(gNetifIndex)) == 0,
                      error = OT_ERROR_FAILED);
 #endif // __linux__
         break;
@@ -331,7 +335,8 @@
         VerifyOrExit(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, gBackboneNetifName, strlen(gBackboneNetifName)) == 0,
                      error = OT_ERROR_FAILED);
 #else  // __NetBSD__ || __FreeBSD__ || __APPLE__
-        VerifyOrExit(setsockopt(fd, IPPROTO_IP, IP_BOUND_IF, &gBackboneNetifIndex, sizeof(gBackboneNetifIndex)) == 0,
+        VerifyOrExit(setsockopt(fd, IPPROTO_IPV6, IPV6_BOUND_IF, &gBackboneNetifIndex, sizeof(gBackboneNetifIndex)) ==
+                         0,
                      error = OT_ERROR_FAILED);
 #endif // __linux__
 #else
@@ -392,26 +397,24 @@
         if (len > 0 && netifName[0] != '\0')
         {
             fd = FdFromHandle(aUdpSocket->mHandle);
-            VerifyOrExit(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &netifName, len) == 0, error = OT_ERROR_FAILED);
+            VerifyOrExit(setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &netifName, len) == 0, {
+                otLogWarnPlat("Failed to bind to device: %s", strerror(errno));
+                error = OT_ERROR_FAILED;
+            });
         }
 
         ExitNow();
 #endif
     }
 
-    switch (connect(fd, reinterpret_cast<struct sockaddr *>(&sin6), sizeof(sin6)))
+    if (connect(fd, reinterpret_cast<struct sockaddr *>(&sin6), sizeof(sin6)) != 0)
     {
-    case 0:
-        break;
 #ifdef __APPLE__
-    case EAFNOSUPPORT:
-        VerifyOrExit(isDisconnect, error = OT_ERROR_FAILED);
-        break;
+        VerifyOrExit(errno == EAFNOSUPPORT && isDisconnect);
 #endif
-
-    default:
+        otLogWarnPlat("Failed to connect to [%s]:%u: %s", Ip6AddressString(&aUdpSocket->mPeerName.mAddress).AsCString(),
+                      aUdpSocket->mPeerName.mPort, strerror(errno));
         error = OT_ERROR_FAILED;
-        break;
     }
 
 exit:
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 65feb22..2b00f1c 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -26,4 +26,14 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
-add_subdirectory(unit)
+if(OT_PLATFORM STREQUAL "simulation")
+    if(OT_FTD)
+        add_subdirectory(unit)
+    endif()
+endif()
+
+option(OT_FUZZ_TARGETS "enable fuzz targets" OFF)
+
+if(OT_FUZZ_TARGETS)
+    add_subdirectory(fuzz)
+endif()
diff --git a/tests/fuzz/CMakeLists.txt b/tests/fuzz/CMakeLists.txt
new file mode 100644
index 0000000..08b7375
--- /dev/null
+++ b/tests/fuzz/CMakeLists.txt
@@ -0,0 +1,130 @@
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+set(COMMON_INCLUDES
+    ${PROJECT_SOURCE_DIR}/include
+    ${PROJECT_SOURCE_DIR}/src/core
+)
+
+set(COMMON_COMPILE_OPTIONS
+    -DOPENTHREAD_FTD=1
+    -DOPENTHREAD_SPINEL_CONFIG_OPENTHREAD_MESSAGE_ENABLE=1
+)
+
+set(COMMON_SOURCES
+    fuzzer_platform.cpp
+)
+
+set(COMMON_LIBS
+    openthread-ftd
+    ${OT_MBEDTLS}
+    $ENV{LIB_FUZZING_ENGINE}
+    ot-config
+)
+
+add_executable(cli-received-fuzzer
+    cli_received.cpp
+    ${COMMON_SOURCES}
+)
+
+target_compile_options(cli-received-fuzzer
+    PRIVATE
+        ${COMMON_COMPILE_OPTIONS}
+)
+
+target_include_directories(cli-received-fuzzer
+    PRIVATE
+        ${COMMON_INCLUDES}
+)
+
+target_link_libraries(cli-received-fuzzer
+    PRIVATE
+        openthread-cli-ftd
+        ${COMMON_LIBS}
+)
+
+add_executable(ip6-send-fuzzer
+    ip6_send.cpp
+    ${COMMON_SOURCES}
+)
+
+target_compile_options(ip6-send-fuzzer
+    PRIVATE
+        ${COMMON_COMPILE_OPTIONS}
+)
+
+target_include_directories(ip6-send-fuzzer
+    PRIVATE
+        ${COMMON_INCLUDES}
+)
+
+target_link_libraries(ip6-send-fuzzer
+    PRIVATE
+        ${COMMON_LIBS}
+)
+
+add_executable(radio-receive-done-fuzzer
+    radio_receive_done.cpp
+    ${COMMON_SOURCES}
+)
+
+target_compile_options(radio-receive-done-fuzzer
+    PRIVATE
+        ${COMMON_COMPILE_OPTIONS}
+)
+
+target_include_directories(radio-receive-done-fuzzer
+    PRIVATE
+        ${COMMON_INCLUDES}
+)
+
+target_link_libraries(radio-receive-done-fuzzer
+    PRIVATE
+        ${COMMON_LIBS}
+)
+
+add_executable(ncp-hdlc-received-fuzzer
+    ncp_hdlc_received.cpp
+    ${COMMON_SOURCES}
+)
+
+target_compile_options(ncp-hdlc-received-fuzzer
+    PRIVATE
+        ${COMMON_COMPILE_OPTIONS}
+)
+
+target_include_directories(ncp-hdlc-received-fuzzer
+    PRIVATE
+        ${COMMON_INCLUDES}
+)
+
+target_link_libraries(ncp-hdlc-received-fuzzer
+    PRIVATE
+        openthread-ncp-ftd
+        ${COMMON_LIBS}
+)
diff --git a/tests/fuzz/Makefile.am b/tests/fuzz/Makefile.am
index bce0e31..0b2f1f4 100644
--- a/tests/fuzz/Makefile.am
+++ b/tests/fuzz/Makefile.am
@@ -29,10 +29,10 @@
 include $(abs_top_nlbuild_autotools_dir)/automake/pre.am
 
 bin_PROGRAMS                                              = \
-    cli-uart-received-fuzzer                                \
+    cli-received-fuzzer                                     \
     ip6-send-fuzzer                                         \
     radio-receive-done-fuzzer                               \
-    ncp-uart-received-fuzzer                                \
+    ncp-hdlc-received-fuzzer                                \
     $(NULL)
 
 AM_CPPFLAGS                                               = \
@@ -51,14 +51,14 @@
     fuzzer_platform.h                                       \
     $(NULL)
 
-cli_uart_received_fuzzer_LDADD                            = \
+cli_received_fuzzer_LDADD                                 = \
     $(top_builddir)/src/cli/libopenthread-cli-ftd.a         \
     $(COMMON_LDADD)                                         \
     $(NULL)
 
-cli_uart_received_fuzzer_SOURCES                          = \
+cli_received_fuzzer_SOURCES                               = \
     $(COMMON_SOURCES)                                       \
-    cli_uart_received.cpp                                   \
+    cli_received.cpp                                        \
     $(NULL)
 
 ip6_send_fuzzer_LDADD                                     = \
@@ -79,14 +79,14 @@
     radio_receive_done.cpp                                  \
     $(NULL)
 
-ncp_uart_received_fuzzer_LDADD                            = \
+ncp_hdlc_received_fuzzer_LDADD                            = \
     $(top_builddir)/src/ncp/libopenthread-ncp-ftd.a         \
     $(COMMON_LDADD)                                         \
     $(NULL)
 
-ncp_uart_received_fuzzer_SOURCES                          = \
+ncp_hdlc_received_fuzzer_SOURCES                          = \
     $(COMMON_SOURCES)                                       \
-    ncp_uart_received.cpp                                   \
+    ncp_hdlc_received.cpp                                   \
     $(NULL)
 
 include $(abs_top_nlbuild_autotools_dir)/automake/post.am
diff --git a/tests/fuzz/cli_uart_received.cpp b/tests/fuzz/cli_received.cpp
similarity index 86%
rename from tests/fuzz/cli_uart_received.cpp
rename to tests/fuzz/cli_received.cpp
index 83f77fe..2dcbbd8 100644
--- a/tests/fuzz/cli_uart_received.cpp
+++ b/tests/fuzz/cli_received.cpp
@@ -28,6 +28,8 @@
 
 #define MAX_ITERATIONS 100
 
+#include <stdarg.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
@@ -38,11 +40,19 @@
 #include <openthread/tasklet.h>
 #include <openthread/thread.h>
 #include <openthread/thread_ftd.h>
-#include <openthread/platform/uart.h>
 
 #include "fuzzer_platform.h"
 #include "common/code_utils.hpp"
 
+static int CliOutput(void *aContext, const char *aFormat, va_list aArguments)
+{
+    OT_UNUSED_VARIABLE(aContext);
+    OT_UNUSED_VARIABLE(aFormat);
+    OT_UNUSED_VARIABLE(aArguments);
+
+    return vsnprintf(nullptr, 0, aFormat, aArguments);
+}
+
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
 {
     const otPanId panId = 0xdead;
@@ -55,17 +65,18 @@
     FuzzerPlatformInit();
 
     instance = otInstanceInitSingle();
-    otCliUartInit(instance);
+    otCliInit(instance, CliOutput, nullptr);
     IgnoreError(otLinkSetPanId(instance, panId));
     IgnoreError(otIp6SetEnabled(instance, true));
     IgnoreError(otThreadSetEnabled(instance, true));
     IgnoreError(otThreadBecomeLeader(instance));
 
-    buf = static_cast<uint8_t *>(malloc(size));
+    buf = static_cast<uint8_t *>(malloc(size + 1));
 
     memcpy(buf, data, size);
+    buf[size] = '\0';
 
-    otPlatUartReceived(buf, (uint16_t)size);
+    otCliInputLine(reinterpret_cast<char *>(buf));
 
     VerifyOrExit(!FuzzerPlatformResetWasRequested());
 
diff --git a/tests/fuzz/fuzzer_platform.cpp b/tests/fuzz/fuzzer_platform.cpp
index 463a6d4..a8ae28b 100644
--- a/tests/fuzz/fuzzer_platform.cpp
+++ b/tests/fuzz/fuzzer_platform.cpp
@@ -40,7 +40,6 @@
 #include <openthread/platform/misc.h>
 #include <openthread/platform/radio.h>
 #include <openthread/platform/settings.h>
-#include <openthread/platform/uart.h>
 
 #include "mac/mac_frame.hpp"
 
@@ -493,28 +492,6 @@
     OT_UNUSED_VARIABLE(aInstance);
 }
 
-otError otPlatUartEnable(void)
-{
-    return OT_ERROR_NONE;
-}
-
-otError otPlatUartDisable(void)
-{
-    return OT_ERROR_NONE;
-}
-
-otError otPlatUartSend(const uint8_t *aBuf, uint16_t aBufLength)
-{
-    OT_UNUSED_VARIABLE(aBuf);
-    OT_UNUSED_VARIABLE(aBufLength);
-    return OT_ERROR_NONE;
-}
-
-otError otPlatUartFlush(void)
-{
-    return OT_ERROR_NOT_IMPLEMENTED;
-}
-
 otError otPlatDiagProcess(otInstance *aInstance,
                           uint8_t     aArgsLength,
                           char *      aArgs[],
diff --git a/tests/fuzz/ncp_uart_received.cpp b/tests/fuzz/ncp_hdlc_received.cpp
similarity index 91%
rename from tests/fuzz/ncp_uart_received.cpp
rename to tests/fuzz/ncp_hdlc_received.cpp
index 9f20aba..a61b0fa 100644
--- a/tests/fuzz/ncp_uart_received.cpp
+++ b/tests/fuzz/ncp_hdlc_received.cpp
@@ -38,11 +38,18 @@
 #include <openthread/tasklet.h>
 #include <openthread/thread.h>
 #include <openthread/thread_ftd.h>
-#include <openthread/platform/uart.h>
 
 #include "fuzzer_platform.h"
 #include "common/code_utils.hpp"
 
+static int HdlcSend(const uint8_t *aBuf, uint16_t aBufLength)
+{
+    OT_UNUSED_VARIABLE(aBuf);
+    OT_UNUSED_VARIABLE(aBufLength);
+
+    return aBufLength;
+}
+
 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
 {
     const otPanId panId = 0xdead;
@@ -55,7 +62,7 @@
     FuzzerPlatformInit();
 
     instance = otInstanceInitSingle();
-    otNcpInit(instance);
+    otNcpHdlcInit(instance, HdlcSend);
     IgnoreError(otLinkSetPanId(instance, panId));
     IgnoreError(otIp6SetEnabled(instance, true));
     IgnoreError(otThreadSetEnabled(instance, true));
@@ -65,7 +72,7 @@
 
     memcpy(buf, data, size);
 
-    otPlatUartReceived(buf, (uint16_t)size);
+    otNcpHdlcReceive(buf, static_cast<uint16_t>(size));
 
     VerifyOrExit(!FuzzerPlatformResetWasRequested());
 
diff --git a/tests/fuzz/oss-fuzz-build b/tests/fuzz/oss-fuzz-build
new file mode 100755
index 0000000..cbfba51
--- /dev/null
+++ b/tests/fuzz/oss-fuzz-build
@@ -0,0 +1,86 @@
+#!/bin/bash
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+(
+    mkdir build
+    cd build || exit
+
+    cmake -GNinja \
+        -DCMAKE_C_FLAGS="${CFLAGS}" \
+        -DCMAKE_CXX_FLAGS="${CXXFLAGS}" \
+        -DOT_BUILD_EXECUTABLES=OFF \
+        -DOT_FUZZ_TARGETS=ON \
+        -DOT_MTD=OFF \
+        -DOT_PLATFORM=external \
+        -DOT_RCP=OFF \
+        -DOT_BORDER_AGENT=ON \
+        -DOT_BORDER_ROUTER=ON \
+        -DOT_CHANNEL_MANAGER=ON \
+        -DOT_CHANNEL_MONITOR=ON \
+        -DOT_CHILD_SUPERVISION=ON \
+        -DOT_COAP=ON \
+        -DOT_COAPS=ON \
+        -DOT_COAP_BLOCK=ON \
+        -DOT_COAP_OBSERVE=ON \
+        -DOT_COMMISSIONER=ON \
+        -DOT_DATASET_UPDATER=ON \
+        -DOT_DHCP6_CLIENT=ON \
+        -DOT_DHCP6_SERVER=ON \
+        -DOT_DNS_CLIENT=ON \
+        -DOT_ECDSA=ON \
+        -DOT_IP6_FRAGM=ON \
+        -DOT_JAM_DETECTION=ON \
+        -DOT_JOINER=ON \
+        -DOT_LINK_RAW=ON \
+        -DOT_LOG_OUTPUT=APP \
+        -DOT_MAC_FILTER=ON \
+        -DOT_MTD_NETDIAG=ON \
+        -DOT_PING_SENDER=ON \
+        -DOT_SERVICE=ON \
+        -DOT_SLAAC=ON \
+        -DOT_SNTP_CLIENT=ON \
+        -DOT_SRP_CLIENT=ON \
+        -DOT_SRP_SERVER=ON \
+        -DOT_THREAD_VERSION=1.2 \
+        ..
+    ninja
+)
+
+find . -name '*-fuzzer' -exec cp -v '{}' "$OUT" ';'
+find . -name '*-fuzzer.dict' -exec cp -v '{}' "$OUT" ';'
+find . -name '*-fuzzer.options' -exec cp -v '{}' "$OUT" ';'
+
+fuzzers=$(find build/tests/fuzz -name "*-fuzzer")
+for f in $fuzzers; do
+    fuzzer=$(basename "$f" -fuzzer)
+
+    if [ -d "tests/fuzz/corpora/${fuzzer}" ]; then
+        zip -j "$OUT/$(basename "$f")"_seed_corpus.zip tests/fuzz/corpora/"${fuzzer}"/*
+    fi
+done
diff --git a/tests/scripts/expect/_common.exp b/tests/scripts/expect/_common.exp
index 25b04ed..c97f211 100644
--- a/tests/scripts/expect/_common.exp
+++ b/tests/scripts/expect/_common.exp
@@ -27,8 +27,15 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
+proc skip_on_macos {} {
+    set OSTYPE [lindex $::tcl_platform(os) 0]
+
+    if { $OSTYPE == "Darwin" } {
+        exit 77
+    }
+}
+
 proc wait_for {command success {failure {[\r\n]FAILURE_NOT_EXPECTED[\r\n]}}} {
-    set result 0
     set timeout 1
     for {set i 0} {$i < 20} {incr i} {
         if {$command != ""} {
@@ -37,22 +44,22 @@
 
         expect {
             -re $success {
-                set result 1
+                return 0
             }
             -re $failure {
-                error "Got failure in wait_for"
+                fail "Failed due to '$failure' found"
             }
             timeout {
                 # Do nothing
             }
         }
-        if {$result == 1} {
-            break
-        }
     }
-    if {$result == 0} {
-        error "Timed out in wait_for"
-    }
+    fail "Failed due to '$success' not found"
+}
+
+proc expect_line {line} {
+    expect -re "\[\r\n \]($line)(?=\[\r\n>\])"
+    return $expect_out(1,string)
 }
 
 proc spawn_node {id {type ""} {radio_url ""}} {
@@ -81,28 +88,28 @@
             spawn /usr/bin/env GCOV_PREFIX=$gcov_prefix $::env(OT_POSIX_APPS)/ot-cli $radio_url
             send "factoryreset\n"
             wait_for "state" "disabled"
-            expect "Done"
+            expect_line "Done"
             send "routerselectionjitter 1\n"
-            expect "Done"
+            expect_line "Done"
         }
         cli {
             spawn /usr/bin/env GCOV_PREFIX=$gcov_prefix $::env(OT_SIMULATION_APPS)/cli/ot-cli-ftd $id
             send "factoryreset\n"
             wait_for "state" "disabled"
-            expect "Done"
+            expect_line "Done"
             send "routerselectionjitter 1\n"
-            expect "Done"
+            expect_line "Done"
         }
         mtd {
             spawn /usr/bin/env GCOV_PREFIX=$gcov_prefix $::env(OT_SIMULATION_APPS)/cli/ot-cli-mtd $id
             send "factoryreset\n"
             wait_for "state" "disabled"
-            expect "Done"
+            expect_line "Done"
         }
     }
 
     expect_after {
-        timeout { error "Timed out" }
+        timeout { fail "Timed out" }
     }
 
     set spawn_ids($id) $spawn_id
@@ -118,25 +125,27 @@
     set spawn_id $spawn_ids($id)
 }
 
+proc setup_leader {} {
+    send "dataset init new\n"
+    expect_line "Done"
+    send "dataset masterkey 00112233445566778899aabbccddeeff\n"
+    expect_line "Done"
+    send "dataset commit active\n"
+    expect_line "Done"
+    send "ifconfig up\n"
+    expect_line "Done"
+    send "thread start\n"
+    expect_line "Done"
+    wait_for "state" "leader"
+    expect_line "Done"
+}
+
 proc dispose_node {id} {
     switch_node $id
     send "\x04"
     expect eof
 }
 
-proc setup_leader {} {
-    send "dataset init new\n"
-    expect "Done"
-    send "dataset commit active\n"
-    expect "Done"
-    send "ifconfig up\n"
-    expect "Done"
-    send "thread start\n"
-    expect "Done"
-    wait_for "state" "leader"
-    expect "Done"
-}
-
 proc dispose_all {} {
     global spawn_ids
     set max_node [array size spawn_ids]
@@ -149,18 +158,16 @@
 proc get_ipaddr {type} {
     send "ipaddr $type\n"
     expect "ipaddr $type"
-    expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-    set rval $expect_out(1,string)
-    expect "Done"
+    set rval [expect_line {([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}}]
+    expect_line "Done"
 
     return $rval
 }
 
 proc get_extaddr {} {
     send "extaddr\n"
-    expect -re {([0-9a-fA-F]{16})}
-    set rval $expect_out(1,string)
-    expect "Done"
+    set rval [expect_line {[0-9a-fA-F]{16}}]
+    expect_line "Done"
 
     return $rval
 }
@@ -168,11 +175,24 @@
 proc get_rloc16 {} {
     send "rloc16\n"
     expect "rloc16"
-    expect -re {([0-9a-fA-F]{4})}
-    set rval $expect_out(1,string)
-    expect "Done"
+    set rval [expect_line {[0-9a-fA-F]{4}}]
+    expect_line "Done"
 
     return $rval
 }
 
+proc setup_default_network {} {
+    send "channel 11\n"
+    expect_line "Done"
+    send "panid 0xface\n"
+    expect_line "Done"
+    send "masterkey 00112233445566778899aabbccddeeff\n"
+    expect_line "Done"
+}
+
+proc fail {message} {
+    dispose_all
+    error $message
+}
+
 set timeout 10
diff --git a/tests/scripts/expect/_multinode.exp b/tests/scripts/expect/_multinode.exp
index 4d068d5..9170c3f 100644
--- a/tests/scripts/expect/_multinode.exp
+++ b/tests/scripts/expect/_multinode.exp
@@ -29,50 +29,61 @@
 
 source "tests/scripts/expect/_common.exp"
 
-proc setup_two_nodes {{childmode {r}} {should_spawn_node true}} {
+set JOINER_PSK "J01NME"
+
+proc setup_two_nodes {{childmode "r"} {should_spawn_node true}} {
     # Sets up a Thread network with 2 nodes, spawn_1 as the leader and spawn_2
     # as a child.
-    set psk "J01NME"
 
     if {$should_spawn_node} {
         spawn_node 2
-    } else {
-        switch_node 2
-    }
-    send "eui64\n"
-    expect -re {([0-9a-f]{16})}
-    set eui64 $expect_out(1,string)
-    expect "Done"
-
-    # Sets up a Thread network with node 1 as the leader.
-    if {$should_spawn_node} {
         spawn_node 1
     } else {
         switch_node 1
     }
     setup_leader
+    setup_node 2 $childmode "child"
+}
 
-    send "commissioner start\n"
-    expect "Done"
-    expect "Commissioner: active"
-    send "commissioner joiner add $eui64 $psk\n"
-    expect "Done"
+proc setup_node {node {mode "r"} {role "child"}} {
+    switch_node $node
+    send "eui64\n"
+    expect "eui64"
+    expect -re {([0-9a-f]{16})}
+    set eui64 $expect_out(1,string)
+    expect_line "Done"
+
+    switch_node 1
+    send "commissioner state\n"
+    expect {
+        "disabled" {
+            expect_line "Done"
+            send "commissioner start\n"
+            expect_line "Done"
+            expect "Commissioner: active"
+        }
+        "active" {
+            expect_line "Done"
+        }
+    }
+    send "commissioner joiner add $eui64 $::JOINER_PSK\n"
+    expect_line "Done"
     wait_for "netdata steeringdata check $eui64" "Done"
-    send "channel\n"
-    expect -re {(\d+)}
-    set channel $expect_out(1,string)
-    expect "Done"
 
-    switch_node 2
-    send "mode $childmode\n"
-    expect "Done"
+    switch_node $node
+    send "mode $mode\n"
+    expect_line "Done"
     send "ifconfig up\n"
-    expect "Done"
-    send "joiner start $psk\n"
-    expect "Done"
-    wait_for "" "Join success"
+    expect_line "Done"
+    send "joiner start $::JOINER_PSK\n"
+    expect_line "Done"
+    wait_for "" "Join success" "Join failed"
     send "thread start\n"
-    expect "Done"
-    wait_for "state" "child"
-    expect "Done"
+    expect_line "Done"
+    wait_for "state" $role
+    expect_line "Done"
+
+    switch_node 1
+    send "commissioner joiner remove $eui64\n"
+    expect_line "Done"
 }
diff --git a/tests/scripts/expect/cli-big-table.exp b/tests/scripts/expect/cli-big-table.exp
index e8ae9b9..a1fd28e 100755
--- a/tests/scripts/expect/cli-big-table.exp
+++ b/tests/scripts/expect/cli-big-table.exp
@@ -28,34 +28,19 @@
 #
 
 source "tests/scripts/expect/_common.exp"
+source "tests/scripts/expect/_multinode.exp"
 
 set max_node 15
 spawn_node 1
-send "panid 0xface\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-wait_for "state" "leader"
-expect "Done"
+setup_leader
 
 for {set i 2} {$i <= $max_node} {incr i} {
     spawn_node $i
-    send "mode r\n"
-    expect "Done"
-    send "panid 0xface\n"
-    expect "Done"
-    send "ifconfig up\n"
-    expect "Done"
-    send "thread start\n"
-    expect "Done"
-    wait_for "state" "child"
-    expect "Done"
+    setup_node $i
 }
 
 set spawn_id $spawn_ids(1)
 send "child table\n"
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/expect/cli-channel.exp b/tests/scripts/expect/cli-channel.exp
index e6ef4ef..238bac9 100755
--- a/tests/scripts/expect/cli-channel.exp
+++ b/tests/scripts/expect/cli-channel.exp
@@ -32,17 +32,17 @@
 spawn_node 1
 
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 
 send "channel monitor stop\n"
-expect "Done"
+expect_line "Done"
 send "channel monitor\n"
 expect "enabled: 0"
-expect "Done"
+expect_line "Done"
 send "channel monitor start\n"
-expect "Done"
+expect_line "Done"
 send "channel monitor\n"
 expect "enabled: 1"
 expect -re {interval: \d+}
@@ -53,28 +53,28 @@
 for {set i 11} {$i <= 26} {incr i} {
     expect -re "ch $i \\(0x\[0-9a-f\]{4}\\) +\\d+\\.\\d+% busy"
 }
-expect "Done"
+expect_line "Done"
 send "channel monitor something_invalid\n"
 expect "Error 7: InvalidArgs"
 
 send "channel manager change 15\n"
-expect "Done"
+expect_line "Done"
 send "channel manager\n"
 expect "channel: 15"
 expect "auto: 0"
-expect "Done"
+expect_line "Done"
 send "channel manager select 1\n"
 expect "Error 13: InvalidState" # Because of insufficient channel monitor samples
 send "channel manager delay 200\n"
-expect "Done"
+expect_line "Done"
 send "channel manager interval 20000\n"
-expect "Done"
+expect_line "Done"
 send "channel manager supported 0x7fff800\n"
-expect "Done"
+expect_line "Done"
 send "channel manager favored 0x7fff800\n"
-expect "Done"
+expect_line "Done"
 send "channel manager auto 1\n"
-expect "Done"
+expect_line "Done"
 send "channel manager\n"
 expect "channel: 15"
 expect "auto: 1"
@@ -82,7 +82,7 @@
 expect "interval: 20000"
 expect "supported: { 11-26}"
 expect "favored: { 11-26}"
-expect "Done"
+expect_line "Done"
 send "channel manager something_invalid\n"
 expect "Error 7: InvalidArgs"
 
diff --git a/tests/scripts/expect/cli-child-supervision.exp b/tests/scripts/expect/cli-child-supervision.exp
index 1c8bf7f..6c8f1c1 100755
--- a/tests/scripts/expect/cli-child-supervision.exp
+++ b/tests/scripts/expect/cli-child-supervision.exp
@@ -34,14 +34,14 @@
 
 switch_node 1
 send "childsupervision interval 30\n"
-expect "Done"
+expect_line "Done"
 send "childsupervision interval\n"
-expect "Done"
+expect_line "Done"
 
 switch_node 2
 send "childsupervision checktimeout 30\n"
-expect "Done"
+expect_line "Done"
 send "childsupervision checktimeout\n"
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/expect/cli-child.exp b/tests/scripts/expect/cli-child.exp
index 9ac2a5c..1e73099 100755
--- a/tests/scripts/expect/cli-child.exp
+++ b/tests/scripts/expect/cli-child.exp
@@ -33,16 +33,8 @@
 setup_two_nodes
 
 switch_node 2
-send "extaddr\n"
-expect "extaddr"
-expect -re {([0-9a-f]{16})}
-set extaddr $expect_out(1,string)
-expect "Done"
-send "rloc16\n"
-expect "rloc16"
-expect -re {([0-9a-f]{4})}
-set rloc $expect_out(1,string)
-expect "Done"
+set extaddr [get_extaddr]
+set rloc [get_rloc16]
 
 switch_node 1
 send "child table\n"
@@ -50,13 +42,13 @@
 expect "+-----+--------+------------+------------+-------+------+-+-+-+---+---+-------+------------------+"
 expect -re "\\| +(\\d+) \\| 0x$rloc \\| +\\d+ \\| +\\d+ \\| +\\d+ \\| +\\d+ \\|\\d\\|\\d\\|\\d\\| *\\d+\\| \\d \\| +\\d+ \\| $extaddr \\|"
 set child_id $expect_out(1,string)
-expect "Done"
+expect_line "Done"
 send "child list\n"
 expect "child list"
 expect $child_id
-expect "Done"
+expect_line "Done"
 send "child $child_id\n"
 expect "Ext Addr: $extaddr"
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/expect/cli-childip.exp b/tests/scripts/expect/cli-childip.exp
index 9a6d06a..294324d 100755
--- a/tests/scripts/expect/cli-childip.exp
+++ b/tests/scripts/expect/cli-childip.exp
@@ -33,26 +33,18 @@
 setup_two_nodes
 
 switch_node 2
-send "ipaddr mleid\n"
-expect "ipaddr mleid"
-expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-set addr $expect_out(1,string)
-expect "Done"
-send "rloc16\n"
-expect "rloc16"
-expect -re {([0-9a-f]{4})}
-set rloc $expect_out(1,string)
-expect "Done"
+set addr [get_ipaddr mleid]
+set rloc [get_rloc16]
 
 switch_node 1
 send "childip\n"
 expect "$rloc: $addr"
-expect "Done"
+expect_line "Done"
 send "childip max 2\n"
-expect "Done"
+expect_line "Done"
 send "childip max\n"
 expect "2"
-expect "Done"
+expect_line "Done"
 send "childip max something_invalid\n"
 expect "Error 7: InvalidArgs"
 send "childip something_invalid\n"
diff --git a/tests/scripts/expect/cli-coap.exp b/tests/scripts/expect/cli-coap.exp
index 871da39..f7bb654 100755
--- a/tests/scripts/expect/cli-coap.exp
+++ b/tests/scripts/expect/cli-coap.exp
@@ -34,47 +34,39 @@
 
 switch_node 1
 send "coap start\n"
-expect "Done"
+expect_line "Done"
 send "coap resource test/resource\n"
-expect "Done"
+expect_line "Done"
 send "coap set Testing123\n"
-expect "Done"
-send "ipaddr mleid\n"
-expect "ipaddr mleid"
-expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-set addr_1 $expect_out(1,string)
-expect "Done"
+expect_line "Done"
+set addr_1 [get_ipaddr mleid]
 
 switch_node 2
 send "coap start\n"
-expect "Done"
+expect_line "Done"
 send "coap parameters request 3000 4 3 5\n"
 expect "Transmission parameters for request:"
 expect "ACK_TIMEOUT=3000 ms, ACK_RANDOM_FACTOR=4/3, MAX_RETRANSMIT=5"
-expect "Done"
+expect_line "Done"
 send "coap parameters response 2500 4 3 3\n"
 expect "Transmission parameters for response:"
 expect "ACK_TIMEOUT=2500 ms, ACK_RANDOM_FACTOR=4/3, MAX_RETRANSMIT=3"
-expect "Done"
+expect_line "Done"
 send "coap get $addr_1 test/resource\n"
-expect "Done"
+expect_line "Done"
 expect "coap response from $addr_1 with payload: 54657374696e67313233" # ASCII of "Testing123"
 send "coap post $addr_1 test/resource con Testing123\n"
-expect "Done"
+expect_line "Done"
 expect "coap response from $addr_1"
 send "coap put $addr_1 test/resource con Testing123\n"
-expect "Done"
+expect_line "Done"
 expect "coap response from $addr_1"
 send "coap delete $addr_1 test/resource con\n"
-expect "Done"
+expect_line "Done"
 expect "coap response from $addr_1"
 send "coap post $addr_1 test/resource none Testing123\n"
-expect "Done"
-send "ipaddr mleid\n"
-expect "ipaddr mleid"
-expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-set addr_2 $expect_out(1,string)
-expect "Done"
+expect_line "Done"
+set addr_2 [get_ipaddr mleid]
 
 switch_node 1
 expect "coap request from $addr_2 GET"
@@ -88,20 +80,20 @@
 expect "coap request from $addr_2 POST with payload: 54657374696e67313233"
 send "coap resource\n"
 expect "test/resource"
-expect "Done"
+expect_line "Done"
 send "coap set\n"
 expect "Testing123"
-expect "Done"
+expect_line "Done"
 send "coap parameters request default\n"
 expect "Transmission parameters for request:"
 expect "default"
-expect "Done"
+expect_line "Done"
 send "coap parameters response default\n"
 expect "Transmission parameters for response:"
 expect "default"
-expect "Done"
+expect_line "Done"
 send "coap help\n"
-expect "Done"
+expect_line "Done"
 send "coap parameters something_invalid\n"
 expect "Error 7: InvalidArgs"
 send "coap get\n"
diff --git a/tests/scripts/expect/cli-coaps.exp b/tests/scripts/expect/cli-coaps.exp
index e2ee895..6a95212 100755
--- a/tests/scripts/expect/cli-coaps.exp
+++ b/tests/scripts/expect/cli-coaps.exp
@@ -34,49 +34,42 @@
 
 switch_node 1
 send "coaps x509\n"
-expect "Done"
+expect_line "Done"
 send "coaps start\n"
-expect "Done"
+expect_line "Done"
 send "coaps resource test/resource\n"
-expect "Done"
+expect_line "Done"
 send "coaps set Testing123\n"
-expect "Done"
-send "ipaddr mleid\n"
-expect "ipaddr mleid"
-expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-set addr_1 $expect_out(1,string)
-expect "Done"
+expect_line "Done"
+
+set addr_1 [get_ipaddr mleid]
 
 switch_node 2
 send "coaps x509\n"
-expect "Done"
+expect_line "Done"
 send "coaps start\n"
-expect "Done"
+expect_line "Done"
 send "coaps connect $addr_1 5684\n"
-expect "Done"
+expect_line "Done"
 expect "coaps connected"
 send "coaps get $addr_1 test/resource\n"
-expect "Done"
+expect_line "Done"
 expect "coaps response from $addr_1 with payload: 54657374696e67313233" # ASCII of "Testing123"
 send "coaps post $addr_1 test/resource con Testing123\n"
-expect "Done"
+expect_line "Done"
 expect "coaps response from $addr_1"
 send "coaps put $addr_1 test/resource con Testing123\n"
-expect "Done"
+expect_line "Done"
 expect "coaps response from $addr_1"
 send "coaps delete $addr_1 test/resource con\n"
-expect "Done"
+expect_line "Done"
 expect "coaps response from $addr_1"
 send "coaps post $addr_1 test/resource none Testing123\n"
-expect "Done"
+expect_line "Done"
 send "coaps get $addr_1 default\n"
-expect "Done"
+expect_line "Done"
 expect "coaps response from $addr_1"
-send "ipaddr mleid\n"
-expect "ipaddr mleid"
-expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-set addr_2 $expect_out(1,string)
-expect "Done"
+set addr_2 [get_ipaddr mleid]
 
 switch_node 1
 expect "coaps request from $addr_2 GET"
@@ -90,12 +83,12 @@
 expect "coaps request from $addr_2 POST with payload: 54657374696e67313233"
 send "coaps resource\n"
 expect "test/resource"
-expect "Done"
+expect_line "Done"
 send "coaps set\n"
 expect "Testing123"
-expect "Done"
+expect_line "Done"
 send "coaps help\n"
-expect "Done"
+expect_line "Done"
 send "coaps get\n"
 expect "Error 7: InvalidArgs"
 send "coaps\n"
@@ -107,10 +100,10 @@
 
 switch_node 2
 send "coaps stop\n"
-expect "Done"
+expect_line "Done"
 
 switch_node 1
 send "coaps stop\n"
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/expect/cli-coex.exp b/tests/scripts/expect/cli-coex.exp
index ca26b37..9bfbfb6 100755
--- a/tests/scripts/expect/cli-coex.exp
+++ b/tests/scripts/expect/cli-coex.exp
@@ -32,15 +32,15 @@
 spawn_node 1
 
 send "coex disable\n"
-expect "Done"
+expect_line "Done"
 send "coex\n"
 expect "Disabled"
-expect "Done"
+expect_line "Done"
 send "coex enable\n"
-expect "Done"
+expect_line "Done"
 send "coex\n"
 expect "Enabled"
-expect "Done"
+expect_line "Done"
 send "coex metrics\n"
 expect -re {Stopped: (true|false)}
 expect -re {Grant Glitch: \d+}
@@ -63,7 +63,7 @@
 expect -re {    Delayed Grant: \d+}
 expect -re {    Average Request To Grant Time: \d+}
 expect -re {    Grant None: \d+}
-expect "Done"
+expect_line "Done"
 send "coex something_invalid\n"
 expect "Error 7: InvalidArgs"
 
diff --git a/tests/scripts/expect/cli-commissioner.exp b/tests/scripts/expect/cli-commissioner.exp
index f4b4e16..1474d2f 100755
--- a/tests/scripts/expect/cli-commissioner.exp
+++ b/tests/scripts/expect/cli-commissioner.exp
@@ -35,46 +35,46 @@
 set eui64 "d45e64fa83f81cf7"
 switch_node 1
 send "commissioner joiner add $eui64 J01NME\n"
-expect "Done"
+expect_line "Done"
 wait_for "netdata steeringdata check $eui64" "Done"
 send "commissioner joiner remove $eui64\n"
-expect "Done"
+expect_line "Done"
 wait_for "netdata steeringdata check $eui64" "NotFound"
 
 switch_node 2
 send "commissioner state\n"
 expect "disabled"
-expect "Done"
+expect_line "Done"
 send "commissioner start\n"
-expect "Done"
+expect_line "Done"
 expect "Commissioner: active"
 send "commissioner state\n"
 expect "active"
-expect "Done"
+expect_line "Done"
 send "commissioner provisioningurl openthread.io\n"
-expect "Done"
+expect_line "Done"
 send "commissioner joiner add * J01NME 1\n"
-expect "Done"
+expect_line "Done"
 send "commissioner joiner remove *\n"
-expect "Done"
+expect_line "Done"
 send "commissioner sessionid\n"
 expect "commissioner sessionid"
 expect -re {(\d+)}
 set sessionid $expect_out(1,string)
 send "commissioner mgmtset sessionid $sessionid steeringdata ffffffff joinerudpport 10001\n"
-expect "Done"
+expect_line "Done"
 send "commissioner mgmtset sessionid $sessionid locator 0x0100\n"
-expect "Done"
+expect_line "Done"
 send "commissioner mgmtget sessionid steeringdata joinerudpport locator binary 0b081209\n"
-expect "Done"
+expect_line "Done"
 send "commissioner stop\n"
 expect "Commissioner: disabled"
-expect "Done"
+expect_line "Done"
 send "commissioner state\n"
 expect "disabled"
-expect "Done"
+expect_line "Done"
 send "commissioner help\n"
-expect "Done"
+expect_line "Done"
 send "commissioner joiner something_invalid\n"
 expect "Error 7: InvalidArgs"
 send "commissioner mgmtget something_invalid\n"
diff --git a/tests/scripts/expect/cli-counters.exp b/tests/scripts/expect/cli-counters.exp
index eda2de1..359d3b4 100755
--- a/tests/scripts/expect/cli-counters.exp
+++ b/tests/scripts/expect/cli-counters.exp
@@ -34,15 +34,15 @@
 send "counters\n"
 expect "mac"
 expect "mle"
-expect "Done"
+expect_line "Done"
 send "counters mac\n"
-expect "Done"
+expect_line "Done"
 send "counters mle\n"
-expect "Done"
+expect_line "Done"
 send "counters mac reset\n"
-expect "Done"
+expect_line "Done"
 send "counters mle reset\n"
-expect "Done"
+expect_line "Done"
 send "counters mac 1\n"
 expect "Error 7: InvalidArgs"
 send "counters mle 1\n"
diff --git a/tests/scripts/expect/cli-dataset.exp b/tests/scripts/expect/cli-dataset.exp
index 259f9cf..5af33eb 100755
--- a/tests/scripts/expect/cli-dataset.exp
+++ b/tests/scripts/expect/cli-dataset.exp
@@ -48,77 +48,77 @@
 send "dataset pending\n"
 expect "Error 23: NotFound"
 send "dataset init active\n"
-expect "Done"
+expect_line "Done"
 send "dataset activetimestamp 100\n"
-expect "Done"
+expect_line "Done"
 send "dataset activetimestamp\n"
 expect "100"
-expect "Done"
+expect_line "Done"
 if {$channel == 11} {
     send "dataset channel 18\n"
-    expect "Done"
+    expect_line "Done"
     send "dataset channel\n"
     expect "18"
-    expect "Done"
+    expect_line "Done"
 } else {
     send "dataset channel 11\n"
-    expect "Done"
+    expect_line "Done"
     send "dataset channel\n"
     expect "11"
-    expect "Done"
+    expect_line "Done"
 }
 send "dataset channelmask 0x03fff800\n"
-expect "Done"
+expect_line "Done"
 send "dataset channelmask\n"
 expect "0x03fff800"
-expect "Done"
+expect_line "Done"
 send "dataset extpanid aabbccddeeff0011\n"
-expect "Done"
+expect_line "Done"
 send "dataset extpanid\n"
 expect "aabbccddeeff0011"
-expect "Done"
+expect_line "Done"
 send "dataset masterkey aabbccddeeff00112233445566778899\n"
-expect "Done"
+expect_line "Done"
 send "dataset masterkey\n"
 expect "aabbccddeeff00112233445566778899"
-expect "Done"
+expect_line "Done"
 send "dataset meshlocalprefix fdde:4860::\n"
-expect "Done"
+expect_line "Done"
 send "dataset meshlocalprefix\n"
 expect "fdde:4860:0:0::/64"
-expect "Done"
+expect_line "Done"
 send "dataset networkname OT-network\n"
-expect "Done"
+expect_line "Done"
 send "dataset networkname\n"
 expect "OT-network"
-expect "Done"
+expect_line "Done"
 send "dataset panid 0xface\n"
-expect "Done"
+expect_line "Done"
 send "dataset panid\n"
 expect "0xface"
-expect "Done"
+expect_line "Done"
 send "dataset pskc 00112233445566778899aabbccddeeff\n"
-expect "Done"
+expect_line "Done"
 send "dataset pskc\n"
 expect "00112233445566778899aabbccddeeff"
-expect "Done"
+expect_line "Done"
 send "dataset securitypolicy 678 onrcb\n"
-expect "Done"
+expect_line "Done"
 send "dataset securitypolicy\n"
 expect "678 onrcb"
-expect "Done"
+expect_line "Done"
 send "dataset pendingtimestamp 100\n"
-expect "Done"
+expect_line "Done"
 send "dataset pendingtimestamp\n"
 expect "100"
-expect "Done"
+expect_line "Done"
 send "dataset delay 30000\n"
-expect "Done"
+expect_line "Done"
 send "dataset delay\n"
 expect "30000"
-expect "Done"
+expect_line "Done"
 send "dataset commit pending\n"
-expect "Done"
+expect_line "Done"
 send "dataset pending\n"
 expect "Pending Timestamp: 100"
 expect "Active Timestamp: 100"
@@ -136,13 +136,13 @@
 expect "PAN ID: 0xface"
 expect "PSKc: 00112233445566778899aabbccddeeff"
 expect "Security Policy: 678, onrcb"
-expect "Done"
+expect_line "Done"
 
 sleep 30
 
 switch_node 2
 wait_for "dataset active" "Active Timestamp: 100"
-expect "Done"
+expect_line "Done"
 send "dataset active\n"
 expect "Active Timestamp: 100"
 if {$channel == 11} {
@@ -158,11 +158,11 @@
 expect "PAN ID: 0xface"
 expect "PSKc: 00112233445566778899aabbccddeeff"
 expect "Security Policy: 678, onrcb"
-expect "Done"
+expect_line "Done"
 send "dataset clear\n"
-expect "Done"
+expect_line "Done"
 send "dataset init active\n"
-expect "Done"
+expect_line "Done"
 send "dataset\n"
 expect "Active Timestamp: 100"
 if {$channel == 11} {
@@ -178,16 +178,12 @@
 expect "PAN ID: 0xface"
 expect "PSKc: 00112233445566778899aabbccddeeff"
 expect "Security Policy: 678, onrcb"
-expect "Done"
+expect_line "Done"
 send "dataset init pending\n"
 expect "Error 23: NotFound"
 
 switch_node 1
-send "ipaddr mleid\n"
-expect "ipaddr mleid"
-expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-set addr $expect_out(1,string)
-expect "Done"
+set addr [get_ipaddr mleid]
 
 switch_node 2
 send "dataset mgmtgetcommand active \
@@ -195,60 +191,60 @@
 localprefix delaytimer panid channel \
 -x 000102030405060708090a0b0e0f0c333435 \
 address $addr\n"
-expect "Done"
+expect_line "Done"
 send "dataset mgmtgetcommand pending \
 activetimestamp pendingtimestamp masterkey networkname extpanid \
 localprefix delaytimer panid channel \
 -x 000102030405060708090a0b0e0f0c333435 \
 address $addr\n"
-expect "Done"
+expect_line "Done"
 
 switch_node 1
 send "dataset init active\n"
-expect "Done"
+expect_line "Done"
 send "dataset networkname Thread\\ 网络\n"
-expect "Done"
+expect_line "Done"
 send "dataset commit active\n"
-expect "Done"
+expect_line "Done"
 send "dataset active -x\n"
 expect "54687265616420e7bd91e7bb9c" ;# UTF-8 of "Thread 网络"
-expect "Done"
+expect_line "Done"
 send "dataset active -x\n"
 expect "dataset active -x"
 expect -re {([0-9a-f]+)[\r\n]+Done}
 set binary $expect_out(1,string)
 send "dataset set pending $binary\n"
-expect "Done"
+expect_line "Done"
 send "dataset pending -x\n"
 expect $binary
-expect "Done"
+expect_line "Done"
 send "dataset pending\n"
 expect "Network Name: Thread 网络"
-expect "Done"
+expect_line "Done"
 send "dataset set active $binary\n"
-expect "Done"
+expect_line "Done"
 send "dataset mgmtsetcommand active activetimestamp 200 -x 030d54687265616420e7bd91e7bb9c\n"
-expect "Done"
+expect_line "Done"
 send "dataset active\n"
 expect "Network Name: Thread 网络"
-expect "Done"
+expect_line "Done"
 send "dataset mgmtsetcommand active activetimestamp 210 -x 0301ff\n"
-expect "Done"
+expect_line "Done"
 send "dataset active\n"
 expect "Active Timestamp: 200"
 expect "Network Name: Thread 网络"
-expect "Done"
+expect_line "Done"
 send "dataset set active 03023432\n"
-expect "Done"
+expect_line "Done"
 send "dataset active\n"
 expect "Network Name: 42"
-expect "Done"
+expect_line "Done"
 send "dataset set active 0301bf\n"
 expect "Error 7: InvalidArgs"
 send "dataset help\n"
-expect "Done"
+expect_line "Done"
 send "dataset\n"
-expect "Done"
+expect_line "Done"
 send "dataset init something_invalid\n"
 expect "Error 7: InvalidArgs"
 send "dataset active something_invalid\n"
@@ -266,7 +262,7 @@
 send "dataset mgmtgetcommand active something_invalid\n"
 expect "Error 7: InvalidArgs"
 send "dataset pskc -p 123456\n"
-expect "Done"
+expect_line "Done"
 send "dataset securitypolicy 678 something_invalid\n"
 expect "Error 7: InvalidArgs"
 send "dataset set something_invalid 00\n"
diff --git a/tests/scripts/expect/cli-discerner.exp b/tests/scripts/expect/cli-discerner.exp
index 64f422b..07a9e41 100755
--- a/tests/scripts/expect/cli-discerner.exp
+++ b/tests/scripts/expect/cli-discerner.exp
@@ -44,27 +44,27 @@
 send "netdata steeringdata check $discerner\n"
 expect "InvalidState"
 send "commissioner start\n"
-expect "Done"
+expect_line "Done"
 expect "Commissioner: active"
 send "commissioner joiner add 1/0 $pskd\n"
 expect "InvalidArgs"
 send "netdata steeringdata check 0xabc/12\n"
 expect "NotFound"
 send "commissioner joiner add $discerner $pskd\n"
-expect "Done"
+expect_line "Done"
 wait_for "netdata steeringdata check $discerner" "Done"
 send "commissioner joiner remove $discerner\n"
-expect "Done"
+expect_line "Done"
 wait_for "netdata steeringdata check $discerner" "NotFound"
 send "commissioner joiner add $discerner $pskd\n"
-expect "Done"
+expect_line "Done"
 
 spawn_node 2
 
 send "mode r\n"
-expect "Done"
+expect_line "Done"
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 send "joiner discerner\n"
 expect "NotFound"
 send "joiner discerner 1/1 2\n"
@@ -82,18 +82,18 @@
 send "joiner discerner invalid/20\n"
 expect "InvalidArgs"
 send "joiner discerner clear\n"
-expect "Done"
+expect_line "Done"
 send "joiner discerner $discerner\n"
-expect "Done"
+expect_line "Done"
 send "joiner discerner\n"
 expect "$discerner"
-expect "Done"
+expect_line "Done"
 send "joiner start $pskd\n"
-expect "Done"
+expect_line "Done"
 wait_for "" "Join success" "Join failed"
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 wait_for "state" "child"
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/expect/cli-extaddr.exp b/tests/scripts/expect/cli-extaddr.exp
index a4fc42d..ca48271 100755
--- a/tests/scripts/expect/cli-extaddr.exp
+++ b/tests/scripts/expect/cli-extaddr.exp
@@ -32,9 +32,9 @@
 spawn_node 1
 
 send "extaddr 99aabbccddeeff00\n"
-expect "Done"
+expect_line "Done"
 send "extaddr 99AABBCCDDEEFF00\n"
-expect "Done"
+expect_line "Done"
 send "extaddr\n"
 expect "99aabbccddeeff00"
 send "extaddr 1\n"
diff --git a/tests/scripts/expect/cli-ipmaddr.exp b/tests/scripts/expect/cli-ipmaddr.exp
index 5084ca2..a4e324f 100755
--- a/tests/scripts/expect/cli-ipmaddr.exp
+++ b/tests/scripts/expect/cli-ipmaddr.exp
@@ -34,35 +34,32 @@
 
 switch_node 1
 send "ipmaddr add ff0e::1\n"
-expect "Done"
+expect_line "Done"
 send "ipmaddr\n"
 expect "ff0e:0:0:0:0:0:0:1"
-expect "Done"
-send "ipaddr mleid\n"
-expect "ipaddr mleid"
-expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-set addr $expect_out(1,string)
+expect_line "Done"
+set addr [get_ipaddr mleid]
 
 switch_node 2
 send "ping ff0e::1\n"
-expect "Done"
+expect_line "Done"
 expect "16 bytes from $addr: icmp_seq=1"
 
 switch_node 1
 send "ipmaddr del ff0e::1\n"
-expect "Done"
+expect_line "Done"
 send "ipmaddr del ff0e::1\n"
 expect "Error 23: NotFound"
 send "ipmaddr promiscuous enable\n"
-expect "Done"
+expect_line "Done"
 send "ipmaddr promiscuous\n"
 expect "Enabled"
-expect "Done"
+expect_line "Done"
 send "ipmaddr promiscuous disable\n"
-expect "Done"
+expect_line "Done"
 send "ipmaddr promiscuous\n"
 expect "Disabled"
-expect "Done"
+expect_line "Done"
 send "ipmaddr something_invalid\n"
 expect "Error 35: InvalidCommand"
 
diff --git a/tests/scripts/expect/cli-log-level.exp b/tests/scripts/expect/cli-log-level.exp
index c7685fc..8544d5a 100755
--- a/tests/scripts/expect/cli-log-level.exp
+++ b/tests/scripts/expect/cli-log-level.exp
@@ -33,12 +33,12 @@
 
 send "log level\n"
 expect -re {\d}
-expect "Done"
+expect_line "Done"
 send "log level 5\n"
-expect "Done"
+expect_line "Done"
 send "log level\n"
 expect "5"
-expect "Done"
+expect_line "Done"
 send "log level -1\n"
 expect "Error 7: InvalidArgs"
 send "log level 6\n"
diff --git a/tests/scripts/expect/cli-mac.exp b/tests/scripts/expect/cli-mac.exp
index 0d6865d..0d30544 100755
--- a/tests/scripts/expect/cli-mac.exp
+++ b/tests/scripts/expect/cli-mac.exp
@@ -32,16 +32,16 @@
 spawn_node 1
 
 send "mac retries direct 5\n"
-expect "Done"
+expect_line "Done"
 send "mac retries direct\n"
 expect "5"
-expect "Done"
+expect_line "Done"
 
 send "mac retries indirect 2\n"
-expect "Done"
+expect_line "Done"
 send "mac retries indirect\n"
 expect "2"
-expect "Done"
+expect_line "Done"
 
 send "mac\n"
 expect "Error 7: InvalidArgs"
diff --git a/tests/scripts/expect/cli-macfilter.exp b/tests/scripts/expect/cli-macfilter.exp
index 5892b83..21456b3 100755
--- a/tests/scripts/expect/cli-macfilter.exp
+++ b/tests/scripts/expect/cli-macfilter.exp
@@ -34,108 +34,108 @@
 send "macfilter\n"
 expect "Address Mode: Disabled"
 expect "RssIn List:"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr add aabbccddeeff0011\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr\n"
 expect "Disabled"
 expect "aabbccddeeff0011"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr allowlist\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr\n"
 expect "Allowlist"
 expect "aabbccddeeff0011"
-expect "Done"
+expect_line "Done"
 
 send "macfilter\n"
 expect "Address Mode: Allowlist"
 expect "aabbccddeeff0011"
 expect "RssIn List:"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr add 2233445566778899\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr remove aabbccddeeff0011\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr denylist\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr\n"
 expect "Denylist"
 expect "2233445566778899"
-expect "Done"
+expect_line "Done"
 
 send "macfilter\n"
 expect "Address Mode: Denylist"
 expect "2233445566778899"
 expect "RssIn List:"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr clear\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr disable\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr\n"
 expect "Disabled"
-expect "Done"
+expect_line "Done"
 
 send "macfilter\n"
 expect "Address Mode: Disabled"
 expect "RssIn List:"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr something_invalid\n"
 expect "Error 35: InvalidCommand"
 
 send "macfilter rss\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter rss add-lqi * 2\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter rss add-lqi aabbccddeeff0011 3\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter rss\n"
 expect -re {aabbccddeeff0011 : rss -?\d+ \(lqi 3\)}
 expect -re {Default rss: -?\d+ \(lqi 2\)}
-expect "Done"
+expect_line "Done"
 
 send "macfilter\n"
 expect "Address Mode: Disabled"
 expect "RssIn List:"
 expect -re {aabbccddeeff0011 : rss -?\d+ \(lqi 3\)}
 expect -re {Default rss : -?\d+ \(lqi 2\)}
-expect "Done"
+expect_line "Done"
 
 send "macfilter rss remove *\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter rss remove aabbccddeeff0011\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter rss add 2233445566778899 -70\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter rss add * -80\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter rss\n"
 expect -re {2233445566778899 : rss -70 \(lqi \d\)}
 expect -re {Default rss: -80 \(lqi \d\)}
-expect "Done"
+expect_line "Done"
 
 send "macfilter rss clear\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter rss something_invalid\n"
 expect "Error 35: InvalidCommand"
diff --git a/tests/scripts/expect/cli-misc.exp b/tests/scripts/expect/cli-misc.exp
index 65992bb..98651e8 100755
--- a/tests/scripts/expect/cli-misc.exp
+++ b/tests/scripts/expect/cli-misc.exp
@@ -40,129 +40,129 @@
 expect -re {Data Version: \d+}
 expect -re {Stable Data Version: \d+}
 expect -re {Leader Router ID: \d+}
-expect "Done"
+expect_line "Done"
 
 send "help\n"
-expect "Done"
+expect_line "Done"
 
 send "bufferinfo\n"
-expect "Done"
+expect_line "Done"
 
 send "ccathreshold -62\n"
-expect "Done"
+expect_line "Done"
 send "ccathreshold\n"
 expect -- "-62 dBm"
-expect "Done"
+expect_line "Done"
 
 send "parent\n"
-expect "Done"
+expect_line "Done"
 
 send "delaytimermin 1\n"
-expect "Done"
+expect_line "Done"
 send "delaytimermin\n"
 expect "1"
-expect "Done"
+expect_line "Done"
 send "delaytimermin 1 2\n"
 send "counters mac 1\n"
 expect "Error 7: InvalidArgs"
 
 send "fem lnagain 11\n"
-expect "Done"
+expect_line "Done"
 send "fem lnagain\n"
 expect -- "11"
-expect "Done"
+expect_line "Done"
 send "fem\n"
 expect -- "LNA gain 11 dBm"
-expect "Done"
+expect_line "Done"
 
 send "ifconfig down\n"
-expect "Done"
+expect_line "Done"
 send "ifconfig\n"
 expect "down"
-expect "Done"
+expect_line "Done"
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 send "ifconfig\n"
 expect "up"
-expect "Done"
+expect_line "Done"
 
 send "ipaddr add ::\n"
-expect "Done"
+expect_line "Done"
 send "ipaddr del ::\n"
-expect "Done"
+expect_line "Done"
 
 send "leaderweight 1\n"
-expect "Done"
+expect_line "Done"
 send "leaderweight\n"
 expect "1"
-expect "Done"
+expect_line "Done"
 
 send "mode rdn\n"
-expect "Done"
+expect_line "Done"
 send "mode\n"
 expect -re "(?=.*r)(?=.*d)(?=.*n)"
 
 send "parent\n"
-expect "Done"
+expect_line "Done"
 
 send "singleton\n"
 expect -re "true|false"
-expect "Done"
+expect_line "Done"
 
 send "state\n"
 expect "disabled"
-expect "Done"
+expect_line "Done"
 
 send "txpower -10\n"
-expect "Done"
+expect_line "Done"
 send "txpower\n"
 expect -- "-10 dBm"
-expect "Done"
+expect_line "Done"
 
 send "thread version\n"
-expect "Done"
+expect_line "Done"
 
 send "version\n"
-expect "Done"
+expect_line "Done"
 send "version api\n"
 expect -re {\d+}
-expect "Done"
+expect_line "Done"
 send "version something_invalid\n"
 expect "Error 35: InvalidCommand"
 
 send "joinerport 10001\n"
-expect "Done"
+expect_line "Done"
 send "joinerport\n"
 expect "10001"
-expect "Done"
+expect_line "Done"
 
 send "parentpriority 1\n"
-expect "Done"
+expect_line "Done"
 send "parentpriority\n"
 expect "1"
-expect "Done"
+expect_line "Done"
 
 send "pollperiod 100000\n"
-expect "Done"
+expect_line "Done"
 send "pollperiod\n"
 expect "100000"
-expect "Done"
+expect_line "Done"
 
 send "prefix add ::/64 low pdcrosn\n"
-expect "Done"
+expect_line "Done"
 send "prefix\n"
 expect "0:0:0:0::/64 pdcrosn low"
-expect "Done"
+expect_line "Done"
 
 send "preferrouterid 1\n"
-expect "Done"
+expect_line "Done"
 
 send "route add ::/64 s low\n"
-expect "Done"
+expect_line "Done"
 send "route\n"
 expect "0:0:0:0::/64 s low"
 send "route remove ::/64\n"
-expect "Done"
+expect_line "Done"
 
 send "diag start\n"
 expect ": InvalidState"
diff --git a/tests/scripts/expect/cli-multicast-loop.exp b/tests/scripts/expect/cli-multicast-loop.exp
index 48b91f3..ef6c8b6 100755
--- a/tests/scripts/expect/cli-multicast-loop.exp
+++ b/tests/scripts/expect/cli-multicast-loop.exp
@@ -37,61 +37,64 @@
 source "tests/scripts/expect/_common.exp"
 
 spawn_node 1
+setup_default_network
 
 set timeout 1
 
 send "panid 0xface\n"
-expect "Done"
+expect_line "Done"
 
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 
 wait_for "state" "leader"
-expect "Done"
+expect_line "Done"
 
 set extaddr1 [get_extaddr]
 
 get_rloc16
 
 spawn_node 2 cli
+setup_default_network
 
 send "panid 0xface\n"
-expect "Done"
+expect_line "Done"
 
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 
 wait_for "state" "router"
-expect "Done"
+expect_line "Done"
 sleep 3
 
 get_rloc16
 
 spawn_node 3 cli
+setup_default_network
 
 send "panid 0xface\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr add ${extaddr1}\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr denylist\n"
-expect "Done"
+expect_line "Done"
 
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 
 wait_for "state" "router"
-expect "Done"
+expect_line "Done"
 sleep 4
 
 set extaddr3 [get_extaddr]
@@ -99,27 +102,28 @@
 get_rloc16
 
 spawn_node 4 cli
+setup_default_network
 
 send "panid 0xface\n"
-expect "Done"
+expect_line "Done"
 
 send "mode rn\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr add ${extaddr3}\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr allowlist\n"
-expect "Done"
+expect_line "Done"
 
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 
 wait_for "state" "child"
-expect "Done"
+expect_line "Done"
 
 get_rloc16
 
@@ -129,7 +133,7 @@
 
 send "networkdiagnostic get ff03::2 5 16\n"
 expect "DIAG_GET.rsp/ans from $rloc(1)"
-expect "Done"
+expect_line "Done"
 
 sleep 5
 
diff --git a/tests/scripts/expect/cli-neighbor.exp b/tests/scripts/expect/cli-neighbor.exp
index 4af05a2..9f5dd9e 100755
--- a/tests/scripts/expect/cli-neighbor.exp
+++ b/tests/scripts/expect/cli-neighbor.exp
@@ -33,26 +33,18 @@
 setup_two_nodes
 
 switch_node 2
-send "rloc16\n"
-expect "rloc16"
-expect -re {([0-9a-f]{4})}
-set rloc $expect_out(1,string)
-expect "Done"
-send "extaddr\n"
-expect "extaddr"
-expect -re {([0-9a-f]{16})}
-set extaddr $expect_out(1,string)
-expect "Done"
+set rloc [get_rloc16]
+set extaddr [get_extaddr]
 
 switch_node 1
 send "neighbor table\n"
 expect "| Role | RLOC16 | Age | Avg RSSI | Last RSSI |R|D|N| Extended MAC     |"
 expect "+------+--------+-----+----------+-----------+-+-+-+------------------+"
 expect -re "\\| +C +\\| 0x$rloc \\| +\\d+ \\| +-?\\d+ \\| +-?\\d+ \\|1\\|0\\|0\\| $extaddr \\|"
-expect "Done"
+expect_line "Done"
 send "neighbor list\n"
 expect "0x$rloc"
-expect "Done"
+expect_line "Done"
 send "neighbor something_invalid\n"
 expect "Error 7: InvalidArgs"
 
diff --git a/tests/scripts/expect/cli-netdata.exp b/tests/scripts/expect/cli-netdata.exp
index faf6537..ff2d62c 100755
--- a/tests/scripts/expect/cli-netdata.exp
+++ b/tests/scripts/expect/cli-netdata.exp
@@ -35,15 +35,15 @@
 switch_node 1
 
 send "netdata help\n"
-expect "Done"
+expect_line "Done"
 
 send "netdata register\n"
-expect "Done"
+expect_line "Done"
 
 send "netdata show\n"
-expect "Done"
+expect_line "Done"
 
 send "netdata show -x\n"
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/expect/cli-networkname.exp b/tests/scripts/expect/cli-networkname.exp
index 2b0ce3e..20c09c6 100755
--- a/tests/scripts/expect/cli-networkname.exp
+++ b/tests/scripts/expect/cli-networkname.exp
@@ -34,47 +34,47 @@
 
 switch_node 1
 send "networkname Thread\\ Network\n"
-expect "Done"
+expect_line "Done"
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 wait_for "state" "leader"
-expect "Done"
+expect_line "Done"
 
 switch_node 2
 send "scan\n"
 expect "Thread Network"
-expect "Done"
+expect_line "Done"
 
 switch_node 1
 send "thread stop\n"
-expect "Done"
+expect_line "Done"
 send "networkname Thread\\ 网络\n"
-expect "Done"
+expect_line "Done"
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 wait_for "state" "leader"
-expect "Done"
+expect_line "Done"
 
 switch_node 2
 send "scan\n"
 expect "Thread 网络"
-expect "Done"
+expect_line "Done"
 
 switch_node 1
 send "thread stop\n"
-expect "Done"
+expect_line "Done"
 send "networkname スレッド\n"
-expect "Done"
+expect_line "Done"
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 wait_for "state" "leader"
-expect "Done"
+expect_line "Done"
 
 switch_node 2
 send "scan\n"
 expect "スレッド"
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/expect/cli-partitionid.exp b/tests/scripts/expect/cli-partitionid.exp
old mode 100644
new mode 100755
index e2e0b60..18a5efa
--- a/tests/scripts/expect/cli-partitionid.exp
+++ b/tests/scripts/expect/cli-partitionid.exp
@@ -32,14 +32,14 @@
 spawn_node 1
 
 send "partitionid preferred 12345678\n"
-expect "Done"
+expect_line "Done"
 send "partitionid preferred\n"
 expect "12345678"
-expect "Done"
+expect_line "Done"
 send "partitionid invalid\n"
 expect "Error 35: InvalidCommand"
 send "partitionid\n"
 expect "0"
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/expect/cli-ping.exp b/tests/scripts/expect/cli-ping.exp
index 1e615ff..db24d47 100755
--- a/tests/scripts/expect/cli-ping.exp
+++ b/tests/scripts/expect/cli-ping.exp
@@ -34,13 +34,13 @@
 spawn_node 1
 
 send "ping ::1 1 2 1 1\n"
-expect "Done"
+expect_line "Done"
 send "ping stop\n"
-expect "Done"
+expect_line "Done"
 send "ping ::1 1 2 0.12345 1\n"
-expect "Done"
+expect_line "Done"
 send "ping stop\n"
-expect "Done"
+expect_line "Done"
 send "ping ::1 1 2 1 1 1\n"
 expect "Error 7: InvalidArgs"
 
@@ -50,11 +50,7 @@
 setup_two_nodes
 
 switch_node 2
-send "ipaddr mleid\n"
-expect "ipaddr mleid"
-expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-set addr $expect_out(1,string)
-expect "Done"
+set addr [get_ipaddr mleid]
 
 switch_node 1
 send "ping $addr\n"
diff --git a/tests/scripts/expect/cli-promiscuous.exp b/tests/scripts/expect/cli-promiscuous.exp
index c2fcf78..61acf49 100755
--- a/tests/scripts/expect/cli-promiscuous.exp
+++ b/tests/scripts/expect/cli-promiscuous.exp
@@ -35,14 +35,14 @@
 
 send "promiscuous\n"
 expect "Disabled"
-expect "Done"
+expect_line "Done"
 send "promiscuous enable\n"
-expect "Done"
+expect_line "Done"
 send "promiscuous\n"
 expect "Enabled"
-expect "Done"
+expect_line "Done"
 send "promiscuous disable\n"
-expect "Done"
+expect_line "Done"
 send "promiscuous a\n"
 expect "Error 7: InvalidArgs"
 
@@ -56,19 +56,19 @@
 expect "channel"
 expect -re {(\d+)}
 set channel $expect_out(1,string)
-expect "Done"
+expect_line "Done"
 
 spawn_node 3
 send "channel $channel\n"
-expect "Done"
+expect_line "Done"
 send "promiscuous enable\n"
-expect "Done"
+expect_line "Done"
 
 switch_node 2
 send "ipaddr\n"
 expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
 set addr $expect_out(1,string)
-expect "Done"
+expect_line "Done"
 
 switch_node 1
 send "ping $addr\n"
@@ -79,6 +79,6 @@
 expect -re {\|( ([0-9A-Z]{2}|\.\.)){16}\|( .){16}\|}
 expect -- "-----------------------------------------------------------------------------------"
 send "promiscuous disable\n"
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/expect/cli-pskc.exp b/tests/scripts/expect/cli-pskc.exp
index 1d211fd..f4de844 100755
--- a/tests/scripts/expect/cli-pskc.exp
+++ b/tests/scripts/expect/cli-pskc.exp
@@ -32,21 +32,21 @@
 spawn_node 1
 
 send "pskc 00112233445566778899aabbccddeeff\n"
-expect "Done"
+expect_line "Done"
 send "pskc\n"
 expect "00112233445566778899aabbccddeeff"
-expect "Done"
+expect_line "Done"
 send "factoryreset\n"
 sleep 0.1
 send "networkname Test\\ Network\n"
-expect "Done"
+expect_line "Done"
 send "extpanid 0001020304050607\n"
-expect "Done"
+expect_line "Done"
 send "pskc -p 12SECRETPASSWORD34\n"
-expect "Done"
+expect_line "Done"
 send "pskc\n"
 expect "c3f59368445a1b6106be420a706d4cc9"
-expect "Done"
+expect_line "Done"
 send "factoryreset\n"
 sleep 0.1
 send "pskc -x\n"
diff --git a/tests/scripts/expect/cli-anycast.exp b/tests/scripts/expect/cli-region.exp
old mode 100644
new mode 100755
similarity index 80%
rename from tests/scripts/expect/cli-anycast.exp
rename to tests/scripts/expect/cli-region.exp
index 3081a6b..3c75c40
--- a/tests/scripts/expect/cli-anycast.exp
+++ b/tests/scripts/expect/cli-region.exp
@@ -29,28 +29,12 @@
 
 source "tests/scripts/expect/_common.exp"
 
+spawn_node 1
 
-set spawn_id [spawn_node 1]
-
-send "panid 0xface\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-
-wait_for "state" "leader"
-expect "Done"
-
-send "ping fdde:ad00:beef:0:0:ff:fe00:fc00\n"
-expect "Done"
-expect "16 bytes from "
-expect {
-    "fdde:ad00:beef:0:0:ff:fe00:fc00" abort
-
-    -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})} {}
-
-    timeout abort
-}
-
-dispose
+send "region\n"
+expect_line "Done"
+send "region US\n"
+expect_line "Done"
+send "region\n"
+expect "US"
+expect_line "Done"
diff --git a/tests/scripts/expect/cli-reset.exp b/tests/scripts/expect/cli-reset.exp
index d432d33..dc29441 100755
--- a/tests/scripts/expect/cli-reset.exp
+++ b/tests/scripts/expect/cli-reset.exp
@@ -32,11 +32,11 @@
 spawn_node 1
 
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 send "panid 0xabcd\n"
-expect "Done"
+expect_line "Done"
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 
 sleep 3
 
@@ -45,24 +45,24 @@
 
 send "ifconfig\n"
 expect "down"
-expect "Done"
+expect_line "Done"
 send "panid\n"
 expect "0xabcd"
-expect "Done"
+expect_line "Done"
 
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 
 send "factoryreset\n"
 sleep 3
 
 send "ifconfig\n"
 expect "down"
-expect "Done"
+expect_line "Done"
 send "panid\n"
 expect "0xffff"
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/expect/cli-router.exp b/tests/scripts/expect/cli-router.exp
index 028476d..b7f1a56 100755
--- a/tests/scripts/expect/cli-router.exp
+++ b/tests/scripts/expect/cli-router.exp
@@ -33,37 +33,33 @@
 setup_two_nodes
 
 switch_node 1
-send "extaddr\n"
-expect "extaddr"
-expect -re {([0-9a-f]{16})}
-set extaddr $expect_out(1,string)
-expect "Done"
+set extaddr [get_extaddr]
 send "router list\n"
 expect "router list"
 expect -re {(\d+)}
 set router_id $expect_out(1,string)
-expect "Done"
+expect_line "Done"
 send "router $router_id\n"
 expect -re {Rloc: ([0-9a-f]{4})}
 set rloc $expect_out(1,string)
-expect "Done"
+expect_line "Done"
 send "router table\n"
 expect "| ID | RLOC16 | Next Hop | Path Cost | LQ In | LQ Out | Age | Extended MAC     |"
 expect "+----+--------+----------+-----------+-------+--------+-----+------------------+"
 expect -re "\\| +$router_id \\| 0x$rloc \\| +\\d+ \\| +\\d+ \\| +\\d+ \\| +\\d+ \\| +\\d+ \\| $extaddr \\|"
-expect "Done"
+expect_line "Done"
 
 switch_node 2
 send "mode rdn\n"
-expect "Done"
+expect_line "Done"
 send "state router\n"
-expect "Done"
+expect_line "Done"
 wait_for "router list" $router_id
 send "router $router_id\n"
 expect "Router ID: $router_id"
 expect "Rloc: $rloc"
 expect -re {Next Hop: [0-9a-f]{4}}
 expect -re {Link: [01]}
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/expect/cli-routereligible.exp b/tests/scripts/expect/cli-routereligible.exp
index f18a3d1..821b127 100755
--- a/tests/scripts/expect/cli-routereligible.exp
+++ b/tests/scripts/expect/cli-routereligible.exp
@@ -32,22 +32,22 @@
 spawn_node 1
 
 send "routereligible disable\n"
-expect "Done"
+expect_line "Done"
 send "routereligible\n"
 expect "Disabled"
-expect "Done"
+expect_line "Done"
 send "routereligible enable\n"
-expect "Done"
+expect_line "Done"
 send "routereligible\n"
 expect "Enabled"
-expect "Done"
+expect_line "Done"
 send "routereligible something_invalid\n"
 expect "Error 7: InvalidArgs"
 send "mode r\n"
-expect "Done"
+expect_line "Done"
 send "routereligible\n"
 expect "Disabled"
-expect "Done"
+expect_line "Done"
 send "routereligible enable\n"
 expect "Error 27: NotCapable"
 
diff --git a/tests/scripts/expect/cli-scan-discover.exp b/tests/scripts/expect/cli-scan-discover.exp
index fd9061e..812b882 100755
--- a/tests/scripts/expect/cli-scan-discover.exp
+++ b/tests/scripts/expect/cli-scan-discover.exp
@@ -35,32 +35,28 @@
 spawn_node 3
 
 switch_node 1
-send "extaddr\n"
-expect "extaddr"
-expect -re {([0-9a-f]{16})}
-set extaddr $expect_out(1,string)
-expect "Done"
+set extaddr [get_extaddr]
 send "panid\n"
 expect "panid"
 expect -re {([0-9a-f]{4})}
 set pan $expect_out(1,string)
-expect "Done"
+expect_line "Done"
 send "extpanid\n"
 expect "extpanid"
 expect -re {([0-9a-f]{16})}
 set extpan $expect_out(1,string)
-expect "Done"
+expect_line "Done"
 expect "> "
 send "networkname\n"
 expect "networkname"
 expect -re {[\r\n]([^\r\n]+?)[\r\n]}
 set network $expect_out(1,string)
-expect "Done"
+expect_line "Done"
 send "channel\n"
 expect "channel"
 expect -re {(\d+)}
 set channel $expect_out(1,string)
-expect "Done"
+expect_line "Done"
 
 switch_node 3
 send "scan $channel\n"
@@ -74,16 +70,16 @@
 for {set i 11} {$i <= 26} {incr i} {
     expect -re "\\| +$i \\| +-?\\d+ \\|"
 }
-expect "Done"
+expect_line "Done"
 send "scan energy 100 $channel\n"
 expect "| Ch | RSSI |"
 expect "+----+------+"
 expect -re "\\| +$channel \\| +-?\\d+ \\|"
-expect "Done"
+expect_line "Done"
 
 switch_node 3
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 send "discover $channel\n"
 expect "| J | Network Name     | Extended PAN     | PAN  | MAC Address      | Ch | dBm | LQI |"
 expect "+---+------------------+------------------+------+------------------+----+-----+-----+"
diff --git a/tests/scripts/expect/cli-udp.exp b/tests/scripts/expect/cli-udp.exp
index e55b458..2a78f7e 100755
--- a/tests/scripts/expect/cli-udp.exp
+++ b/tests/scripts/expect/cli-udp.exp
@@ -34,31 +34,25 @@
 
 switch_node 2
 send "udp open\n"
-expect "Done"
+expect_line "Done"
 send "udp bind :: 11001\n"
-expect "Done"
-send "ipaddr mleid\n"
-expect "ipaddr mleid"
-expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-set addr_2 $expect_out(1,string)
+expect_line "Done"
+set addr_2 [get_ipaddr mleid]
 
 switch_node 1
-send "ipaddr mleid\n"
-expect "ipaddr mleid"
-expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-set addr_1 $expect_out(1,string)
+set addr_1 [get_ipaddr mleid]
 send "udp open\n"
-expect "Done"
+expect_line "Done"
 send "udp bind :: 11002\n"
-expect "Done"
+expect_line "Done"
 send "udp connect $addr_2 11001\n"
-expect "Done"
+expect_line "Done"
 send "udp send -s 10\n"
-expect "Done"
+expect_line "Done"
 send "udp send -x a68656c6c6f\n"
-expect "Done"
+expect_line "Done"
 send "udp send -t there\n"
-expect "Done"
+expect_line "Done"
 
 switch_node 2
 expect "10 bytes from $addr_1 11002 0123456789"
@@ -68,7 +62,7 @@
 
 switch_node 1
 send "udp help\n"
-expect "Done"
+expect_line "Done"
 send "udp connect something_invalid\n"
 expect "Error 7: InvalidArgs"
 send "udp send -x something_invalid\n"
@@ -78,17 +72,17 @@
 
 send "udp linksecurity\n"
 expect "Enabled"
-expect "Done"
+expect_line "Done"
 send "udp linksecurity disable\n"
-expect "Done"
+expect_line "Done"
 send "udp linksecurity\n"
 expect "Disabled"
-expect "Done"
+expect_line "Done"
 send "udp linksecurity enable\n"
-expect "Done"
+expect_line "Done"
 send "udp linksecurity\n"
 expect "Enabled"
-expect "Done"
+expect_line "Done"
 
 send "udp linksecurity something_invalid\n"
 expect "Error 35: InvalidCommand"
diff --git a/tests/scripts/expect/cli-unsecure-port.exp b/tests/scripts/expect/cli-unsecure-port.exp
index bf99d97..51c36d4 100755
--- a/tests/scripts/expect/cli-unsecure-port.exp
+++ b/tests/scripts/expect/cli-unsecure-port.exp
@@ -32,33 +32,33 @@
 spawn_node 1
 
 send "unsecureport remove all\n"
-expect "Done"
+expect_line "Done"
 
 send "unsecureport get\n"
-expect "Done"
+expect_line "Done"
 
 send "unsecureport add 123\n"
-expect "Done"
+expect_line "Done"
 
 send "unsecureport add 456\n"
-expect "Done"
+expect_line "Done"
 
 send "unsecureport get\n"
 expect "123 456"
-expect "Done"
+expect_line "Done"
 
 send "unsecureport remove 123\n"
-expect "Done"
+expect_line "Done"
 
 send "unsecureport get\n"
 expect "456"
-expect "Done"
+expect_line "Done"
 
 send "unsecureport remove all\n"
-expect "Done"
+expect_line "Done"
 
 send "unsecureport get\n"
-expect "Done"
+expect_line "Done"
 
 send "unsecureport invalid_command\n"
 expect "Error 35: InvalidCommand"
diff --git a/tests/scripts/expect/posix-ccathreshold.exp b/tests/scripts/expect/posix-ccathreshold.exp
index 34e4d96..0ea5d7c 100755
--- a/tests/scripts/expect/posix-ccathreshold.exp
+++ b/tests/scripts/expect/posix-ccathreshold.exp
@@ -27,13 +27,15 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
+source "tests/scripts/expect/_common.exp"
+
 spawn $env(OT_POSIX_APPS)/ot-cli "spinel+hdlc+uart://$env(OT_SIMULATION_APPS)/ncp/ot-rcp?forkpty-arg=1&cca-threshold=-100"
 expect_after {
     timeout { exit 1 }
 }
 send "ccathreshold\n"
 expect -- "-100"
-expect "Done"
+expect_line "Done"
 send "\x04"
 expect eof
 
@@ -43,7 +45,7 @@
 }
 send "ccathreshold\n"
 expect -- "-128"
-expect "Done"
+expect_line "Done"
 send "\x04"
 expect eof
 
@@ -53,7 +55,7 @@
 }
 send "ccathreshold\n"
 expect "127"
-expect "Done"
+expect_line "Done"
 send "\x04"
 expect eof
 
diff --git a/tests/scripts/expect/posix-diag-rcp.exp b/tests/scripts/expect/posix-diag-rcp.exp
index e933922..2e4732e 100755
--- a/tests/scripts/expect/posix-diag-rcp.exp
+++ b/tests/scripts/expect/posix-diag-rcp.exp
@@ -33,25 +33,25 @@
 
 send "diag rcp\n"
 expect "diagnostics mode is disabled"
-expect "Done"
+expect_line "Done"
 send "diag start\n"
 expect "start diagnostics mode"
 expect "status 0x00"
-expect "Done"
+expect_line "Done"
 send "diag rcp\n"
 expect "diagnostics mode is enabled"
-expect "Done"
+expect_line "Done"
 send "diag rcp channel\n"
 expect "failed"
 expect "status 0x7"
-expect "Done"
+expect_line "Done"
 send "diag rcp channel 11\n"
-expect "Done"
+expect_line "Done"
 send "diag rcp power\n"
 expect "failed"
 expect "status 0x7"
-expect "Done"
+expect_line "Done"
 send "diag rcp power 10\n"
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/expect/posix-fem-lnagain.exp b/tests/scripts/expect/posix-fem-lnagain.exp
index 77adfaa..f157c24 100755
--- a/tests/scripts/expect/posix-fem-lnagain.exp
+++ b/tests/scripts/expect/posix-fem-lnagain.exp
@@ -27,13 +27,15 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
+source "tests/scripts/expect/_common.exp"
+
 spawn $env(OT_POSIX_APPS)/ot-cli "spinel+hdlc+uart://$env(OT_SIMULATION_APPS)/ncp/ot-rcp?forkpty-arg=1&fem-lnagain=10"
 expect_after {
     timeout { exit 1 }
 }
 send "fem lnagain\n"
 expect "10"
-expect "Done"
+expect_line "Done"
 send "\x04"
 expect eof
 
@@ -43,7 +45,7 @@
 }
 send "fem lnagain\n"
 expect -- "-128"
-expect "Done"
+expect_line "Done"
 send "\x04"
 expect eof
 
diff --git a/tests/scripts/expect/posix-max-power-table.exp b/tests/scripts/expect/posix-max-power-table.exp
index 733edfb..86d3a0f 100755
--- a/tests/scripts/expect/posix-max-power-table.exp
+++ b/tests/scripts/expect/posix-max-power-table.exp
@@ -33,24 +33,24 @@
 spawn_node 1 "rcp" "spinel+hdlc+uart://$env(OT_SIMULATION_APPS)/ncp/ot-rcp?max-power-table=11,12,13,14,15,16,17,18,19,20,21,22,23,24,-1,0x7f&forkpty-arg=1"
 send "channel supported\n"
 expect "0x3fff800"
-expect "Done"
+expect_line "Done"
 send "channel preferred\n"
 expect "0x3fff800"
-expect "Done"
+expect_line "Done"
 send "txpower 20\n"
-expect "Done"
+expect_line "Done"
 send "txpower\n"
 expect "11 dBm"
-expect "Done"
+expect_line "Done"
 send "\x04"
 expect eof
 # allows all channels by default
 spawn_node 1 "rcp" "spinel+hdlc+uart://$env(OT_SIMULATION_APPS)/ncp/ot-rcp?forkpty-arg=1"
 send "channel supported\n"
 expect "0x7fff800"
-expect "Done"
+expect_line "Done"
 send "channel preferred\n"
 expect "0x7fff800"
-expect "Done"
+expect_line "Done"
 send "\x04"
 expect eof
diff --git a/tests/scripts/expect/posix-rcp-restoration.exp b/tests/scripts/expect/posix-rcp-restoration.exp
index 2222432..c0dac4c 100755
--- a/tests/scripts/expect/posix-rcp-restoration.exp
+++ b/tests/scripts/expect/posix-rcp-restoration.exp
@@ -31,6 +31,9 @@
 source "tests/scripts/expect/_multinode.exp"
 
 
+# The expect on macOS doesn't support `try` or `file tempfile`.
+skip_on_macos
+
 file tempfile socat_out
 set socat_pid [exec socat -d -d pty,raw,echo=0 pty,raw,echo=0 >/dev/null 2>$socat_out &]
 while {true} {
@@ -70,36 +73,28 @@
     setup_two_nodes "-" false
 
     switch_node 2
-    send "ipaddr mleid\n"
-    expect "ipaddr mleid"
-    expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-    set addr_2 $expect_out(1,string)
-    expect "Done"
+    set addr_2 [get_ipaddr mleid]
     send "udp open\n"
-    expect "Done"
+    expect_line "Done"
     send "udp bind :: 11003\n"
-    expect "Done"
+    expect_line "Done"
     send "pollperiod 100000\n"
-    expect "Done"
+    expect_line "Done"
 
     sleep 1
 
     switch_node 1
-    send "ipaddr mleid\n"
-    expect "ipaddr mleid"
-    expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-    set addr_1 $expect_out(1,string)
-    expect "Done"
+    set addr_1 [get_ipaddr mleid]
     send "udp open\n"
-    expect "Done"
+    expect_line "Done"
     send "udp bind :: 11004\n"
-    expect "Done"
+    expect_line "Done"
     send "udp connect $addr_2 11003\n"
-    expect "Done"
+    expect_line "Done"
     send "udp send hello\n"
-    expect "Done"
+    expect_line "Done"
     send "udp send there\n"
-    expect "Done"
+    expect_line "Done"
 
     sleep 1
 
@@ -113,7 +108,7 @@
 
     switch_node 2
     send "pollperiod 1000\n"
-    expect "Done"
+    expect_line "Done"
     expect "5 bytes from $addr_1 11004 hello"
     expect "5 bytes from $addr_1 11004 there"
 
@@ -134,34 +129,26 @@
     setup_two_nodes "-" false
 
     switch_node 2
-    send "ipaddr mleid\n"
-    expect "ipaddr mleid"
-    expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-    set addr_2 $expect_out(1,string)
-    expect "Done"
+    set addr_2 [get_ipaddr mleid]
     send "udp open\n"
-    expect "Done"
+    expect_line "Done"
     send "udp bind :: 11003\n"
-    expect "Done"
+    expect_line "Done"
     send "pollperiod 100000\n"
-    expect "Done"
+    expect_line "Done"
 
     sleep 1
 
     switch_node 1
-    send "ipaddr mleid\n"
-    expect "ipaddr mleid"
-    expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-    set addr_1 $expect_out(1,string)
-    expect "Done"
+    set addr_1 [get_ipaddr mleid]
     send "udp open\n"
-    expect "Done"
+    expect_line "Done"
     send "udp bind :: 11004\n"
-    expect "Done"
+    expect_line "Done"
     send "udp connect $addr_2 11003\n"
-    expect "Done"
+    expect_line "Done"
     send "udp send hello\n"
-    expect "Done"
+    expect_line "Done"
 
     sleep 1
 
@@ -175,7 +162,7 @@
 
     switch_node 2
     send "pollperiod 1000\n"
-    expect "Done"
+    expect_line "Done"
     expect "5 bytes from $addr_1 11004 hello"
 
     dispose_all
@@ -190,47 +177,25 @@
     puts "Many children, queued child-to-child packets"
 
     spawn_node 1 "rcp" "spinel+hdlc_uart://$host_pty"
-    send "panid 0xface\n"
-    expect "Done"
-    send "ifconfig up\n"
-    expect "Done"
-    send "thread start\n"
-    expect "Done"
-    wait_for "state" "leader"
-    expect "Done"
-    send "ipaddr mleid\n"
-    expect "ipaddr mleid"
-    expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-    set addr(1) $expect_out(1,string)
-    expect "Done"
+    setup_leader
+    set addr(1) [get_ipaddr mleid]
     send "udp open\n"
-    expect "Done"
+    expect_line "Done"
     send "udp bind :: 11004\n"
-    expect "Done"
+    expect_line "Done"
 
-    set max_children 15
+    set max_children 10
     for {set i 2} {$i <= $max_children + 1} {incr i} {
         spawn_node $i
-        send "mode -\n"
-        expect "Done"
-        send "panid 0xface\n"
-        expect "Done"
-        send "ifconfig up\n"
-        expect "Done"
-        send "thread start\n"
-        expect "Done"
-        wait_for "state" "child"
-        send "ipaddr mleid\n"
-        expect "ipaddr mleid"
-        expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-        set addr($i) $expect_out(1,string)
-        expect "Done"
+        setup_node $i "-"
+        switch_node $i
+        set addr($i) [get_ipaddr mleid]
         send "udp open\n"
-        expect "Done"
+        expect_line "Done"
         send "udp bind :: 11003\n"
-        expect "Done"
+        expect_line "Done"
         send "pollperiod 100000\n"
-        expect "Done"
+        expect_line "Done"
     }
 
     sleep 1
@@ -238,7 +203,7 @@
     switch_node 1
     for {set i 2} {$i <= $max_children + 1} {incr i} {
         send "udp send $addr($i) 11003 hello\n"
-        expect "Done"
+        expect_line "Done"
     }
 
     sleep 1
@@ -254,13 +219,13 @@
     for {set i 7} {$i <= 9} {incr i} {
         switch_node $i
         send "pollperiod 1000\n"
-        expect "Done"
+        expect_line "Done"
         expect "5 bytes from $addr(1) 11004 hello"
     }
 
     switch_node 4
     send "udp send $addr(5) 11003 hello_from_node_4\n"
-    expect "Done"
+    expect_line "Done"
     if {$::env(THREAD_VERSION) != "1.1"} {
         expect "5 bytes from $addr(1) 11004 hello"
     }
@@ -277,7 +242,7 @@
 
     switch_node 5
     send "pollperiod 1000\n"
-    expect "Done"
+    expect_line "Done"
     expect "17 bytes from $addr(4) 11003 hello_from_node_4"
 
     dispose_all
@@ -287,11 +252,11 @@
 
     spawn_node 1 "rcp" "spinel+hdlc_uart://$host_pty"
     send "ifconfig up\n"
-    expect "Done"
+    expect_line "Done"
     send "thread start\n"
-    expect "Done"
+    expect_line "Done"
     wait_for "state" "leader"
-    expect "Done"
+    expect_line "Done"
 
     send "scan energy 100\n"
     expect "| Ch | RSSI |"
@@ -310,7 +275,7 @@
     for {set i 11} {$i <= 26} {incr i} {
         expect -re "\\| +$i \\| +-?\\d+ \\|"
     }
-    expect "Done"
+    expect_line "Done"
 
     dispose_all
 } finally {
diff --git a/tests/scripts/expect/posix-rcp.exp b/tests/scripts/expect/posix-rcp.exp
index c6dd51d..d2ccd4d 100755
--- a/tests/scripts/expect/posix-rcp.exp
+++ b/tests/scripts/expect/posix-rcp.exp
@@ -32,7 +32,7 @@
 spawn_node 1
 
 send "rcp version\n"
-expect "Done"
+expect_line "Done"
 send "rcp something_invalid\n"
 expect "Error 7: InvalidArgs"
 
diff --git a/tests/scripts/expect/posix-scan-tx-to-sleep.exp b/tests/scripts/expect/posix-scan-tx-to-sleep.exp
index 7f29714..a613737 100755
--- a/tests/scripts/expect/posix-scan-tx-to-sleep.exp
+++ b/tests/scripts/expect/posix-scan-tx-to-sleep.exp
@@ -31,44 +31,40 @@
 
 spawn_node 1 "rcp" "spinel+hdlc+uart://$env(OT_SIMULATION_APPS)/ncp/ot-rcp?forkpty-arg=--sleep-to-tx&forkpty-arg=1"
 send "dataset init new\n"
-expect "Done"
+expect_line "Done"
 send "dataset panid 0xface\n"
-expect "Done"
+expect_line "Done"
 send "dataset commit active\n"
-expect "Done"
+expect_line "Done"
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 wait_for "state" "leader"
-expect "Done"
+expect_line "Done"
 
-send "extaddr\n"
-expect "extaddr"
-expect -re {([0-9a-f]{16})}
-set extaddr $expect_out(1,string)
-expect "Done"
+set extaddr [get_extaddr]
 send "panid\n"
 expect "panid"
 expect -re {([0-9a-f]{4})}
 set pan $expect_out(1,string)
-expect "Done"
+expect_line "Done"
 send "extpanid\n"
 expect "extpanid"
 expect -re {([0-9a-f]{16})}
 set extpan $expect_out(1,string)
-expect "Done"
+expect_line "Done"
 expect "> "
 send "networkname\n"
 expect "networkname"
 expect -re {[\r\n]([^\r\n]+)[\r\n]}
 set network $expect_out(1,string)
-expect "Done"
+expect_line "Done"
 send "channel\n"
 expect "channel"
 expect -re {(\d+)}
 set channel $expect_out(1,string)
-expect "Done"
+expect_line "Done"
 
 spawn_node 2 "rcp" "spinel+hdlc+uart://$env(OT_SIMULATION_APPS)/ncp/ot-rcp?forkpty-arg=--sleep-to-tx&forkpty-arg=2"
 send "scan\n"
diff --git a/tests/scripts/expect/simulation-networktime.exp b/tests/scripts/expect/simulation-networktime.exp
index df98db2..ccbe3b5 100755
--- a/tests/scripts/expect/simulation-networktime.exp
+++ b/tests/scripts/expect/simulation-networktime.exp
@@ -32,12 +32,12 @@
 spawn_node 1
 
 send "networktime 20 200\n"
-expect "Done"
+expect_line "Done"
 send "networktime\n"
 expect -re {Network Time:     \d+us \((unsynchronized|resync needed|synchronized)\)}
 expect "Time Sync Period: 20s"
 expect "XTAL Threshold:   200ppm"
-expect "Done"
+expect_line "Done"
 send "networktime something_invalid\n"
 expect "Error 7: InvalidArgs"
 
diff --git a/tests/scripts/expect/tun-dns-client.exp b/tests/scripts/expect/tun-dns-client.exp
index ef9c504..36909ba 100755
--- a/tests/scripts/expect/tun-dns-client.exp
+++ b/tests/scripts/expect/tun-dns-client.exp
@@ -32,16 +32,16 @@
 spawn_node 1
 
 send "panid 0xface\n"
-expect "Done"
+expect_line "Done"
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 wait_for "state" "leader"
-expect "Done"
+expect_line "Done"
 
 send "dns resolve ipv6.google.com ::1 53\n"
 expect "DNS response for ipv6.google.com"
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/expect/tun-netif.exp b/tests/scripts/expect/tun-netif.exp
index d9635c7..ad6d956 100755
--- a/tests/scripts/expect/tun-netif.exp
+++ b/tests/scripts/expect/tun-netif.exp
@@ -33,6 +33,6 @@
 
 send "netif\n"
 expect -re {[\r\n][^\r\n:]+?:\d+}
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/expect/tun-netstat.exp b/tests/scripts/expect/tun-netstat.exp
index 1e5ed92..bd33388 100755
--- a/tests/scripts/expect/tun-netstat.exp
+++ b/tests/scripts/expect/tun-netstat.exp
@@ -30,33 +30,29 @@
 source "tests/scripts/expect/_common.exp"
 
 spawn_node 1
+setup_leader
 
-send "ifconfig up\n"
-expect "Done"
 send "udp open\n"
-expect "Done"
+expect_line "Done"
 send "netstat\n"
 expect "|                 Local Address                 |                  Peer Address                 |"
 expect "+-----------------------------------------------+-----------------------------------------------+"
 expect "| 0:0:0:0:0:0:0:0:*                             | 0:0:0:0:0:0:0:0:*                             |"
-expect "Done"
+expect_line "Done"
 send "udp bind :: 10001\n"
-expect "Done"
+expect_line "Done"
 send "netstat\n"
 expect "|                 Local Address                 |                  Peer Address                 |"
 expect "+-----------------------------------------------+-----------------------------------------------+"
 expect "| 0:0:0:0:0:0:0:0:10001                         | 0:0:0:0:0:0:0:0:*                             |"
-expect "Done"
-send "ipaddr mleid\n"
-expect "ipaddr mleid"
-expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-set addr $expect_out(1,string)
+expect_line "Done"
+set addr [get_ipaddr mleid]
 send "udp connect $addr 10001\n"
-expect "Done"
+expect_line "Done"
 send "netstat\n"
 expect "|                 Local Address                 |                  Peer Address                 |"
 expect "+-----------------------------------------------+-----------------------------------------------+"
 expect -re "\\| 0:0:0:0:0:0:0:0:10001 +\\| $addr:10001 +\\|"
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/expect/tun-realm-local-multicast.exp b/tests/scripts/expect/tun-realm-local-multicast.exp
index daba343..e8c0543 100755
--- a/tests/scripts/expect/tun-realm-local-multicast.exp
+++ b/tests/scripts/expect/tun-realm-local-multicast.exp
@@ -37,53 +37,45 @@
 source "tests/scripts/expect/_common.exp"
 
 spawn_node 1
-
-set timeout 1
-
-send "panid 0xface\n"
-expect "Done"
+setup_default_network
 
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 
 wait_for "state" "leader"
-expect "Done"
+expect_line "Done"
 
 set extaddr1 [get_extaddr]
 
 spawn_node 2 cli
-
-send "panid 0xface\n"
-expect "Done"
+setup_default_network
 
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 
 wait_for "state" "router"
 sleep 3
 
 spawn_node 3 cli
-
-send "panid 0xface\n"
-expect "Done"
+setup_default_network
 
 send "macfilter addr add ${extaddr1}\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr denylist\n"
-expect "Done"
+expect_line "Done"
 
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 
 wait_for "state" "router"
 sleep 4
@@ -91,24 +83,22 @@
 set extaddr3 [get_extaddr]
 
 spawn_node 4 cli
-
-send "panid 0xface\n"
-expect "Done"
+setup_default_network
 
 send "mode rn\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr add ${extaddr3}\n"
-expect "Done"
+expect_line "Done"
 
 send "macfilter addr allowlist\n"
-expect "Done"
+expect_line "Done"
 
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 
 wait_for "state" "child"
 
@@ -117,7 +107,7 @@
 switch_node 1
 
 send "ping ${mleid4}\n"
-expect "Done"
+expect_line "Done"
 expect "16 bytes from $mleid4: icmp_seq=1"
 
 dispose_all
diff --git a/tests/scripts/expect/tun-sntp.exp b/tests/scripts/expect/tun-sntp.exp
index aa920d7..a724390 100755
--- a/tests/scripts/expect/tun-sntp.exp
+++ b/tests/scripts/expect/tun-sntp.exp
@@ -27,27 +27,23 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
-set OSTYPE [lindex $tcl_platform(os) 0]
-
-if { $OSTYPE == "Darwin" } {
-    exit 77
-}
-
 source "tests/scripts/expect/_common.exp"
 
+skip_on_macos
+
 spawn_node 1
 
 send "panid 0xface\n"
-expect "Done"
+expect_line "Done"
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 wait_for "state" "leader"
-expect "Done"
+expect_line "Done"
 
 send "sntp query ::1 123\n"
 expect -re {SNTP response - Unix time: \d+ \(era: \d+\)}
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/expect/tun-udp.exp b/tests/scripts/expect/tun-udp.exp
index 2eb4f6e..187bda4 100755
--- a/tests/scripts/expect/tun-udp.exp
+++ b/tests/scripts/expect/tun-udp.exp
@@ -36,30 +36,35 @@
 set timeout 1
 
 send "panid 0xface\n"
-expect "Done"
+expect_line "Done"
 
 send "ifconfig up\n"
-expect "Done"
+expect_line "Done"
 
 send "thread start\n"
-expect "Done"
+expect_line "Done"
 
 wait_for "state" "leader"
-expect "Done"
+expect_line "Done"
 
 send "udp open\n"
-expect "Done"
+expect_line "Done"
 
 send "udp bind :: 1234\n"
-expect "Done"
+expect_line "Done"
 
-send "udp connect fdde:ad00:beef:0:bb1:ebd6:ad10:f33 1234\n"
-expect "Done"
+send "dataset active\n"
+expect -re {Mesh Local Prefix: ([0-9a-f:]*)::\/64}
+set prefix $expect_out(1,string)
+expect_line "Done"
+
+send "udp connect $prefix:bb1:ebd6:ad10:f33 1234\n"
+expect_line "Done"
 
 send "udp connect :: 1\n"
-expect "Done"
+expect_line "Done"
 
 send "udp close\n"
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/expect/v1_2-rcp-restoration.exp b/tests/scripts/expect/v1_2-rcp-restoration.exp
old mode 100644
new mode 100755
diff --git a/tests/scripts/expect/v1_2-sed_mac_send.exp b/tests/scripts/expect/v1_2-sed_mac_send.exp
index 4a2e15d..faeeb35 100755
--- a/tests/scripts/expect/v1_2-sed_mac_send.exp
+++ b/tests/scripts/expect/v1_2-sed_mac_send.exp
@@ -34,8 +34,8 @@
 
 switch_node 2
 send "mac send emptydata\n"
-expect "Done"
+expect_line "Done"
 send "mac send datarequest\n"
-expect "Done"
+expect_line "Done"
 
 dispose_all
diff --git a/tests/scripts/thread-cert/Cert_7_1_05_BorderRouterAsRouter.py b/tests/scripts/thread-cert/Cert_7_1_05_BorderRouterAsRouter.py
index eff1b0b..ba2f4f1 100755
--- a/tests/scripts/thread-cert/Cert_7_1_05_BorderRouterAsRouter.py
+++ b/tests/scripts/thread-cert/Cert_7_1_05_BorderRouterAsRouter.py
@@ -159,16 +159,17 @@
         # Step 5: The DUT MUST send a multicast MLE Data Response,
         #         including at least three Prefix TLVs (Prefix 1, Prefix2,
         #         and Prefix 3).
-        _dv_pkt = pkts.filter_wpan_src64(ROUTER).\
-            filter_LLANMA().\
-            filter_mle_cmd(MLE_DATA_RESPONSE).\
-            filter(lambda p: {
-                              Ipv6Addr(PREFIX_2001[:-3]),
-                              Ipv6Addr(PREFIX_2002[:-3]),
-                              Ipv6Addr(PREFIX_2003[:-3])
-                             } == set(p.thread_nwd.tlv.prefix)
-                   ).\
-            must_next()
+        with pkts.save_index():
+            _dv_pkt = pkts.filter_wpan_src64(ROUTER).\
+                filter_LLANMA().\
+                filter_mle_cmd(MLE_DATA_RESPONSE).\
+                filter(lambda p: {
+                                  Ipv6Addr(PREFIX_2001[:-3]),
+                                  Ipv6Addr(PREFIX_2002[:-3]),
+                                  Ipv6Addr(PREFIX_2003[:-3])
+                                 } <= set(p.thread_nwd.tlv.prefix)
+                       ).\
+                must_next()
 
         # Step 6: MED automatically sends MLE Child Update Request to its parent
         #         (DUT), reporting its configured global addresses in the Address
@@ -183,6 +184,7 @@
         _pkt = pkts.filter_wpan_src64(MED).\
             filter_wpan_dst64(ROUTER).\
             filter_mle_cmd(MLE_CHILD_UPDATE_REQUEST).\
+            filter(lambda p: len(p.mle.tlv.addr_reg_iid) >= 4).\
             must_next()
         pkts.filter_wpan_src64(ROUTER).\
             filter_wpan_dst64(MED).\
@@ -192,7 +194,7 @@
                               MODE_TLV,
                               ADDRESS_REGISTRATION_TLV
                              } < set(p.mle.tlv.type) and\
-                   len(p.mle.tlv.addr_reg_iid) > 0 and\
+                   len(p.mle.tlv.addr_reg_iid) >= 3 and\
                    set(p.mle.tlv.addr_reg_iid) < set(_pkt.mle.tlv.addr_reg_iid)
                    ).\
             must_next()
@@ -240,6 +242,7 @@
         _pkt = pkts.filter_wpan_src64(SED).\
             filter_wpan_dst64(ROUTER).\
             filter_mle_cmd(MLE_CHILD_UPDATE_REQUEST).\
+            filter(lambda p: len(p.mle.tlv.addr_reg_iid) >= 3).\
             must_next()
         pkts.filter_wpan_src64(ROUTER).\
             filter_wpan_dst64(SED).\
@@ -249,7 +252,7 @@
                               MODE_TLV,
                               ADDRESS_REGISTRATION_TLV
                              } < set(p.mle.tlv.type) and\
-                   len(p.mle.tlv.addr_reg_iid) > 0 and\
+                   len(p.mle.tlv.addr_reg_iid) >= 2 and\
                    set(p.mle.tlv.addr_reg_iid) < set(_pkt.mle.tlv.addr_reg_iid)
                    ).\
             must_next()
diff --git a/tests/scripts/thread-cert/Cert_8_3_01_CommissionerPetition.py b/tests/scripts/thread-cert/Cert_8_3_01_CommissionerPetition.py
index 8a2e742..a1b4a6b 100755
--- a/tests/scripts/thread-cert/Cert_8_3_01_CommissionerPetition.py
+++ b/tests/scripts/thread-cert/Cert_8_3_01_CommissionerPetition.py
@@ -119,7 +119,7 @@
         #             Commissioner ID TLV
         pv.verify_attached('COMMISSIONER', 'LEADER')
         _pkt = pkts.last()
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _leader_pet_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_wpan_dst16(LEADER_RLOC16).\
             filter_coap_request(LEAD_PET_URI).\
             filter(lambda p: {
@@ -143,7 +143,7 @@
         #             - Leader Data TLV
         #             - Network Data TLV
         #             - Source Address TLV
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_leader_pet_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(LEAD_PET_URI).\
             filter(lambda p: {
                               NM_STATE_TLV,
@@ -182,7 +182,7 @@
         #         CoAP Payload
         #             State TLV (value = Accept)
         #             Commissioner Session ID TLV
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _leader_ka_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(LEAD_KA_URI).\
             filter(lambda p: {
@@ -198,7 +198,7 @@
         #             2.04 Changed
         #         CoAP Payload
         #             State TLV (value = Accept)
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_leader_ka_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(LEAD_KA_URI).\
             filter(lambda p: {
                               NM_STATE_TLV
@@ -214,7 +214,7 @@
         #         CoAP Payload
         #             Commissioner Session ID TLV
         #             Steering Data TLV
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _mgmt_set_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(MGMT_COMMISSIONER_SET_URI).\
             filter(lambda p: {
@@ -237,7 +237,7 @@
         #             - Leader Data TLV
         #             - Network Data TLV
         #             - Source Address TLV
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_mgmt_set_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(MGMT_COMMISSIONER_SET_URI).\
             filter(lambda p: {
                               NM_STATE_TLV
@@ -276,7 +276,7 @@
         #         CoAP Payload
         #             State TLV (value = Reject)
         #             Commissioner Session ID TLV
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _leader_ka_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(LEAD_KA_URI).\
             filter(lambda p: {
@@ -300,7 +300,7 @@
         #             - Leader Data TLV
         #             - Network Data TLV
         #             - Source Address TLV
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_leader_ka_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(LEAD_KA_URI).\
             filter(lambda p: {
                               NM_STATE_TLV
@@ -334,7 +334,7 @@
         #              CON POST coap://<L>:MM/c/lp
         #          CoAP Payload
         #              Commissioner ID TLV
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _leader_pet_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(LEAD_PET_URI).\
             filter(lambda p: {
@@ -352,7 +352,7 @@
         #              Commissioner ID TLV
         #              Commissioner Session ID TLV (contains higher Session ID number
         #              than in Step 9)
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_leader_pet_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(LEAD_PET_URI).\
             filter(lambda p: {
                               NM_STATE_TLV,
diff --git a/tests/scripts/thread-cert/Cert_9_2_01_MGMTCommissionerGet.py b/tests/scripts/thread-cert/Cert_9_2_01_MGMTCommissionerGet.py
index 4e6870d..0c8d314 100755
--- a/tests/scripts/thread-cert/Cert_9_2_01_MGMTCommissionerGet.py
+++ b/tests/scripts/thread-cert/Cert_9_2_01_MGMTCommissionerGet.py
@@ -56,6 +56,7 @@
 
 class Cert_9_2_01_MGMTCommissionerGet(thread_cert.TestCase):
     SUPPORT_NCP = False
+    USE_MESSAGE_FACTORY = True
 
     TOPOLOGY = {
         COMMISSIONER: {
@@ -82,7 +83,6 @@
         self.nodes[COMMISSIONER].start()
         self.simulator.go(5)
         self.assertEqual(self.nodes[COMMISSIONER].get_state(), 'router')
-        self.simulator.get_messages_sent_by(LEADER)
 
         self.collect_leader_aloc(LEADER)
         self.collect_rlocs()
@@ -143,7 +143,7 @@
         #             Border Agent Locator TLV
         #             Commissioner Session ID TLV
         #             Steering Data TLV
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(MGMT_COMMISSIONER_GET_URI).\
             filter(lambda p: {
                               NM_COMMISSIONER_SESSION_ID_TLV,
@@ -163,7 +163,7 @@
         #         CoAP Payload
         #             Commissioner Session ID TLV
         #             Steering Data TLV
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _mgmt_get_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(MGMT_COMMISSIONER_GET_URI).\
             filter(lambda p: {
@@ -181,7 +181,7 @@
         #         Encoded values for the requested Commissioner Dataset parameters
         #             Commissioner Session ID TLV
         #             Steering Data TLV
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_mgmt_get_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(MGMT_COMMISSIONER_GET_URI).\
             filter(lambda p: {
                               NM_COMMISSIONER_SESSION_ID_TLV,
@@ -199,7 +199,7 @@
         #         CoAP Payload
         #             Commissioner Session ID TLV
         #             PAN ID TLV
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _mgmt_get_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(MGMT_COMMISSIONER_GET_URI).\
             filter(lambda p: {
@@ -217,7 +217,7 @@
         #         Encoded values for the requested Commissioner Dataset parameters
         #             Commissioner Session ID TLV
         #             (PAN ID TLV in Get TLV is ignored)
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_mgmt_get_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(MGMT_COMMISSIONER_GET_URI).\
             filter(lambda p: {
                               NM_COMMISSIONER_SESSION_ID_TLV
@@ -234,7 +234,7 @@
         #         CoAP Payload
         #             Border Agent Locator TLV
         #             Network Name TLV
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _mgmt_get_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(MGMT_COMMISSIONER_GET_URI).\
             filter(lambda p: {
@@ -252,7 +252,7 @@
         #         Encoded values for the requested Commissioner Dataset parameters
         #             Border Agent Locator TLV
         #             (Network Name TLV in Get TLV is ignored)
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_mgmt_get_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(MGMT_COMMISSIONER_GET_URI).\
             filter(lambda p: {
                               NM_BORDER_AGENT_LOCATOR_TLV
diff --git a/tests/scripts/thread-cert/Cert_9_2_02_MGMTCommissionerSet.py b/tests/scripts/thread-cert/Cert_9_2_02_MGMTCommissionerSet.py
index bb1d998..fde4007 100755
--- a/tests/scripts/thread-cert/Cert_9_2_02_MGMTCommissionerSet.py
+++ b/tests/scripts/thread-cert/Cert_9_2_02_MGMTCommissionerSet.py
@@ -152,7 +152,7 @@
         #         CoAP Payload
         #             (missing Commissioner Session ID TLV)
         #             Steering Data TLV (0xFF)
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _mgmt_set_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(MGMT_COMMISSIONER_SET_URI).\
             filter(lambda p:
@@ -167,7 +167,7 @@
         #             2.04 Changed
         #         CoAP Payload
         #             State TLV (value = Reject)
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_mgmt_set_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(MGMT_COMMISSIONER_SET_URI).\
             filter(lambda p:
                    [NM_STATE_TLV] == p.coap.tlv.type and\
@@ -182,7 +182,7 @@
         #         CoAP Payload
         #             Commissioner Session ID TLV
         #             Steering Data TLV (0xFF)
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _mgmt_set_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(MGMT_COMMISSIONER_SET_URI).\
             filter(lambda p: {
@@ -199,7 +199,7 @@
         #             2.04 Changed
         #         CoAP Payload
         #             State TLV (value = Accept)
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_mgmt_set_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(MGMT_COMMISSIONER_SET_URI).\
             filter(lambda p:
                    [NM_STATE_TLV] == p.coap.tlv.type and\
@@ -241,7 +241,7 @@
         #         CoAP Payload
         #             Commissioner Session ID TLV
         #             Border Agent Locator TLV (0x0400) (not allowed TLV)
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _mgmt_set_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(MGMT_COMMISSIONER_SET_URI).\
             filter(lambda p: {
@@ -258,7 +258,7 @@
         #             2.04 Changed
         #         CoAP Payload
         #             State TLV (value = Reject)
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_mgmt_set_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(MGMT_COMMISSIONER_SET_URI).\
             filter(lambda p:
                    [NM_STATE_TLV] == p.coap.tlv.type and\
@@ -274,7 +274,7 @@
         #             Commissioner Session ID TLV
         #             Steering Data TLV (0xFF)
         #             Border Agent Locator TLV (0x0400) (not allowed TLV)
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _mgmt_set_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(MGMT_COMMISSIONER_SET_URI).\
             filter(lambda p: {
@@ -293,7 +293,7 @@
         #             2.04 Changed
         #         CoAP Payload
         #             State TLV (value = Reject)
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_mgmt_set_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(MGMT_COMMISSIONER_SET_URI).\
             filter(lambda p:
                    [NM_STATE_TLV] == p.coap.tlv.type and\
@@ -308,7 +308,7 @@
         #         CoAP Payload
         #             Commissioner Session ID TLV (0xFFFF) (invalid value)
         #             Steering Data TLV (0xFF)
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _mgmt_set_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(MGMT_COMMISSIONER_SET_URI).\
             filter(lambda p: {
@@ -326,7 +326,7 @@
         #             2.04 Changed
         #         CoAP Payload
         #             State TLV (value = Reject)
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_mgmt_set_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(MGMT_COMMISSIONER_SET_URI).\
             filter(lambda p:
                    [NM_STATE_TLV] == p.coap.tlv.type and\
@@ -342,7 +342,7 @@
         #             Commissioner Session ID TLV
         #             Steering Data TLV (0xFF)
         #             Channel TLV (not allowed TLV)
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _mgmt_set_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(MGMT_COMMISSIONER_SET_URI).\
             filter(lambda p: {
@@ -360,7 +360,7 @@
         #             2.04 Changed
         #         CoAP Payload
         #             State TLV (value = Accept)
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_mgmt_set_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(MGMT_COMMISSIONER_SET_URI).\
             filter(lambda p:
                    [NM_STATE_TLV] == p.coap.tlv.type and\
diff --git a/tests/scripts/thread-cert/Cert_9_2_03_ActiveDatasetGet.py b/tests/scripts/thread-cert/Cert_9_2_03_ActiveDatasetGet.py
index 78b547a..fb12027 100755
--- a/tests/scripts/thread-cert/Cert_9_2_03_ActiveDatasetGet.py
+++ b/tests/scripts/thread-cert/Cert_9_2_03_ActiveDatasetGet.py
@@ -57,6 +57,7 @@
 
 class Cert_9_2_03_ActiveDatasetGet(thread_cert.TestCase):
     SUPPORT_NCP = False
+    USE_MESSAGE_FACTORY = False
 
     TOPOLOGY = {
         COMMISSIONER: {
@@ -83,7 +84,6 @@
         self.nodes[COMMISSIONER].start()
         self.simulator.go(5)
         self.assertEqual(self.nodes[COMMISSIONER].get_state(), 'router')
-        self.simulator.get_messages_sent_by(LEADER)
 
         self.collect_rlocs()
         self.collect_rloc16s()
@@ -152,7 +152,7 @@
         #             PAN ID TLV
         #             PSKc TLV
         #             Security Policy TLV
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(MGMT_ACTIVE_GET_URI).\
             filter(lambda p: {
                               NM_ACTIVE_TIMESTAMP_TLV,
diff --git a/tests/scripts/thread-cert/Cert_9_2_19_PendingDatasetGet.py b/tests/scripts/thread-cert/Cert_9_2_19_PendingDatasetGet.py
index f00f37b..0232355 100755
--- a/tests/scripts/thread-cert/Cert_9_2_19_PendingDatasetGet.py
+++ b/tests/scripts/thread-cert/Cert_9_2_19_PendingDatasetGet.py
@@ -131,7 +131,7 @@
         #             CON POST coap://<L>:MM/c/pg
         #         CoAP Payload
         #             <empty> - get all Active Operational Dataset parameters
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _mgmt_get_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(MGMT_PENDING_GET_URI).\
             filter(lambda p: p.coap.tlv.type is nullField).\
@@ -143,7 +143,7 @@
         #             2.04 Changed
         #         CoAP Payload
         #             <empty> - (no Pending Operational Dataset)
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_mgmt_get_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(MGMT_PENDING_GET_URI).\
             filter(lambda p: p.coap.tlv.type is nullField).\
             must_next()
@@ -158,7 +158,7 @@
         #             Delay Timer TLV: 1 minute
         #             Pending Timestamp TLV: 30s
         #             PAN ID TLV: 0xAFCE (new value)
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _mgmt_pending_set_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(MGMT_PENDING_SET_URI).\
             filter(lambda p: {
@@ -179,7 +179,7 @@
         #             2.04 Changed
         #         CoAP Payload
         #             State TLV (value = Accept (01))
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_mgmt_pending_set_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(MGMT_PENDING_SET_URI).\
             filter(lambda p:
                    p.thread_meshcop.tlv.state == 1
@@ -192,7 +192,7 @@
         #             CON POST coap://<L>:MM/c/pg
         #         CoAP Payload
         #             <empty> - get all Active Operational Dataset parameters
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _mgmt_pending_get_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(MGMT_PENDING_GET_URI).\
             filter(lambda p: p.coap.tlv.type is nullField).\
@@ -216,7 +216,7 @@
         #             Pending Timestamp TLV
         #             PSKc TLV
         #             Security Policy TLV
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_mgmt_pending_get_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(MGMT_PENDING_GET_URI).\
             filter(lambda p: {
                               NM_ACTIVE_TIMESTAMP_TLV,
@@ -241,7 +241,7 @@
         #             CON POST coap://<L>:MM/c/pg
         #         CoAP Payload
         #             PAN ID TLV
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _mgmt_pending_get_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(MGMT_PENDING_GET_URI).\
             filter(lambda p: {
@@ -257,7 +257,7 @@
         #         CoAP Payload
         #             PAN ID TLV
         #             Delay Timer TLV
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_mgmt_pending_get_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(MGMT_PENDING_GET_URI).\
             filter(lambda p: {
                               NM_DELAY_TIMER_TLV,
@@ -274,7 +274,7 @@
         #             CON POST coap://<L>:MM/c/pg
         #         CoAP Payload
         #             <empty> - get all Active Operational Dataset parameters
-        pkts.filter_wpan_src64(COMMISSIONER).\
+        _mgmt_pending_get_pkt = pkts.filter_wpan_src64(COMMISSIONER).\
             filter_ipv6_2dsts(LEADER_ALOC, LEADER_RLOC).\
             filter_coap_request(MGMT_PENDING_GET_URI).\
             filter(lambda p: p.coap.tlv.type is nullField).\
@@ -286,7 +286,7 @@
         #             2.04 Changed
         #         CoAP Payload
         #             <empty> - (no Pending Operational Dataset)
-        pkts.filter_ipv6_src_dst(LEADER_RLOC, COMMISSIONER_RLOC).\
+        pkts.filter_ipv6_src_dst(_mgmt_pending_get_pkt.ipv6.dst, COMMISSIONER_RLOC).\
             filter_coap_ack(MGMT_PENDING_GET_URI).\
             filter(lambda p: p.thread_meshcop.tlv.type is nullField).\
            must_next()
diff --git a/tests/scripts/thread-cert/Makefile.am b/tests/scripts/thread-cert/Makefile.am
index 37b8fcc..b777c7d 100644
--- a/tests/scripts/thread-cert/Makefile.am
+++ b/tests/scripts/thread-cert/Makefile.am
@@ -154,9 +154,12 @@
     test_coap.py                                                     \
     test_coap_observe.py                                             \
     test_coaps.py                                                    \
+    test_coap_block.py                                               \
     test_common.py                                                   \
     test_crypto.py                                                   \
+    test_dataset_updater.py                                          \
     test_diag.py                                                     \
+    test_dnssd.py                                                    \
     test_ipv6.py                                                     \
     test_ipv6_fragmentation.py                                       \
     test_ipv6_source_selection.py                                    \
@@ -170,6 +173,10 @@
     test_route_table.py                                              \
     test_router_reattach.py                                          \
     test_service.py                                                  \
+    test_srp_auto_start_mode.py                                      \
+    test_srp_lease.py                                                \
+    test_srp_name_conflicts.py                                       \
+    test_srp_register_single_service.py                              \
     thread_cert.py                                                   \
     tlvs_parsing.py                                                  \
     thread_cert.py                                                   \
@@ -202,9 +209,12 @@
     test_coap.py                                                     \
     test_coap_observe.py                                             \
     test_coaps.py                                                    \
+    test_coap_block.py                                               \
     test_common.py                                                   \
     test_crypto.py                                                   \
+    test_dataset_updater.py                                          \
     test_diag.py                                                     \
+    test_dnssd.py                                                    \
     test_ipv6.py                                                     \
     test_ipv6_fragmentation.py                                       \
     test_ipv6_source_selection.py                                    \
@@ -218,6 +228,10 @@
     test_route_table.py                                              \
     test_router_reattach.py                                          \
     test_service.py                                                  \
+    test_srp_auto_start_mode.py                                      \
+    test_srp_lease.py                                                \
+    test_srp_name_conflicts.py                                       \
+    test_srp_register_single_service.py                              \
     Cert_5_1_01_RouterAttach.py                                      \
     Cert_5_1_02_ChildAddressTimeout.py                               \
     Cert_5_1_03_RouterAddressReallocation.py                         \
diff --git a/tests/scripts/thread-cert/backbone/bbr_5_11_01.py b/tests/scripts/thread-cert/backbone/bbr_5_11_01.py
index 57f0c47..9c0352a 100755
--- a/tests/scripts/thread-cert/backbone/bbr_5_11_01.py
+++ b/tests/scripts/thread-cert/backbone/bbr_5_11_01.py
@@ -91,6 +91,11 @@
         self.simulator.go(5)
         self.assertEqual('router', self.nodes[ROUTER2].get_state())
 
+        # The OTBR docker enables SRP Server by default, lets explicitly
+        # disable SRP server to avoid Network Data population.
+        # TODO: Enhance the test script to tolerate additional Sertivce TLV
+        # in Network Data.
+        self.nodes[BR_1].srp_server_set_enabled(False)
         self.nodes[BR_1].start()
         self.simulator.go(5)
         self.assertEqual('router', self.nodes[BR_1].get_state())
diff --git a/tests/scripts/thread-cert/border_router/test_advertising_proxy.py b/tests/scripts/thread-cert/border_router/test_advertising_proxy.py
new file mode 100755
index 0000000..28e093e
--- /dev/null
+++ b/tests/scripts/thread-cert/border_router/test_advertising_proxy.py
@@ -0,0 +1,232 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+import ipaddress
+import logging
+import unittest
+
+import config
+import thread_cert
+
+# Test description:
+#   This test verifies the basic functionality of advertising proxy.
+#
+# Topology:
+#    ----------------(eth)--------------------
+#           |                   |
+#          BR (Leader)    HOST (mDNS Browser)
+#           |
+#        ROUTER
+#
+
+BR = 1
+ROUTER = 2
+HOST = 3
+LEASE = 10  # Seconds
+KEY_LEASE = 20  # Seconds
+
+
+class SingleHostAndService(thread_cert.TestCase):
+    USE_MESSAGE_FACTORY = False
+
+    TOPOLOGY = {
+        BR: {
+            'name': 'BR_1',
+            'allowlist': [ROUTER],
+            'is_otbr': True,
+            'version': '1.2',
+            'router_selection_jitter': 2,
+        },
+        ROUTER: {
+            'name': 'Router_1',
+            'allowlist': [BR],
+            'version': '1.2',
+            'router_selection_jitter': 2,
+        },
+        HOST: {
+            'name': 'Host',
+            'is_host': True
+        },
+    }
+
+    def test(self):
+        host = self.nodes[HOST]
+        server = self.nodes[BR]
+        client = self.nodes[ROUTER]
+
+        host.start(start_radvd=False)
+        self.simulator.go(5)
+
+        server.srp_server_set_enabled(True)
+        server.srp_server_set_lease_range(LEASE, LEASE, KEY_LEASE, KEY_LEASE)
+        server.start()
+        self.simulator.go(5)
+        self.assertEqual('leader', server.get_state())
+
+        client.start()
+        self.simulator.go(5)
+        self.assertEqual('router', client.get_state())
+
+        #
+        # 1. Register a single service.
+        #
+
+        client.srp_client_set_host_name('my-host')
+        client.srp_client_set_host_address('2001::1')
+        client.srp_client_add_service('my-service', '_ipps._tcp', 12345)
+        client.srp_client_enable_auto_start_mode()
+        self.simulator.go(2)
+
+        self.check_host_and_service(server, client, '2001::1')
+
+        #
+        # 2. Discover the service by the HOST on the ethernet. This makes sure
+        #    the Advertising Proxy multicasts the same service on ethernet.
+        #
+
+        self.host_check_mdns_service(host, '2001::1')
+
+        #
+        # 3. Check if the Advertising Proxy removes the service from ethernet
+        #    when the SRP client removes it.
+        #
+
+        client.srp_client_remove_host()
+        self.simulator.go(2)
+
+        self.assertIsNone(host.discover_mdns_service('my-service', '_ipps._tcp', 'my-host'))
+
+        #
+        # 4. Check if we can discover the mDNS service again when re-registering the
+        #    service from the SRP client.
+        #
+
+        client.srp_client_set_host_name('my-host')
+        client.srp_client_set_host_address('2001::1')
+        client.srp_client_add_service('my-service', '_ipps._tcp', 12345)
+        self.simulator.go(2)
+
+        self.check_host_and_service(server, client, '2001::1')
+        self.host_check_mdns_service(host, '2001::1')
+
+        #
+        # 5. Update the SRP host address and make sure the Advertising Proxy
+        #    will follow it.
+        #
+
+        client.srp_client_set_host_address('2001::2')
+        self.simulator.go(8)
+
+        self.check_host_and_service(server, client, '2001::2')
+        self.host_check_mdns_service(host, '2001::2')
+
+        #
+        # 6. Check if the service is removed by the Advertising Proxy when the SRP server is stopped.
+        #
+
+        server.srp_server_set_enabled(False)
+        self.simulator.go(2)
+
+        self.assertEqual(len(server.srp_server_get_hosts()), 0)
+        self.assertEqual(len(server.srp_server_get_services()), 0)
+        self.assertIsNone(host.discover_mdns_service('my-service', '_ipps._tcp', 'my-host'))
+
+        server.srp_server_set_enabled(True)
+        self.simulator.go(LEASE)
+
+        self.check_host_and_service(server, client, '2001::2')
+        self.host_check_mdns_service(host, '2001::2')
+
+        #
+        # 7. Check if the expired service is removed by the Advertising Proxy.
+        #
+
+        client.srp_client_stop()
+        self.simulator.go(LEASE + 2)
+
+        self.assertIsNone(host.discover_mdns_service('my-service', '_ipps._tcp', 'my-host'))
+
+    def host_check_mdns_service(self, host, host_addr):
+        service = host.discover_mdns_service('my-service', '_ipps._tcp', 'my-host')
+        self.assertIsNotNone(service)
+        self.assertEqual(service['instance'], 'my-service')
+        self.assertEqual(service['name'], '_ipps._tcp')
+        self.assertEqual(service['port'], 12345)
+        self.assertEqual(service['priority'], 0)
+        self.assertEqual(service['weight'], 0)
+        self.assertEqual(service['host'], 'my-host')
+        self.assertEqual(ipaddress.ip_address(service['addresses'][0]), ipaddress.ip_address(host_addr))
+        self.assertEqual(len(service['addresses']), 1)
+
+    def check_host_and_service(self, server, client, host_addr):
+        """Check that we have properly registered host and service instance.
+        """
+
+        client_services = client.srp_client_get_services()
+        print(client_services)
+        self.assertEqual(len(client_services), 1)
+        client_service = client_services[0]
+
+        # Verify that the client possesses correct service resources.
+        self.assertEqual(client_service['instance'], 'my-service')
+        self.assertEqual(client_service['name'], '_ipps._tcp')
+        self.assertEqual(int(client_service['port']), 12345)
+        self.assertEqual(int(client_service['priority']), 0)
+        self.assertEqual(int(client_service['weight']), 0)
+
+        # Verify that the client received a SUCCESS response for the server.
+        self.assertEqual(client_service['state'], 'Registered')
+
+        server_services = server.srp_server_get_services()
+        print(server_services)
+        self.assertEqual(len(server_services), 1)
+        server_service = server_services[0]
+
+        # Verify that the server accepted the SRP registration and stores
+        # the same service resources.
+        self.assertEqual(server_service['deleted'], 'false')
+        self.assertEqual(server_service['instance'], client_service['instance'])
+        self.assertEqual(server_service['name'], client_service['name'])
+        self.assertEqual(int(server_service['port']), int(client_service['port']))
+        self.assertEqual(int(server_service['priority']), int(client_service['priority']))
+        self.assertEqual(int(server_service['weight']), int(client_service['weight']))
+        self.assertEqual(server_service['host'], 'my-host')
+
+        server_hosts = server.srp_server_get_hosts()
+        print(server_hosts)
+        self.assertEqual(len(server_hosts), 1)
+        server_host = server_hosts[0]
+
+        self.assertEqual(server_host['deleted'], 'false')
+        self.assertEqual(server_host['fullname'], server_service['host_fullname'])
+        self.assertEqual(len(server_host['addresses']), 1)
+        self.assertEqual(ipaddress.ip_address(server_host['addresses'][0]), ipaddress.ip_address(host_addr))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/scripts/thread-cert/border_router/test_dnssd_server.py b/tests/scripts/thread-cert/border_router/test_dnssd_server.py
new file mode 100644
index 0000000..80282f0
--- /dev/null
+++ b/tests/scripts/thread-cert/border_router/test_dnssd_server.py
@@ -0,0 +1,284 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+import ipaddress
+import json
+import logging
+import unittest
+
+import config
+import thread_cert
+
+# Test description:
+#   This test verifies DNS-SD server works on a Duckhorn BR and is accessible from a Host.
+#
+# Topology:
+#    ----------------(eth)--------------------
+#           |                      |
+#          BR1 (Leader, Server)   HOST
+#         /        \
+#      CLIENT1    CLIENT2
+
+SERVER = BR1 = 1
+CLIENT1, CLIENT2 = 2, 3
+HOST = 4
+
+DIGGER = HOST
+
+DOMAIN = 'default.service.arpa.'
+SERVICE = '_testsrv._udp'
+SERVICE_FULL_NAME = f'{SERVICE}.{DOMAIN}'
+
+VALID_SERVICE_NAMES = [
+    '_abc._udp.default.service.arpa.',
+    '_abc._tcp.default.service.arpa.',
+]
+
+WRONG_SERVICE_NAMES = [
+    '_testsrv._udp.default.service.xxxx.',
+    '_testsrv._txp,default.service.arpa.',
+]
+
+
+class TestDnssdServerOnBr(thread_cert.TestCase):
+    USE_MESSAGE_FACTORY = False
+
+    TOPOLOGY = {
+        BR1: {
+            'name': 'SERVER',
+            'is_otbr': True,
+            'version': '1.2',
+            'router_selection_jitter': 1,
+        },
+        CLIENT1: {
+            'name': 'CLIENT1',
+            'router_selection_jitter': 1,
+        },
+        CLIENT2: {
+            'name': 'CLIENT2',
+            'router_selection_jitter': 1,
+        },
+        HOST: {
+            'name': 'Host',
+            'is_host': True
+        },
+    }
+
+    def test(self):
+        self.nodes[HOST].start(start_radvd=False)
+        self.simulator.go(5)
+
+        self.nodes[BR1].start()
+        self.simulator.go(5)
+        self.assertEqual('leader', self.nodes[BR1].get_state())
+        self.nodes[SERVER].srp_server_set_enabled(True)
+
+        self.nodes[CLIENT1].start()
+
+        self.simulator.go(5)
+        self.assertEqual('router', self.nodes[CLIENT1].get_state())
+
+        self.nodes[CLIENT2].start()
+        self.simulator.go(5)
+        self.assertEqual('router', self.nodes[CLIENT2].get_state())
+
+        self.simulator.go(10)
+
+        # Router1 can ping to/from the Host on infra link.
+        self.assertTrue(self.nodes[BR1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0],
+                                             backbone=True))
+        self.assertTrue(self.nodes[HOST].ping(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
+                                              backbone=True))
+
+        client1_addrs = [
+            self.nodes[CLIENT1].get_mleid(), self.nodes[CLIENT1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]
+        ]
+        self._config_srp_client_services(CLIENT1, 'ins1', 'host1', 11111, 1, 1, client1_addrs)
+
+        client2_addrs = [
+            self.nodes[CLIENT2].get_mleid(), self.nodes[CLIENT2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]
+        ]
+        self._config_srp_client_services(CLIENT2, 'ins2', 'host2', 22222, 2, 2, client2_addrs)
+
+        ins1_full_name = f'ins1.{SERVICE_FULL_NAME}'
+        ins2_full_name = f'ins2.{SERVICE_FULL_NAME}'
+        host1_full_name = f'host1.{DOMAIN}'
+        host2_full_name = f'host2.{DOMAIN}'
+        server_addr = self.nodes[SERVER].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]
+
+        # check if PTR query works
+        dig_result = self.nodes[DIGGER].dns_dig(server_addr, SERVICE_FULL_NAME, 'PTR')
+
+        self._assert_dig_result_matches(
+            dig_result, {
+                'QUESTION': [(SERVICE_FULL_NAME, 'IN', 'PTR')],
+                'ANSWER': [(SERVICE_FULL_NAME, 'IN', 'PTR', f'ins1.{SERVICE_FULL_NAME}'),
+                           (SERVICE_FULL_NAME, 'IN', 'PTR', f'ins2.{SERVICE_FULL_NAME}')],
+                'ADDITIONAL': [
+                    (ins1_full_name, 'IN', 'SRV', 1, 1, 11111, host1_full_name),
+                    (ins1_full_name, 'IN', 'TXT', '""'),
+                    (host1_full_name, 'IN', 'AAAA', client1_addrs[0]),
+                    (host1_full_name, 'IN', 'AAAA', client1_addrs[1]),
+                    (ins2_full_name, 'IN', 'SRV', 2, 2, 22222, host2_full_name),
+                    (ins2_full_name, 'IN', 'TXT', '""'),
+                    (host2_full_name, 'IN', 'AAAA', client2_addrs[0]),
+                    (host2_full_name, 'IN', 'AAAA', client2_addrs[1]),
+                ],
+            })
+
+        # check if SRV query works
+        dig_result = self.nodes[DIGGER].dns_dig(server_addr, ins1_full_name, 'SRV')
+        self._assert_dig_result_matches(
+            dig_result, {
+                'QUESTION': [(ins1_full_name, 'IN', 'SRV')],
+                'ANSWER': [(ins1_full_name, 'IN', 'SRV', 1, 1, 11111, host1_full_name),],
+                'ADDITIONAL': [
+                    (host1_full_name, 'IN', 'AAAA', client1_addrs[0]),
+                    (host1_full_name, 'IN', 'AAAA', client1_addrs[1]),
+                ],
+            })
+
+        dig_result = self.nodes[DIGGER].dns_dig(server_addr, ins2_full_name, 'SRV')
+        self._assert_dig_result_matches(
+            dig_result, {
+                'QUESTION': [(ins2_full_name, 'IN', 'SRV')],
+                'ANSWER': [(ins2_full_name, 'IN', 'SRV', 2, 2, 22222, host2_full_name),],
+                'ADDITIONAL': [
+                    (host2_full_name, 'IN', 'AAAA', client2_addrs[0]),
+                    (host2_full_name, 'IN', 'AAAA', client2_addrs[1]),
+                ],
+            })
+
+        # check if TXT query works
+        dig_result = self.nodes[DIGGER].dns_dig(server_addr, ins1_full_name, 'TXT')
+        self._assert_dig_result_matches(dig_result, {
+            'QUESTION': [(ins1_full_name, 'IN', 'TXT')],
+            'ANSWER': [(ins1_full_name, 'IN', 'TXT', '""'),],
+        })
+
+        dig_result = self.nodes[DIGGER].dns_dig(server_addr, ins2_full_name, 'TXT')
+        self._assert_dig_result_matches(dig_result, {
+            'QUESTION': [(ins2_full_name, 'IN', 'TXT')],
+            'ANSWER': [(ins2_full_name, 'IN', 'TXT', '""'),],
+        })
+
+        # check if AAAA query works
+        dig_result = self.nodes[DIGGER].dns_dig(server_addr, host1_full_name, 'AAAA')
+        self._assert_dig_result_matches(
+            dig_result, {
+                'QUESTION': [(host1_full_name, 'IN', 'AAAA'),],
+                'ANSWER': [
+                    (host1_full_name, 'IN', 'AAAA', client1_addrs[0]),
+                    (host1_full_name, 'IN', 'AAAA', client1_addrs[1]),
+                ],
+            })
+
+        dig_result = self.nodes[DIGGER].dns_dig(server_addr, host2_full_name, 'AAAA')
+        self._assert_dig_result_matches(
+            dig_result, {
+                'QUESTION': [(host2_full_name, 'IN', 'AAAA'),],
+                'ANSWER': [
+                    (host2_full_name, 'IN', 'AAAA', client2_addrs[0]),
+                    (host2_full_name, 'IN', 'AAAA', client2_addrs[1]),
+                ],
+            })
+
+        # check some invalid queries
+        for qtype in ['A', 'CNAME']:
+            dig_result = self.nodes[DIGGER].dns_dig(server_addr, host1_full_name, qtype)
+            self._assert_dig_result_matches(dig_result, {
+                'status': 'NOTIMP',
+            })
+
+        for service_name in WRONG_SERVICE_NAMES:
+            dig_result = self.nodes[DIGGER].dns_dig(server_addr, service_name, 'PTR')
+            self._assert_dig_result_matches(dig_result, {
+                'status': 'NXDOMAIN',
+            })
+
+    def _config_srp_client_services(self, client, instancename, hostname, port, priority, weight, addrs):
+        self.nodes[client].netdata_show()
+        srp_server_port = self.nodes[client].get_srp_server_port()
+
+        self.nodes[client].srp_client_start(self.nodes[SERVER].get_mleid(), srp_server_port)
+        self.nodes[client].srp_client_set_host_name(hostname)
+        self.nodes[client].srp_client_set_host_address(*addrs)
+        self.nodes[client].srp_client_add_service(instancename, SERVICE, port, priority, weight)
+
+        self.simulator.go(5)
+        self.assertEqual(self.nodes[client].srp_client_get_host_state(), 'Registered')
+
+    def _assert_have_question(self, dig_result, question):
+        self.assertIn(question, dig_result['QUESTION'], (question, dig_result))
+
+    def _assert_have_answer(self, dig_result, record, additional=False):
+        for dig_answer in dig_result['ANSWER' if not additional else 'ADDITIONAL']:
+            dig_answer = list(dig_answer)
+            dig_answer[1:2] = []  # remove TTL from answer
+
+            record = list(record)
+
+            # convert IPv6 addresses to `ipaddress.IPv6Address` before matching
+            if dig_answer[2] == 'AAAA':
+                dig_answer[3] = ipaddress.IPv6Address(dig_answer[3])
+
+            if record[2] == 'AAAA':
+                record[3] = ipaddress.IPv6Address(record[3])
+
+            if dig_answer == record:
+                return
+
+        self.fail((record, dig_result))
+
+    def _assert_dig_result_matches(self, dig_result, expected_result):
+        self.assertEqual(dig_result['opcode'], expected_result.get('opcode', 'QUERY'), dig_result)
+        self.assertEqual(dig_result['status'], expected_result.get('status', 'NOERROR'), dig_result)
+
+        if 'QUESTION' in expected_result:
+            self.assertEqual(len(dig_result['QUESTION']), len(expected_result['QUESTION']), dig_result)
+
+            for question in expected_result['QUESTION']:
+                self._assert_have_question(dig_result, question)
+
+        if 'ANSWER' in expected_result:
+            self.assertEqual(len(dig_result['ANSWER']), len(expected_result['ANSWER']), dig_result)
+
+            for record in expected_result['ANSWER']:
+                self._assert_have_answer(dig_result, record, additional=False)
+
+        if 'ADDITIONAL' in expected_result:
+            self.assertEqual(len(dig_result['ADDITIONAL']), len(expected_result['ADDITIONAL']), dig_result)
+
+            for record in expected_result['ADDITIONAL']:
+                self._assert_have_answer(dig_result, record, additional=True)
+
+        logging.info("dig result matches:\r%s", json.dumps(dig_result, indent=True))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/scripts/thread-cert/border_router/test_multi_border_routers.py b/tests/scripts/thread-cert/border_router/test_multi_border_routers.py
new file mode 100755
index 0000000..f67d49b
--- /dev/null
+++ b/tests/scripts/thread-cert/border_router/test_multi_border_routers.py
@@ -0,0 +1,217 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2020, The OpenThread Authors.
+#  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.
+#
+import logging
+import unittest
+
+import config
+import thread_cert
+
+# Test description:
+#   This test verifies that a single OMR and on-link prefix is chosen
+#   and advertised when there are multiple Border Routers in the same
+#   Thread and infrastructure network.
+#
+# Topology:
+#    ----------------(eth)------------------
+#           |                  |     |
+#          BR1 (Leader) ----- BR2   HOST
+#           |                  |
+#        ROUTER1            ROUTER2
+#
+
+BR1 = 1
+ROUTER1 = 2
+BR2 = 3
+ROUTER2 = 4
+HOST = 5
+
+CHANNEL1 = 18
+
+
+class MultiBorderRouters(thread_cert.TestCase):
+    USE_MESSAGE_FACTORY = False
+
+    TOPOLOGY = {
+        BR1: {
+            'name': 'BR_1',
+            'allowlist': [ROUTER1, BR2],
+            'is_otbr': True,
+            'version': '1.2',
+            'channel': CHANNEL1,
+            'router_selection_jitter': 2,
+        },
+        ROUTER1: {
+            'name': 'Router_1',
+            'allowlist': [BR1],
+            'version': '1.2',
+            'channel': CHANNEL1,
+            'router_selection_jitter': 2,
+        },
+        BR2: {
+            'name': 'BR_2',
+            'allowlist': [BR1, ROUTER2],
+            'is_otbr': True,
+            'version': '1.2',
+            'channel': CHANNEL1,
+            'router_selection_jitter': 2,
+        },
+        ROUTER2: {
+            'name': 'Router_2',
+            'allowlist': [BR2],
+            'version': '1.2',
+            'channel': CHANNEL1,
+            'router_selection_jitter': 2,
+        },
+        HOST: {
+            'name': 'Host',
+            'is_host': True
+        },
+    }
+
+    def test(self):
+        self.nodes[HOST].start(start_radvd=False)
+        self.simulator.go(5)
+
+        self.nodes[BR1].start()
+        self.simulator.go(5)
+        self.assertEqual('leader', self.nodes[BR1].get_state())
+
+        self.nodes[ROUTER1].start()
+        self.simulator.go(5)
+        self.assertEqual('router', self.nodes[ROUTER1].get_state())
+
+        self.nodes[BR2].start()
+        self.simulator.go(5)
+        self.assertEqual('router', self.nodes[BR2].get_state())
+
+        self.nodes[ROUTER2].start()
+        self.simulator.go(5)
+        self.assertEqual('router', self.nodes[ROUTER2].get_state())
+
+        #
+        # Case 1. bi-directional connectivity when there are two BRs.
+        #
+
+        self.simulator.go(10)
+        self.collect_ipaddrs()
+
+        logging.info("BR1     addrs: %r", self.nodes[BR1].get_addrs())
+        logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs())
+        logging.info("BR2     addrs: %r", self.nodes[BR2].get_addrs())
+        logging.info("ROUTER2 addrs: %r", self.nodes[ROUTER2].get_addrs())
+        logging.info("HOST    addrs: %r", self.nodes[HOST].get_addrs())
+
+        self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1)
+        self.assertTrue(len(self.nodes[BR2].get_prefixes()) == 1)
+        self.assertTrue(len(self.nodes[ROUTER2].get_prefixes()) == 1)
+
+        br1_omr_prefix = self.nodes[BR1].get_prefixes()[0]
+
+        # Each BR should independently register an external route for the on-link prefix.
+        self.assertTrue(len(self.nodes[BR1].get_routes()) == 2)
+        self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 2)
+        self.assertTrue(len(self.nodes[BR2].get_routes()) == 2)
+        self.assertTrue(len(self.nodes[ROUTER2].get_routes()) == 2)
+
+        external_route = self.nodes[BR1].get_routes()[0]
+        br1_on_link_prefix = external_route.split(' ')[0]
+
+        self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+        self.assertTrue(len(self.nodes[BR2].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+        self.assertTrue(len(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+        self.assertTrue(len(self.nodes[HOST].get_matched_ula_addresses(br1_on_link_prefix)) == 1)
+
+        # Router1 and Router2 can ping each other inside the Thread network.
+        self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
+        self.assertTrue(self.nodes[ROUTER2].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
+
+        # Both Router1 and Router2 can ping to/from the Host on infra link.
+        self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_matched_ula_addresses(br1_on_link_prefix)[0]))
+        self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
+                                              backbone=True))
+        self.assertTrue(self.nodes[ROUTER2].ping(self.nodes[HOST].get_matched_ula_addresses(br1_on_link_prefix)[0]))
+        self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
+                                              backbone=True))
+
+        #
+        # Case 2. Another BR continues providing Border Routing when current one is disabled.
+        #
+
+        self.nodes[BR1].disable_br()
+
+        self.simulator.go(15)
+        self.collect_ipaddrs()
+
+        logging.info("BR1     addrs: %r", self.nodes[BR1].get_addrs())
+        logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs())
+        logging.info("BR2     addrs: %r", self.nodes[BR2].get_addrs())
+        logging.info("ROUTER2 addrs: %r", self.nodes[ROUTER2].get_addrs())
+        logging.info("HOST    addrs: %r", self.nodes[HOST].get_addrs())
+
+        self.assertGreaterEqual(len(self.nodes[HOST].get_addrs()), 2)
+
+        self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1)
+        self.assertTrue(len(self.nodes[BR2].get_prefixes()) == 1)
+        self.assertTrue(len(self.nodes[ROUTER2].get_prefixes()) == 1)
+
+        br2_omr_prefix = self.nodes[BR1].get_prefixes()[0]
+        self.assertNotEqual(br1_omr_prefix, br2_omr_prefix)
+
+        # Only BR2 will register external route for the on-link prefix.
+        self.assertTrue(len(self.nodes[BR1].get_routes()) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 1)
+        self.assertTrue(len(self.nodes[BR2].get_routes()) == 1)
+        self.assertTrue(len(self.nodes[ROUTER2].get_routes()) == 1)
+
+        br2_external_route = self.nodes[BR2].get_routes()[0]
+        br2_on_link_prefix = br2_external_route.split(' ')[0]
+
+        self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+        self.assertTrue(len(self.nodes[BR2].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+        self.assertTrue(len(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+
+        self.assertTrue(len(self.nodes[HOST].get_matched_ula_addresses(br2_on_link_prefix)) == 1)
+
+        # Router1 and Router2 can ping each other inside the Thread network.
+        self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
+        self.assertTrue(self.nodes[ROUTER2].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
+
+        # Both Router1 and Router2 can ping to/from the Host on infra link.
+        for router in [ROUTER1, ROUTER2]:
+            self.assertTrue(self.nodes[router].ping(self.nodes[HOST].get_matched_ula_addresses(br2_on_link_prefix)[0]))
+            self.assertTrue(self.nodes[HOST].ping(self.nodes[router].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
+                                                  backbone=True))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/scripts/thread-cert/border_router/test_multi_thread_networks.py b/tests/scripts/thread-cert/border_router/test_multi_thread_networks.py
new file mode 100755
index 0000000..a05432d
--- /dev/null
+++ b/tests/scripts/thread-cert/border_router/test_multi_thread_networks.py
@@ -0,0 +1,149 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2020, The OpenThread Authors.
+#  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.
+#
+import logging
+import unittest
+
+import config
+import thread_cert
+
+# Test description:
+#   This test verifies bi-directional connectivity accross multiple Thread networks.
+#
+# Topology:
+#    -------------(eth)----------------
+#           |               |
+#          BR1             BR2
+#           |               |
+#        ROUTER1         ROUTER2
+#
+#     Thread Net1       Thread Net2
+#
+
+BR1 = 1
+ROUTER1 = 2
+BR2 = 3
+ROUTER2 = 4
+
+CHANNEL1 = 18
+CHANNEL2 = 19
+
+
+class MultiThreadNetworks(thread_cert.TestCase):
+    USE_MESSAGE_FACTORY = False
+
+    TOPOLOGY = {
+        BR1: {
+            'name': 'BR_1',
+            'allowlist': [ROUTER1],
+            'is_otbr': True,
+            'version': '1.2',
+            'channel': CHANNEL1,
+            'router_selection_jitter': 1,
+        },
+        ROUTER1: {
+            'name': 'Router_1',
+            'allowlist': [BR1],
+            'version': '1.2',
+            'channel': CHANNEL1,
+            'router_selection_jitter': 1,
+        },
+        BR2: {
+            'name': 'BR_2',
+            'allowlist': [ROUTER2],
+            'is_otbr': True,
+            'version': '1.2',
+            'channel': CHANNEL2,
+            'router_selection_jitter': 1,
+        },
+        ROUTER2: {
+            'name': 'Router_2',
+            'allowlist': [BR2],
+            'version': '1.2',
+            'channel': CHANNEL2,
+            'router_selection_jitter': 1,
+        },
+    }
+
+    def test(self):
+        self.nodes[BR1].start()
+        self.simulator.go(5)
+        self.assertEqual('leader', self.nodes[BR1].get_state())
+
+        self.nodes[ROUTER1].start()
+        self.simulator.go(5)
+        self.assertEqual('router', self.nodes[ROUTER1].get_state())
+
+        self.nodes[BR2].start()
+        self.simulator.go(5)
+        self.assertEqual('leader', self.nodes[BR2].get_state())
+
+        self.nodes[ROUTER2].start()
+        self.simulator.go(5)
+        self.assertEqual('router', self.nodes[ROUTER2].get_state())
+
+        self.collect_ipaddrs()
+
+        logging.info("BR1     addrs: %r", self.nodes[BR1].get_addrs())
+        logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs())
+        logging.info("BR2     addrs: %r", self.nodes[BR2].get_addrs())
+        logging.info("ROUTER2 addrs: %r", self.nodes[ROUTER2].get_addrs())
+
+        self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1)
+        self.assertTrue(len(self.nodes[BR2].get_prefixes()) == 1)
+        self.assertTrue(len(self.nodes[ROUTER2].get_prefixes()) == 1)
+
+        br1_omr_prefix = self.nodes[BR1].get_prefixes()[0]
+        br2_omr_prefix = self.nodes[BR2].get_prefixes()[0]
+
+        self.assertNotEqual(br1_omr_prefix, br2_omr_prefix)
+
+        # Each BR should independently register an external route for the on-link prefix
+        # and OMR prefix in another Thread Network.
+        self.assertTrue(len(self.nodes[BR1].get_routes()) == 2)
+        self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 2)
+        self.assertTrue(len(self.nodes[BR2].get_routes()) == 2)
+        self.assertTrue(len(self.nodes[ROUTER2].get_routes()) == 2)
+
+        br1_external_routes = self.nodes[BR1].get_routes()
+        br2_external_routes = self.nodes[BR2].get_routes()
+
+        br1_external_routes.sort()
+        br2_external_routes.sort()
+        self.assertNotEqual(br1_external_routes, br2_external_routes)
+
+        self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+        self.assertTrue(len(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+
+        self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[ROUTER2].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
+        self.assertTrue(self.nodes[ROUTER2].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/scripts/thread-cert/border_router/test_plat_udp_accessiblity.py b/tests/scripts/thread-cert/border_router/test_plat_udp_accessiblity.py
new file mode 100644
index 0000000..180e70c
--- /dev/null
+++ b/tests/scripts/thread-cert/border_router/test_plat_udp_accessiblity.py
@@ -0,0 +1,145 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+import unittest
+
+import thread_cert
+import config
+
+# Test description:
+#   This test verifies UDP servers can be accessible using RLOC/ALOC/MLEID/LINK-LOCAL/OMR when PLAT_UDP is enabled.
+#   This test uses SRP server for convince.
+#
+# Topology:
+#    -----------(eth)------
+#           |
+#          BR1 (Leader)
+#           |
+#        ROUTER1
+#
+
+BR1 = 1
+ROUTER1 = 2
+
+
+class TestPlatUdpAccessibility(thread_cert.TestCase):
+    USE_MESSAGE_FACTORY = False
+
+    TOPOLOGY = {
+        BR1: {
+            'name': 'BR_1',
+            'is_otbr': True,
+            'version': '1.2',
+            'router_selection_jitter': 1,
+        },
+        ROUTER1: {
+            'name': 'Router_1',
+            'version': '1.2',
+            'router_selection_jitter': 1,
+        },
+    }
+
+    def test(self):
+        self.nodes[BR1].start()
+        self.simulator.go(5)
+        self.assertEqual('leader', self.nodes[BR1].get_state())
+        self.nodes[BR1].srp_server_set_enabled(True)
+
+        self.nodes[ROUTER1].start()
+        self.simulator.go(5)
+        self.assertEqual('router', self.nodes[ROUTER1].get_state())
+
+        # Router1 can ping to/from the Host on infra link.
+        self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[BR1].get_rloc()))
+
+        server_port = self.nodes[BR1].get_srp_server_port()
+
+        self._test_srp_server(self.nodes[BR1].get_mleid(), server_port)
+        self._test_srp_server(self.nodes[BR1].get_linklocal(), server_port)
+        self._test_srp_server(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0], server_port)
+        self._test_srp_server(self.nodes[BR1].get_rloc(), server_port)
+        for server_aloc in self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.ALOC):
+            self._test_srp_server(server_aloc, server_port)
+
+        self._testDhcp6ClientAfterReset(BR1, BR1, BR1)
+
+    def _testDhcp6ClientAfterReset(self, server, client, reset_device):
+        DHCP6_PREFIX = '2001::/64'
+
+        # Configure DHCP6 server
+        self.nodes[server].add_prefix(DHCP6_PREFIX, 'pdros')
+        self.simulator.go(3)
+        self.nodes[server].register_netdata()
+        self.simulator.go(10)
+
+        # Verify DHCP6 client works
+        self.assertTrue(self.nodes[client].get_addr(DHCP6_PREFIX))
+        self.simulator.go(3)
+
+        self.nodes[reset_device].reset()
+        self.nodes[reset_device].start()
+        self.simulator.go(5)
+        self.assertIn(self.nodes[reset_device].get_state(), ['leader', 'router'])
+        self.simulator.go(5)
+
+        if reset_device == server:
+            # Reconfigure DHCP6 server if necessary
+            self.nodes[server].add_prefix(DHCP6_PREFIX, 'pdros')
+            self.simulator.go(3)
+            self.nodes[server].register_netdata()
+
+        self.simulator.go(10)
+
+        # Verify DHCP6 client works after reset
+        self.assertTrue(self.nodes[client].get_addr(DHCP6_PREFIX))
+        self.simulator.go(3)
+
+    def _test_srp_server(self, server_addr, server_port):
+        print(f'Testing SRP server: {server_addr}:{server_port}')
+
+        # check if the SRP client can register to the SRP server
+        self.nodes[ROUTER1].srp_client_start(server_addr, server_port)
+        self.nodes[ROUTER1].srp_client_set_host_name('host1')
+        self.nodes[ROUTER1].srp_client_set_host_address(self.nodes[ROUTER1].get_rloc())
+        self.nodes[ROUTER1].srp_client_add_service('ins1', '_ipp._tcp', 11111)
+        self.simulator.go(3)
+        self.assertEqual(self.nodes[ROUTER1].srp_client_get_host_state(), 'Registered')
+
+        # check if the SRP client can remove from the SRP server
+        self.nodes[ROUTER1].srp_client_remove_host('host1')
+        self.nodes[ROUTER1].srp_client_remove_service('ins1', '_ipp._tcp')
+        self.simulator.go(3)
+        self.assertEqual(self.nodes[ROUTER1].srp_client_get_host_state(), 'Removed')
+
+        # stop the SRP client for the next round
+        self.nodes[ROUTER1].srp_client_stop()
+        self.simulator.go(3)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/scripts/thread-cert/border_router/test_single_border_router.py b/tests/scripts/thread-cert/border_router/test_single_border_router.py
new file mode 100755
index 0000000..dc07be1
--- /dev/null
+++ b/tests/scripts/thread-cert/border_router/test_single_border_router.py
@@ -0,0 +1,327 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2020, The OpenThread Authors.
+#  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.
+#
+import logging
+import unittest
+
+import config
+import thread_cert
+
+# Test description:
+#   This test verifies bi-directional connectivity between Thread end device
+#   and infra host.
+#
+# Topology:
+#    ----------------(eth)--------------------
+#           |                 |
+#          BR1 (Leader)      HOST
+#           |
+#        ROUTER1
+#
+
+BR1 = 1
+ROUTER1 = 2
+HOST = 4
+
+CHANNEL1 = 18
+
+# The two prefixes are set small enough that a random-generated OMR prefix is
+# very likely greater than them. So that the duckhorn BR will remove the random-generated one.
+ON_MESH_PREFIX1 = "fd00:00:00:01::/64"
+ON_MESH_PREFIX2 = "fd00:00:00:02::/64"
+
+
+class SingleBorderRouter(thread_cert.TestCase):
+    USE_MESSAGE_FACTORY = False
+
+    TOPOLOGY = {
+        BR1: {
+            'name': 'BR_1',
+            'allowlist': [ROUTER1],
+            'is_otbr': True,
+            'version': '1.2',
+            'channel': CHANNEL1,
+            'router_selection_jitter': 2,
+        },
+        ROUTER1: {
+            'name': 'Router_1',
+            'allowlist': [BR1],
+            'version': '1.2',
+            'channel': CHANNEL1,
+            'router_selection_jitter': 2,
+        },
+        HOST: {
+            'name': 'Host',
+            'is_host': True
+        },
+    }
+
+    def test(self):
+        self.nodes[HOST].start(start_radvd=False)
+        self.simulator.go(5)
+
+        self.nodes[BR1].start()
+        self.simulator.go(5)
+        self.assertEqual('leader', self.nodes[BR1].get_state())
+
+        self.nodes[ROUTER1].start()
+        self.simulator.go(5)
+        self.assertEqual('router', self.nodes[ROUTER1].get_state())
+
+        #
+        # Case 1. There is no OMR prefix or on-link prefix.
+        #
+
+        self.simulator.go(10)
+        self.collect_ipaddrs()
+
+        logging.info("BR1     addrs: %r", self.nodes[BR1].get_addrs())
+        logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs())
+        logging.info("HOST    addrs: %r", self.nodes[HOST].get_addrs())
+
+        self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1)
+        self.assertTrue(len(self.nodes[BR1].get_routes()) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 1)
+
+        omr_prefix = self.nodes[BR1].get_prefixes()[0]
+        external_route = self.nodes[BR1].get_routes()[0]
+
+        self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+        self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1)
+
+        br1_omr_address = self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]
+        router1_omr_address = self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0]
+        host_ula_address = self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]
+
+        # Router1 can ping to/from the Host on infra link.
+        self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]))
+        self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
+                                              backbone=True))
+
+        #
+        # Case 2. User adds smaller on-mesh prefix.
+        #         1. Should deregister our local OMR prefix.
+        #         2. Should re-register our local OMR prefix when user prefix
+        #            is removed.
+        #
+
+        self.nodes[BR1].add_prefix(ON_MESH_PREFIX1)
+        self.nodes[BR1].add_prefix(ON_MESH_PREFIX2)
+        self.nodes[BR1].register_netdata()
+
+        self.simulator.go(10)
+        self.collect_ipaddrs()
+
+        logging.info("BR1     addrs: %r", self.nodes[BR1].get_addrs())
+        logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs())
+        logging.info("HOST    addrs: %r", self.nodes[HOST].get_addrs())
+
+        self.assertGreaterEqual(len(self.nodes[HOST].get_addrs()), 2)
+
+        self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 2)
+        self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 2)
+        self.assertTrue(len(self.nodes[BR1].get_routes()) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 1)
+
+        self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 2)
+        self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 2)
+        self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1)
+
+        # Router1 can ping to/from the Host on infra link.
+        self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]))
+        self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
+                                              backbone=True))
+        self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[1],
+                                              backbone=True))
+
+        # Remove user prefixes, should re-register local OMR prefix.
+        self.nodes[BR1].remove_prefix(ON_MESH_PREFIX1)
+        self.nodes[BR1].remove_prefix(ON_MESH_PREFIX2)
+        self.nodes[BR1].register_netdata()
+
+        self.simulator.go(10)
+        self.collect_ipaddrs()
+
+        logging.info("BR1     addrs: %r", self.nodes[BR1].get_addrs())
+        logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs())
+        logging.info("HOST    addrs: %r", self.nodes[HOST].get_addrs())
+
+        self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1)
+        self.assertTrue(len(self.nodes[BR1].get_routes()) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 1)
+
+        # The same local OMR and on-link prefix should be re-register.
+        self.assertEqual(omr_prefix, self.nodes[BR1].get_prefixes()[0])
+        self.assertEqual(omr_prefix, self.nodes[ROUTER1].get_prefixes()[0])
+        self.assertEqual(external_route, self.nodes[BR1].get_routes()[0])
+        self.assertEqual(external_route, self.nodes[ROUTER1].get_routes()[0])
+
+        self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+        self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1)
+
+        self.assertEqual(br1_omr_address, self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])
+        self.assertEqual(router1_omr_address, self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])
+        self.assertEqual(host_ula_address, self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])
+
+        # Router1 can ping to/from the Host on infra link.
+        self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]))
+        self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
+                                              backbone=True))
+
+        #
+        # Case 3. OMR and on-link prefixes should be removed when Border Routing is
+        #         explicitly disabled and added when Border Routing is enabled again.
+        #
+
+        self.nodes[BR1].disable_br()
+
+        self.simulator.go(10)
+        self.collect_ipaddrs()
+
+        logging.info("BR1     addrs: %r", self.nodes[BR1].get_addrs())
+        logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs())
+        logging.info("HOST    addrs: %r", self.nodes[HOST].get_addrs())
+
+        self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 0)
+        self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 0)
+        self.assertTrue(len(self.nodes[BR1].get_routes()) == 0)
+        self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 0)
+        self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 0)
+        self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 0)
+
+        # Per RFC 4862, the host will not immediately remove the ULA address, but deprecate it.
+        self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1)
+
+        self.nodes[BR1].enable_br()
+
+        # It takes around 10 seconds to start sending RA messages.
+        self.simulator.go(15)
+        self.collect_ipaddrs()
+
+        logging.info("BR1     addrs: %r", self.nodes[BR1].get_addrs())
+        logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs())
+        logging.info("HOST    addrs: %r", self.nodes[HOST].get_addrs())
+
+        self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1)
+        self.assertTrue(len(self.nodes[BR1].get_routes()) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 1)
+
+        # The same local OMR and on-link prefix should be re-registered.
+        self.assertEqual(omr_prefix, self.nodes[BR1].get_prefixes()[0])
+        self.assertEqual(omr_prefix, self.nodes[ROUTER1].get_prefixes()[0])
+        self.assertEqual(external_route, self.nodes[BR1].get_routes()[0])
+        self.assertEqual(external_route, self.nodes[ROUTER1].get_routes()[0])
+
+        self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+        self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1)
+
+        self.assertEqual(br1_omr_address, self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])
+        self.assertEqual(router1_omr_address, self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])
+        self.assertEqual(host_ula_address, self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])
+
+        # Router1 can ping to/from the Host on infra link.
+        self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]))
+        self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
+                                              backbone=True))
+
+        #
+        # Case 4. The Routing Manager should be stopped if the infra interface went down.
+        #
+
+        self.nodes[BR1].disable_ether()
+
+        self.simulator.go(10)
+        self.collect_ipaddrs()
+
+        logging.info("BR1     addrs: %r", self.nodes[BR1].get_addrs())
+        logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs())
+        logging.info("HOST    addrs: %r", self.nodes[HOST].get_addrs())
+
+        self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 0)
+        self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 0)
+        self.assertTrue(len(self.nodes[BR1].get_routes()) == 0)
+        self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 0)
+        self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 0)
+        self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 0)
+
+        self.nodes[BR1].enable_ether()
+
+        # It takes around 10 seconds to start sending RA messages.
+        self.simulator.go(15)
+        self.collect_ipaddrs()
+
+        logging.info("BR1     addrs: %r", self.nodes[BR1].get_addrs())
+        logging.info("ROUTER1 addrs: %r", self.nodes[ROUTER1].get_addrs())
+        logging.info("HOST    addrs: %r", self.nodes[HOST].get_addrs())
+
+        self.assertTrue(len(self.nodes[BR1].get_prefixes()) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_prefixes()) == 1)
+        self.assertTrue(len(self.nodes[BR1].get_routes()) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_routes()) == 1)
+
+        # The same local OMR and on-link prefix should be re-registered.
+        self.assertEqual(omr_prefix, self.nodes[BR1].get_prefixes()[0])
+        self.assertEqual(omr_prefix, self.nodes[ROUTER1].get_prefixes()[0])
+        self.assertEqual(external_route, self.nodes[BR1].get_routes()[0])
+        self.assertEqual(external_route, self.nodes[ROUTER1].get_routes()[0])
+
+        self.assertTrue(len(self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+        self.assertTrue(len(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)) == 1)
+        self.assertTrue(len(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)) == 1)
+
+        self.assertEqual(br1_omr_address, self.nodes[BR1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])
+        self.assertEqual(router1_omr_address, self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0])
+        self.assertEqual(host_ula_address, self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0])
+
+        # Router1 can ping to/from the Host on infra link.
+        self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]))
+        self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
+                                              backbone=True))
+
+        #
+        # Case 5. Test if the linux host is still reachable if rejoin the network.
+        #
+
+        self.nodes[HOST].disable_ether()
+        self.simulator.go(10)
+        self.nodes[HOST].enable_ether()
+        self.simulator.go(10)
+
+        self.assertTrue(self.nodes[ROUTER1].ping(self.nodes[HOST].get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA)[0]))
+        self.assertTrue(self.nodes[HOST].ping(self.nodes[ROUTER1].get_ip6_address(config.ADDRESS_TYPE.OMR)[0],
+                                              backbone=True))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/scripts/thread-cert/config.py b/tests/scripts/thread-cert/config.py
index a1851c7..eaff213 100644
--- a/tests/scripts/thread-cert/config.py
+++ b/tests/scripts/thread-cert/config.py
@@ -78,6 +78,11 @@
 ALL_DOMAIN_BBRS_ADDRESS = 'ff32:40:fd00:7d03:7d03:7d03:0:3'
 ALL_DOMAIN_BBRS_ADDRESS_ALTER = 'ff32:40:fd00:7d04:7d04:7d04:0:3'
 
+ONLINK_PREFIX = 'fd00:dead:face::/64'
+
+# Any address starts with 'fd' are considered on-link address.
+ONLINK_PREFIX_REGEX_PATTERN = '^fd'
+
 DEFAULT_MASTER_KEY = bytearray([
     0x00,
     0x11,
@@ -106,6 +111,8 @@
     ML_EID = 'ML_EID'
     DUA = 'DUA'
     BACKBONE_GUA = 'BACKBONE_GUA'
+    OMR = 'OMR'
+    ONLINK_ULA = 'ONLINK_ULA'
 
 
 RSSI = {
diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py
index 3f3fc7c..58ab2c4 100755
--- a/tests/scripts/thread-cert/node.py
+++ b/tests/scripts/thread-cert/node.py
@@ -51,6 +51,8 @@
 
 
 class OtbrDocker:
+    RESET_DELAY = 3
+
     _socat_proc = None
     _ot_rcp_proc = None
     _docker_proc = None
@@ -217,12 +219,94 @@
             else:
                 return lines
 
+    def dns_dig(self, server: str, name: str, qtype: str):
+        """
+        Run dig command to query a DNS server.
+
+        Args:
+            server: the server address.
+            name: the name to query.
+            qtype: the query type (e.g. AAAA, PTR, TXT, SRV).
+
+        Returns:
+            The dig result similar as below:
+            {
+                "opcode": "QUERY",
+                "status": "NOERROR",
+                "id": "64144",
+                "QUESTION": [
+                    ('google.com.', 'IN', 'AAAA')
+                ],
+                "ANSWER": [
+                    ('google.com.', 107,	'IN', 'AAAA', '2404:6800:4008:c00::71'),
+                    ('google.com.', 107,	'IN', 'AAAA', '2404:6800:4008:c00::8a'),
+                    ('google.com.', 107,	'IN', 'AAAA', '2404:6800:4008:c00::66'),
+                    ('google.com.', 107,	'IN', 'AAAA', '2404:6800:4008:c00::8b'),
+                ],
+                "ADDITIONAL": [
+                ],
+            }
+        """
+        output = self.bash(f'dig -6 @{server} {name} {qtype}')
+
+        section = None
+        dig_result = {
+            'QUESTION': [],
+            'ANSWER': [],
+            'ADDITIONAL': [],
+        }
+
+        for line in output:
+            line = line.strip()
+
+            if line.startswith(';; ->>HEADER<<- '):
+                headers = line[len(';; ->>HEADER<<- '):].split(', ')
+                for header in headers:
+                    key, val = header.split(': ')
+                    dig_result[key] = val
+
+                continue
+
+            if line == ';; QUESTION SECTION:':
+                section = 'QUESTION'
+                continue
+            elif line == ';; ANSWER SECTION:':
+                section = 'ANSWER'
+                continue
+            elif line == ';; ADDITIONAL SECTION:':
+                section = 'ADDITIONAL'
+                continue
+            elif section and not line:
+                section = None
+                continue
+
+            if section:
+                assert line
+
+                if section == 'QUESTION':
+                    assert line.startswith(';')
+                    line = line[1:]
+
+                record = list(line.split())
+
+                if section != 'QUESTION':
+                    record[1] = int(record[1])
+                    if record[3] == 'SRV':
+                        record[4], record[5], record[6] = map(int, [record[4], record[5], record[6]])
+
+                dig_result[section].append(tuple(record))
+
+        return dig_result
+
     def _setup_sysctl(self):
         self.bash(f'sysctl net.ipv6.conf.{self.ETH_DEV}.accept_ra=2')
+        self.bash(f'sysctl net.ipv6.conf.{self.ETH_DEV}.accept_ra_rt_info_max_plen=64')
 
 
 class OtCli:
 
+    RESET_DELAY = 0.1
+
     def __init__(self, nodeid, is_mtd=False, version=None, is_bbr=False, **kwargs):
         self.verbose = int(float(os.getenv('VERBOSE', 0)))
         self.node_type = os.getenv('NODE_TYPE', 'sim')
@@ -261,8 +345,13 @@
         # Default command if no match below, will be overridden if below conditions are met.
         cmd = './ot-cli-%s' % (mode)
 
+        # For Thread 1.2 MTD node, use ot-cli-mtd build regardless of OT_CLI_PATH
+        if self.version == '1.2' and mode == 'mtd' and 'top_builddir' in os.environ:
+            srcdir = os.environ['top_builddir']
+            cmd = '%s/examples/apps/cli/ot-cli-%s %d' % (srcdir, mode, nodeid)
+
         # If Thread version of node matches the testing environment version.
-        if self.version == self.env_version:
+        elif self.version == self.env_version:
             # Load Thread 1.2 BBR device when testing Thread 1.2 scenarios
             # which requires device with Backbone functionality.
             if self.version == '1.2' and self.is_bbr:
@@ -283,6 +372,7 @@
             if 'RADIO_DEVICE' in os.environ:
                 cmd += ' --real-time-signal=+1 -v spinel+hdlc+uart://%s?forkpty-arg=%d' % (os.environ['RADIO_DEVICE'],
                                                                                            nodeid)
+                self.is_posix = True
             else:
                 cmd += ' %d' % nodeid
 
@@ -298,6 +388,7 @@
             if 'RADIO_DEVICE_1_1' in os.environ:
                 cmd += ' --real-time-signal=+1 -v spinel+hdlc+uart://%s?forkpty-arg=%d' % (
                     os.environ['RADIO_DEVICE_1_1'], nodeid)
+                self.is_posix = True
             else:
                 cmd += ' %d' % nodeid
 
@@ -326,6 +417,7 @@
             if 'RADIO_DEVICE' in os.environ:
                 args = ' --real-time-signal=+1 spinel+hdlc+uart://%s?forkpty-arg=%d' % (os.environ['RADIO_DEVICE'],
                                                                                         nodeid)
+                self.is_posix = True
             else:
                 args = ''
 
@@ -365,6 +457,7 @@
             if 'RADIO_DEVICE_1_1' in os.environ:
                 args = ' --real-time-signal=+1 spinel+hdlc+uart://%s?forkpty-arg=%d' % (os.environ['RADIO_DEVICE_1_1'],
                                                                                         nodeid)
+                self.is_posix = True
             else:
                 args = ''
 
@@ -418,6 +511,7 @@
     def __init__(self, nodeid, name=None, simulator=None, **kwargs):
         self.nodeid = nodeid
         self.name = name or ('Node%d' % nodeid)
+        self.is_posix = False
 
         self.simulator = simulator
         if self.simulator:
@@ -444,6 +538,9 @@
                 if timeout <= 0:
                     raise
 
+    def _expect_done(self, timeout=-1):
+        self._expect('Done', timeout)
+
     def _prepare_pattern(self, pattern):
         """Build a new pexpect pattern matching line by line.
 
@@ -493,12 +590,12 @@
 
         return results
 
-    def _expect_command_output(self, cmd: str):
+    def _expect_command_output(self, cmd: str, ignore_logs=True):
         lines = []
         cmd_output_started = False
 
         while True:
-            self._expect(r"[^\n]+")
+            self._expect(r"[^\n]+\n")
             line = self.pexpect.match.group(0).decode('utf8').strip()
 
             if line.startswith('> '):
@@ -511,7 +608,7 @@
                 cmd_output_started = True
                 continue
 
-            if not cmd_output_started:
+            if not cmd_output_started or (ignore_logs and self.__is_logging_line(line)):
                 continue
 
             if line == 'Done':
@@ -524,6 +621,9 @@
         print(f'_expect_command_output({cmd!r}) returns {lines!r}')
         return lines
 
+    def __is_logging_line(self, line: str) -> bool:
+        return len(line) >= 6 and line[:6] in {'[DEBG]', '[INFO]', '[NOTE]', '[WARN]', '[CRIT]', '[NONE]'}
+
     def read_cert_messages_in_commissioning_log(self, timeout=-1):
         """Get the log of the traffic after DTLS handshake.
         """
@@ -588,7 +688,7 @@
     def set_mode(self, mode):
         cmd = 'mode %s' % mode
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def debug(self, level):
         # `debug` command will not trigger interaction with simulator
@@ -604,29 +704,29 @@
 
     def interface_up(self):
         self.send_command('ifconfig up')
-        self._expect('Done')
+        self._expect_done()
 
     def interface_down(self):
         self.send_command('ifconfig down')
-        self._expect('Done')
+        self._expect_done()
 
     def thread_start(self):
         self.send_command('thread start')
-        self._expect('Done')
+        self._expect_done()
 
     def thread_stop(self):
         self.send_command('thread stop')
-        self._expect('Done')
+        self._expect_done()
 
     def commissioner_start(self):
         cmd = 'commissioner start'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def commissioner_stop(self):
         cmd = 'commissioner stop'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def commissioner_state(self):
         states = [r'disabled', r'petitioning', r'active']
@@ -636,32 +736,32 @@
     def commissioner_add_joiner(self, addr, psk):
         cmd = 'commissioner joiner add %s %s' % (addr, psk)
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def commissioner_set_provisioning_url(self, provisioning_url=''):
         cmd = 'commissioner provisioningurl %s' % provisioning_url
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def joiner_start(self, pskd='', provisioning_url=''):
         cmd = 'joiner start %s %s' % (pskd, provisioning_url)
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def clear_allowlist(self):
         cmd = 'macfilter addr clear'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def enable_allowlist(self):
         cmd = 'macfilter addr allowlist'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def disable_allowlist(self):
         cmd = 'macfilter addr disable'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def add_allowlist(self, addr, rssi=None):
         cmd = 'macfilter addr add %s' % addr
@@ -670,7 +770,7 @@
             cmd += ' %s' % rssi
 
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def get_bbr_registration_jitter(self):
         self.send_command('bbr jitter')
@@ -679,22 +779,275 @@
     def set_bbr_registration_jitter(self, jitter):
         cmd = 'bbr jitter %d' % jitter
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
+
+    def srp_server_set_enabled(self, enable):
+        cmd = f'srp server {"enable" if enable else "disable"}'
+        self.send_command(cmd)
+        self._expect_done()
+
+    def srp_server_set_lease_range(self, min_lease, max_lease, min_key_lease, max_key_lease):
+        self.send_command(f'srp server lease {min_lease} {max_lease} {min_key_lease} {max_key_lease}')
+        self._expect_done()
+
+    def srp_server_get_hosts(self):
+        """Returns the host list on the SRP server as a list of property
+           dictionary.
+
+           Example output:
+           [{
+               'fullname': 'my-host.default.service.arpa.',
+               'name': 'my-host',
+               'deleted': 'false',
+               'addresses': ['2001::1', '2001::2']
+           }]
+        """
+
+        cmd = 'srp server host'
+        self.send_command(cmd)
+        lines = self._expect_command_output(cmd)
+        host_list = []
+        while lines:
+            host = {}
+
+            host['fullname'] = lines.pop(0).strip()
+            host['name'] = host['fullname'].split('.')[0]
+
+            host['deleted'] = lines.pop(0).strip().split(':')[1].strip()
+            if host['deleted'] == 'true':
+                host_list.append(host)
+                continue
+
+            addresses = lines.pop(0).strip().split('[')[1].strip(' ]').split(',')
+            map(str.strip, addresses)
+            host['addresses'] = [addr for addr in addresses if addr]
+
+            host_list.append(host)
+
+        return host_list
+
+    def srp_server_get_host(self, host_name):
+        """Returns host on the SRP server that matches given host name.
+
+           Example usage:
+           self.srp_server_get_host("my-host")
+        """
+
+        for host in self.srp_server_get_hosts():
+            if host_name == host['name']:
+                return host
+
+    def srp_server_get_services(self):
+        """Returns the service list on the SRP server as a list of property
+           dictionary.
+
+           Example output:
+           [{
+               'fullname': 'my-service._ipps._tcp.default.service.arpa.',
+               'instance': 'my-service',
+               'name': '_ipps._tcp',
+               'deleted': 'false',
+               'port': '12345',
+               'priority': '0',
+               'weight': '0',
+               'TXT': ['abc=010203'],
+               'host_fullname': 'my-host.default.service.arpa.',
+               'host': 'my-host',
+               'addresses': ['2001::1', '2001::2']
+           }]
+
+           Note that the TXT data is output as a HEX string.
+        """
+
+        cmd = 'srp server service'
+        self.send_command(cmd)
+        lines = self._expect_command_output(cmd)
+        service_list = []
+        while lines:
+            service = {}
+
+            service['fullname'] = lines.pop(0).strip()
+            name_labels = service['fullname'].split('.')
+            service['instance'] = name_labels[0]
+            service['name'] = '.'.join(name_labels[1:3])
+
+            service['deleted'] = lines.pop(0).strip().split(':')[1].strip()
+            if service['deleted'] == 'true':
+                service_list.append(service)
+                continue
+
+            # 'port', 'priority', 'weight'
+            for i in range(0, 3):
+                key_value = lines.pop(0).strip().split(':')
+                service[key_value[0].strip()] = key_value[1].strip()
+
+            txt_entries = lines.pop(0).strip().split('[')[1].strip(' ]').split(',')
+            txt_entries = map(str.strip, txt_entries)
+            service['TXT'] = [txt for txt in txt_entries if txt]
+
+            service['host_fullname'] = lines.pop(0).strip().split(':')[1].strip()
+            service['host'] = service['host_fullname'].split('.')[0]
+
+            addresses = lines.pop(0).strip().split('[')[1].strip(' ]').split(',')
+            addresses = map(str.strip, addresses)
+            service['addresses'] = [addr for addr in addresses if addr]
+
+            service_list.append(service)
+
+        return service_list
+
+    def srp_server_get_service(self, instance_name, service_name):
+        """Returns service on the SRP server that matches given instance
+           name and service name.
+
+           Example usage:
+           self.srp_server_get_service("my-service", "_ipps._tcp")
+        """
+
+        for service in self.srp_server_get_services():
+            if (instance_name == service['instance'] and service_name == service['name']):
+                return service
+
+    def get_srp_server_port(self):
+        """Returns the SRP server UDP port by parsing
+           the SRP Server Data in Network Data.
+        """
+
+        for service in self.get_services():
+            # TODO: for now, we are using 0xfd as the SRP service data.
+            #       May use a dedicated bit flag for SRP server.
+            if int(service[1], 16) == 0x5d:
+                # The SRP server data are 2-bytes UDP port number.
+                return int(service[2], 16)
+
+    def srp_client_start(self, server_address, server_port):
+        self.send_command(f'srp client start {server_address} {server_port}')
+        self._expect_done()
+
+    def srp_client_stop(self):
+        self.send_command(f'srp client stop')
+        self._expect_done()
+
+    def srp_client_get_state(self):
+        cmd = 'srp client state'
+        self.send_command(cmd)
+        return self._expect_command_output(cmd)[0]
+
+    def srp_client_get_auto_start_mode(self):
+        cmd = 'srp client autostart'
+        self.send_command(cmd)
+        return self._expect_command_output(cmd)[0]
+
+    def srp_client_enable_auto_start_mode(self):
+        self.send_command(f'srp client autostart enable')
+        self._expect_done()
+
+    def srp_client_disable_auto_start_mode(self):
+        self.send_command(f'srp client autostart able')
+        self._expect_done()
+
+    def srp_client_get_server_address(self):
+        cmd = 'srp client server address'
+        self.send_command(cmd)
+        return self._expect_command_output(cmd)[0]
+
+    def srp_client_get_server_port(self):
+        cmd = 'srp client server port'
+        self.send_command(cmd)
+        return int(self._expect_command_output(cmd)[0])
+
+    def srp_client_get_host_state(self):
+        cmd = 'srp client host state'
+        self.send_command(cmd)
+        return self._expect_command_output(cmd)[0]
+
+    def srp_client_set_host_name(self, name):
+        self.send_command(f'srp client host name {name}')
+        self._expect_done()
+
+    def srp_client_get_host_name(self):
+        self.send_command(f'srp client host name')
+        self._expect_done()
+
+    def srp_client_remove_host(self, remove_key=False):
+        self.send_command(f'srp client host remove {"1" if remove_key else "0"}')
+        self._expect_done()
+
+    def srp_client_clear_host(self):
+        self.send_command(f'srp client host clear')
+        self._expect_done()
+
+    def srp_client_set_host_address(self, *addrs: str):
+        self.send_command(f'srp client host address {" ".join(addrs)}')
+        self._expect_done()
+
+    def srp_client_get_host_address(self):
+        self.send_command(f'srp client host address')
+        self._expect_done()
+
+    def srp_client_add_service(self, instance_name, service_name, port, priority=0, weight=0, txt_entries=[]):
+        txt_record = "".join(self._encode_txt_entry(entry) for entry in txt_entries)
+        self.send_command(
+            f'srp client service add {instance_name} {service_name} {port} {priority} {weight} {txt_record}')
+        self._expect_done()
+
+    def srp_client_remove_service(self, instance_name, service_name):
+        self.send_command(f'srp client service remove {instance_name} {service_name}')
+        self._expect_done()
+
+    def srp_client_get_services(self):
+        cmd = 'srp client service'
+        self.send_command(cmd)
+        service_lines = self._expect_command_output(cmd)
+        return [self._parse_srp_client_service(line) for line in service_lines]
+
+    def _encode_txt_entry(self, entry):
+        """Encodes the TXT entry to the DNS-SD TXT record format as a HEX string.
+
+           Example usage:
+           self._encode_txt_entries(['abc'])     -> '03616263'
+           self._encode_txt_entries(['def='])    -> '046465663d'
+           self._encode_txt_entries(['xyz=XYZ']) -> '0778797a3d58595a'
+        """
+        return '{:02x}'.format(len(entry)) + "".join("{:02x}".format(ord(c)) for c in entry)
+
+    def _parse_srp_client_service(self, line: str):
+        """Parse one line of srp service list into a dictionary which
+           maps string keys to string values.
+
+           Example output for input
+           'instance:\"%s\", name:\"%s\", state:%s, port:%d, priority:%d, weight:%d"'
+           {
+               'instance': 'my-service',
+               'name': '_ipps._udp',
+               'state': 'ToAdd',
+               'port': '12345',
+               'priority': '0',
+               'weight': '0'
+           }
+
+           Note that value of 'port', 'priority' and 'weight' are represented
+           as strings but not integers.
+        """
+        key_values = [word.strip().split(':') for word in line.split(',')]
+        keys = [key_value[0] for key_value in key_values]
+        values = [key_value[1].strip('"') for key_value in key_values]
+        return dict(zip(keys, values))
 
     def enable_backbone_router(self):
         cmd = 'bbr enable'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def disable_backbone_router(self):
         cmd = 'bbr disable'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def register_backbone_router(self):
         cmd = 'bbr register'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def get_backbone_router_state(self):
         states = [r'Disabled', r'Primary', r'Secondary']
@@ -742,7 +1095,7 @@
             cmd += ' timeout %d' % mlr_timeout
 
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def set_domain_prefix(self, prefix, flags='prosD'):
         self.add_prefix(prefix, flags)
@@ -763,7 +1116,7 @@
         if iid is not None:
             cmd += ' ' + str(iid)
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def set_dua_iid(self, iid: str):
         assert len(iid) == 16
@@ -771,12 +1124,12 @@
 
         cmd = 'dua iid {}'.format(iid)
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def clear_dua_iid(self):
         cmd = 'dua iid clear'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def multicast_listener_list(self) -> Dict[ipaddress.IPv6Address, int]:
         cmd = 'bbr mgmt mlr listener'
@@ -797,7 +1150,7 @@
     def multicast_listener_clear(self):
         cmd = f'bbr mgmt mlr listener clear'
         self.send_command(cmd)
-        self._expect("Done")
+        self._expect_done()
 
     def multicast_listener_add(self, ip: Union[ipaddress.IPv6Address, str], timeout: int = 0):
         if not isinstance(ip, ipaddress.IPv6Address):
@@ -810,7 +1163,7 @@
     def set_next_mlr_response(self, status: int):
         cmd = 'bbr mgmt mlr response {}'.format(status)
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def register_multicast_listener(self, *ipaddrs: Union[ipaddress.IPv6Address, str], timeout=None):
         assert len(ipaddrs) > 0, ipaddrs
@@ -833,17 +1186,17 @@
     def set_link_quality(self, addr, lqi):
         cmd = 'macfilter rss add-lqi %s %s' % (addr, lqi)
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def set_outbound_link_quality(self, lqi):
         cmd = 'macfilter rss add-lqi * %s' % (lqi)
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def remove_allowlist(self, addr):
         cmd = 'macfilter addr remove %s' % addr
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def get_addr16(self):
         self.send_command('rloc16')
@@ -863,7 +1216,7 @@
         assert len(addr64) == 16
         int(addr64, 16)
         self.send_command('extaddr %s' % addr64)
-        self._expect('Done')
+        self._expect_done()
 
     def get_eui64(self):
         self.send_command('eui64')
@@ -871,7 +1224,7 @@
 
     def set_extpanid(self, extpanid):
         self.send_command('extpanid %s' % extpanid)
-        self._expect('Done')
+        self._expect_done()
 
     def get_joiner_id(self):
         self.send_command('joiner id')
@@ -884,7 +1237,7 @@
     def set_channel(self, channel):
         cmd = 'channel %d' % channel
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def get_masterkey(self):
         self.send_command('masterkey')
@@ -893,7 +1246,7 @@
     def set_masterkey(self, masterkey):
         cmd = 'masterkey %s' % masterkey
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def get_key_sequence_counter(self):
         self.send_command('keysequence counter')
@@ -903,17 +1256,17 @@
     def set_key_sequence_counter(self, key_sequence_counter):
         cmd = 'keysequence counter %d' % key_sequence_counter
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def set_key_switch_guardtime(self, key_switch_guardtime):
         cmd = 'keysequence guardtime %d' % key_switch_guardtime
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def set_network_id_timeout(self, network_id_timeout):
         cmd = 'networkidtimeout %d' % network_id_timeout
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def _escape_escapable(self, string):
         """Escape CLI escapable characters in the given string.
@@ -936,7 +1289,7 @@
     def set_network_name(self, network_name):
         cmd = 'networkname %s' % self._escape_escapable(network_name)
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def get_panid(self):
         self.send_command('panid')
@@ -946,12 +1299,12 @@
     def set_panid(self, panid=config.PANID):
         cmd = 'panid %d' % panid
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def set_parent_priority(self, priority):
         cmd = 'parentpriority %d' % priority
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def get_preferred_partition_id(self):
         self.send_command('partitionid preferred')
@@ -960,7 +1313,7 @@
     def set_preferred_partition_id(self, partition_id):
         cmd = 'partitionid preferred %d' % partition_id
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def get_pollperiod(self):
         self.send_command('pollperiod')
@@ -968,41 +1321,41 @@
 
     def set_pollperiod(self, pollperiod):
         self.send_command('pollperiod %d' % pollperiod)
-        self._expect('Done')
+        self._expect_done()
 
     def get_csl_info(self):
         self.send_command('csl')
-        self._expect('Done')
+        self._expect_done()
 
     def set_csl_channel(self, csl_channel):
         self.send_command('csl channel %d' % csl_channel)
-        self._expect('Done')
+        self._expect_done()
 
     def set_csl_period(self, csl_period):
         self.send_command('csl period %d' % csl_period)
-        self._expect('Done')
+        self._expect_done()
 
     def set_csl_timeout(self, csl_timeout):
         self.send_command('csl timeout %d' % csl_timeout)
-        self._expect('Done')
+        self._expect_done()
 
     def send_mac_emptydata(self):
         self.send_command('mac send emptydata')
-        self._expect('Done')
+        self._expect_done()
 
     def send_mac_datarequest(self):
         self.send_command('mac send datarequest')
-        self._expect('Done')
+        self._expect_done()
 
     def set_router_upgrade_threshold(self, threshold):
         cmd = 'routerupgradethreshold %d' % threshold
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def set_router_downgrade_threshold(self, threshold):
         cmd = 'routerdowngradethreshold %d' % threshold
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def get_router_downgrade_threshold(self) -> int:
         self.send_command('routerdowngradethreshold')
@@ -1011,7 +1364,7 @@
     def set_router_eligible(self, enable: bool):
         cmd = f'routereligible {"enable" if enable else "disable"}'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def get_router_eligible(self) -> bool:
         states = [r'Disabled', r'Enabled']
@@ -1021,12 +1374,12 @@
     def prefer_router_id(self, router_id):
         cmd = 'preferrouterid %d' % router_id
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def release_router_id(self, router_id):
         cmd = 'releaserouterid %d' % router_id
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def get_state(self):
         states = [r'detached', r'child', r'router', r'leader', r'disabled']
@@ -1036,7 +1389,7 @@
     def set_state(self, state):
         cmd = 'state %s' % state
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def get_timeout(self):
         self.send_command('childtimeout')
@@ -1045,12 +1398,12 @@
     def set_timeout(self, timeout):
         cmd = 'childtimeout %d' % timeout
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def set_max_children(self, number):
         cmd = 'childmax %d' % number
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def get_weight(self):
         self.send_command('leaderweight')
@@ -1059,27 +1412,27 @@
     def set_weight(self, weight):
         cmd = 'leaderweight %d' % weight
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def add_ipaddr(self, ipaddr):
         cmd = 'ipaddr add %s' % ipaddr
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def del_ipaddr(self, ipaddr):
         cmd = 'ipaddr del %s' % ipaddr
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def add_ipmaddr(self, ipmaddr):
         cmd = 'ipmaddr add %s' % ipmaddr
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def del_ipmaddr(self, ipmaddr):
         cmd = 'ipmaddr del %s' % ipmaddr
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def get_addrs(self):
         self.send_command('ipaddr')
@@ -1162,12 +1515,23 @@
             serverData,
         )
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def remove_service(self, enterpriseNumber, serviceData):
         cmd = 'service remove %s %s' % (enterpriseNumber, serviceData)
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
+
+    def __getOmrAddress(self):
+        prefixes = [prefix.split('::')[0] for prefix in self.get_prefixes()]
+        omr_addrs = []
+        for addr in self.get_addrs():
+            for prefix in prefixes:
+                if (addr.startswith(prefix)):
+                    omr_addrs.append(addr)
+                    break
+
+        return omr_addrs
 
     def __getLinkLocalAddress(self):
         for ip6Addr in self.get_addrs():
@@ -1242,6 +1606,8 @@
             return self.__getDua()
         elif address_type == config.ADDRESS_TYPE.BACKBONE_GUA:
             return self._getBackboneGua()
+        elif address_type == config.ADDRESS_TYPE.OMR:
+            return self.__getOmrAddress()
         else:
             return None
 
@@ -1252,17 +1618,62 @@
     def set_context_reuse_delay(self, delay):
         cmd = 'contextreusedelay %d' % delay
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def add_prefix(self, prefix, flags='paosr', prf='med'):
         cmd = 'prefix add %s %s %s' % (prefix, flags, prf)
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def remove_prefix(self, prefix):
         cmd = 'prefix remove %s' % prefix
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
+
+    def enable_br(self):
+        self.send_command('br enable')
+        self._expect_done()
+
+    def disable_br(self):
+        self.send_command('br disable')
+        self._expect_done()
+
+    def get_prefixes(self):
+        return self.get_netdata()['Prefixes']
+
+    def get_routes(self):
+        return self.get_netdata()['Routes']
+
+    def get_services(self):
+        netdata = self.netdata_show()
+        services = []
+        services_section = False
+
+        for line in netdata:
+            if line.startswith('Services:'):
+                services_section = True
+            elif services_section:
+                services.append(line.strip().split(' '))
+        return services
+
+    def netdata_show(self):
+        self.send_command('netdata show')
+        return self._expect_command_output('netdata show')
+
+    def get_netdata(self):
+        raw_netdata = self.netdata_show()
+        netdata = {'Prefixes': [], 'Routes': [], 'Services': []}
+        key_list = ['Prefixes', 'Routes', 'Services']
+        key = None
+
+        for i in range(0, len(raw_netdata)):
+            keys = list(filter(raw_netdata[i].startswith, key_list))
+            if keys != []:
+                key = keys[0]
+            elif key is not None:
+                netdata[key].append(raw_netdata[i])
+
+        return netdata
 
     def add_route(self, prefix, stable=False, prf='med'):
         cmd = 'route add %s ' % prefix
@@ -1270,16 +1681,16 @@
             cmd += 's'
         cmd += ' %s' % prf
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def remove_route(self, prefix):
         cmd = 'route remove %s' % prefix
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def register_netdata(self):
         self.send_command('netdata register')
-        self._expect('Done')
+        self._expect_done()
 
     def send_network_diag_get(self, addr, tlv_types):
         self.send_command('networkdiagnostic get %s %s' % (addr, ' '.join([str(t.value) for t in tlv_types])))
@@ -1290,7 +1701,7 @@
         else:
             timeout = 8
 
-        self._expect('Done', timeout=timeout)
+        self._expect_done(timeout=timeout)
 
     def send_network_diag_reset(self, addr, tlv_types):
         self.send_command('networkdiagnostic reset %s %s' % (addr, ' '.join([str(t.value) for t in tlv_types])))
@@ -1301,7 +1712,7 @@
         else:
             timeout = 8
 
-        self._expect('Done', timeout=timeout)
+        self._expect_done(timeout=timeout)
 
     def energy_scan(self, mask, count, period, scan_duration, ipaddr):
         cmd = 'commissioner energy %d %d %d %d %s' % (
@@ -1375,12 +1786,12 @@
 
     def reset(self):
         self.send_command('reset')
-        time.sleep(0.1)
+        time.sleep(self.RESET_DELAY)
 
     def set_router_selection_jitter(self, jitter):
         cmd = 'routerselectionjitter %d' % jitter
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def set_active_dataset(
         self,
@@ -1392,31 +1803,31 @@
         security_policy=[],
     ):
         self.send_command('dataset clear')
-        self._expect('Done')
+        self._expect_done()
 
         cmd = 'dataset activetimestamp %d' % timestamp
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
         if panid is not None:
             cmd = 'dataset panid %d' % panid
             self.send_command(cmd)
-            self._expect('Done')
+            self._expect_done()
 
         if channel is not None:
             cmd = 'dataset channel %d' % channel
             self.send_command(cmd)
-            self._expect('Done')
+            self._expect_done()
 
         if channel_mask is not None:
             cmd = 'dataset channelmask %d' % channel_mask
             self.send_command(cmd)
-            self._expect('Done')
+            self._expect_done()
 
         if master_key is not None:
             cmd = 'dataset masterkey %s' % master_key
             self.send_command(cmd)
-            self._expect('Done')
+            self._expect_done()
 
         if security_policy and len(security_policy) == 2:
             cmd = 'dataset securitypolicy %s %s' % (
@@ -1424,48 +1835,65 @@
                 security_policy[1],
             )
             self.send_command(cmd)
-            self._expect('Done')
+            self._expect_done()
 
         # Set the meshlocal prefix in config.py
         self.send_command('dataset meshlocalprefix %s' % config.MESH_LOCAL_PREFIX.split('/')[0])
-        self._expect('Done')
+        self._expect_done()
 
         self.send_command('dataset commit active')
-        self._expect('Done')
+        self._expect_done()
 
     def set_pending_dataset(self, pendingtimestamp, activetimestamp, panid=None, channel=None, delay=None):
         self.send_command('dataset clear')
-        self._expect('Done')
+        self._expect_done()
 
         cmd = 'dataset pendingtimestamp %d' % pendingtimestamp
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
         cmd = 'dataset activetimestamp %d' % activetimestamp
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
         if panid is not None:
             cmd = 'dataset panid %d' % panid
             self.send_command(cmd)
-            self._expect('Done')
+            self._expect_done()
 
         if channel is not None:
             cmd = 'dataset channel %d' % channel
             self.send_command(cmd)
-            self._expect('Done')
+            self._expect_done()
 
         if delay is not None:
             cmd = 'dataset delay %d' % delay
             self.send_command(cmd)
-            self._expect('Done')
+            self._expect_done()
 
         # Set the meshlocal prefix in config.py
         self.send_command('dataset meshlocalprefix %s' % config.MESH_LOCAL_PREFIX.split('/')[0])
-        self._expect('Done')
+        self._expect_done()
 
         self.send_command('dataset commit pending')
-        self._expect('Done')
+        self._expect_done()
+
+    def start_dataset_updater(self, panid=None, channel=None):
+        self.send_command('dataset clear')
+        self._expect_done()
+
+        if panid is not None:
+            cmd = 'dataset panid %d' % panid
+            self.send_command(cmd)
+            self._expect_done()
+
+        if channel is not None:
+            cmd = 'dataset channel %d' % channel
+            self.send_command(cmd)
+            self._expect_done()
+
+        self.send_command('dataset updater start')
+        self._expect_done()
 
     def announce_begin(self, mask, count, period, ipaddr):
         cmd = 'commissioner announce %d %d %d %s' % (
@@ -1475,7 +1903,7 @@
             ipaddr,
         )
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def send_mgmt_active_set(
         self,
@@ -1519,7 +1947,7 @@
             cmd += '-x %s ' % binary
 
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def send_mgmt_active_get(self, addr='', tlvs=[]):
         cmd = 'dataset mgmtgetcommand active'
@@ -1534,7 +1962,7 @@
             cmd += tlv_str
 
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def send_mgmt_pending_get(self, addr='', tlvs=[]):
         cmd = 'dataset mgmtgetcommand pending'
@@ -1549,7 +1977,7 @@
             cmd += tlv_str
 
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def send_mgmt_pending_set(
         self,
@@ -1588,7 +2016,7 @@
             cmd += 'networkname %s ' % self._escape_escapable(network_name)
 
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def coap_cancel(self):
         """
@@ -1596,7 +2024,7 @@
         """
         cmd = 'coap cancel'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def coap_delete(self, ipaddr, uri, con=False, payload=None):
         """
@@ -1610,6 +2038,12 @@
         """
         return self._coap_rq('get', ipaddr, uri, con, payload)
 
+    def coap_get_block(self, ipaddr, uri, size=16, count=0):
+        """
+        Send a GET request via CoAP.
+        """
+        return self._coap_rq_block('get', ipaddr, uri, size, count)
+
     def coap_observe(self, ipaddr, uri, con=False, payload=None):
         """
         Send a GET request via CoAP with Observe set.
@@ -1622,12 +2056,24 @@
         """
         return self._coap_rq('post', ipaddr, uri, con, payload)
 
+    def coap_post_block(self, ipaddr, uri, size=16, count=0):
+        """
+        Send a POST request via CoAP.
+        """
+        return self._coap_rq_block('post', ipaddr, uri, size, count)
+
     def coap_put(self, ipaddr, uri, con=False, payload=None):
         """
         Send a PUT request via CoAP.
         """
         return self._coap_rq('put', ipaddr, uri, con, payload)
 
+    def coap_put_block(self, ipaddr, uri, size=16, count=0):
+        """
+        Send a PUT request via CoAP.
+        """
+        return self._coap_rq_block('put', ipaddr, uri, size, count)
+
     def _coap_rq(self, method, ipaddr, uri, con=False, payload=None):
         """
         Issue a GET/POST/PUT/DELETE/GET OBSERVE request.
@@ -1644,6 +2090,20 @@
         self.send_command(cmd)
         return self.coap_wait_response()
 
+    def _coap_rq_block(self, method, ipaddr, uri, size=16, count=0):
+        """
+        Issue a GET/POST/PUT/DELETE/GET OBSERVE BLOCK request.
+        """
+        cmd = 'coap %s %s %s' % (method, ipaddr, uri)
+
+        cmd += ' block-%d' % size
+
+        if count != 0:
+            cmd += ' %d' % count
+
+        self.send_command(cmd)
+        return self.coap_wait_response()
+
     def coap_wait_response(self):
         """
         Wait for a CoAP response, and return it.
@@ -1664,7 +2124,10 @@
             observe = int(observe, base=10)
 
         if payload is not None:
-            payload = binascii.a2b_hex(payload).decode('UTF-8')
+            try:
+                payload = binascii.a2b_hex(payload).decode('UTF-8')
+            except UnicodeDecodeError:
+                pass
 
         # Return the values received
         return dict(source=source, observe=observe, payload=payload)
@@ -1728,6 +2191,14 @@
         """
         cmd = 'coap resource %s' % path
         self.send_command(cmd)
+        self._expect_done()
+
+    def coap_set_resource_path_block(self, path, count=0):
+        """
+        Set the path for the CoAP resource and how many blocks can be received from this resource.
+        """
+        cmd = 'coap resource %s %d' % (path, count)
+        self.send_command(cmd)
         self._expect('Done')
 
     def coap_set_content(self, content):
@@ -1736,7 +2207,7 @@
         """
         cmd = 'coap set %s' % content
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def coap_start(self):
         """
@@ -1744,7 +2215,7 @@
         """
         cmd = 'coap start'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def coap_stop(self):
         """
@@ -1759,30 +2230,30 @@
         else:
             timeout = 5
 
-        self._expect('Done', timeout=timeout)
+        self._expect_done(timeout=timeout)
 
     def coaps_start_psk(self, psk, pskIdentity):
         cmd = 'coaps psk %s %s' % (psk, pskIdentity)
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
         cmd = 'coaps start'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def coaps_start_x509(self):
         cmd = 'coaps x509'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
         cmd = 'coaps start'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def coaps_set_resource_path(self, path):
         cmd = 'coaps resource %s' % path
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def coaps_stop(self):
         cmd = 'coaps stop'
@@ -1794,7 +2265,7 @@
         else:
             timeout = 5
 
-        self._expect('Done', timeout=timeout)
+        self._expect_done(timeout=timeout)
 
     def coaps_connect(self, ipaddr):
         cmd = 'coaps connect %s' % ipaddr
@@ -1811,7 +2282,7 @@
     def coaps_disconnect(self):
         cmd = 'coaps disconnect'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
         self.simulator.go(5)
 
     def coaps_get(self):
@@ -1831,12 +2302,12 @@
         if tlvs_binary is not None:
             cmd += ' -x %s' % tlvs_binary
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def commissioner_mgmtset(self, tlvs_binary):
         cmd = 'commissioner mgmtset -x %s' % tlvs_binary
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def bytes_to_hex_str(self, src):
         return ''.join(format(x, '02x') for x in src)
@@ -1850,22 +2321,22 @@
     def udp_start(self, local_ipaddr, local_port):
         cmd = 'udp open'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
         cmd = 'udp bind %s %s' % (local_ipaddr, local_port)
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def udp_stop(self):
         cmd = 'udp close'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def udp_send(self, bytes, ipaddr, port, success=True):
         cmd = 'udp send %s %d -s %d ' % (ipaddr, port, bytes)
         self.send_command(cmd)
         if success:
-            self._expect('Done')
+            self._expect_done()
         else:
             self._expect('Error')
 
@@ -1875,7 +2346,7 @@
     def set_routereligible(self, enable: bool):
         cmd = f'routereligible {"enable" if enable else "disable"}'
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def router_list(self):
         cmd = 'router list'
@@ -1885,7 +2356,7 @@
         g = self.pexpect.match.groups()
         router_list = g[0].decode('utf8') + ' ' + g[1].decode('utf8')
         router_list = [int(x) for x in router_list.split()]
-        self._expect('Done')
+        self._expect_done()
         return router_list
 
     def router_table(self):
@@ -1908,7 +2379,7 @@
 
             line = line[1:][:-1]
             line = [x.strip() for x in line.split('|')]
-            if len(line) != 8:
+            if len(line) < 9:
                 print("unexpected line %d: %s" % (i, line))
                 continue
 
@@ -1927,6 +2398,7 @@
             lqout = int(line[5])
             age = int(line[6])
             emac = str(line[7])
+            link = int(line[8])
 
             router_table[id] = {
                 'rloc16': rloc16,
@@ -1936,6 +2408,7 @@
                 'lqout': lqout,
                 'age': age,
                 'emac': emac,
+                'link': link,
             }
 
         return router_table
@@ -1943,12 +2416,12 @@
     def link_metrics_query_single_probe(self, dst_addr: str, linkmetrics_flags: str):
         cmd = 'linkmetrics query %s single %s' % (dst_addr, linkmetrics_flags)
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def link_metrics_query_forward_tracking_series(self, dst_addr: str, series_id: int):
         cmd = 'linkmetrics query %s forward %d' % (dst_addr, series_id)
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def link_metrics_mgmt_req_enhanced_ack_based_probing(self,
                                                          dst_addr: str,
@@ -1961,28 +2434,171 @@
         else:
             cmd = cmd + " clear"
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def link_metrics_mgmt_req_forward_tracking_series(self, dst_addr: str, series_id: int, series_flags: str,
                                                       metrics_flags: str):
         cmd = "linkmetrics mgmt %s forward %d %s %s" % (dst_addr, series_id, series_flags, metrics_flags)
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def link_metrics_send_link_probe(self, dst_addr: str, series_id: int, length: int):
         cmd = "linkmetrics probe %s %d %d" % (dst_addr, series_id, length)
         self.send_command(cmd)
-        self._expect('Done')
+        self._expect_done()
 
     def send_address_notification(self, dst: str, target: str, mliid: str):
         cmd = f'fake /a/an {dst} {target} {mliid}'
         self.send_command(cmd)
-        self._expect("Done")
+        self._expect_done()
 
     def send_proactive_backbone_notification(self, target: str, mliid: str, ltt: int):
         cmd = f'fake /b/ba {target} {mliid} {ltt}'
         self.send_command(cmd)
-        self._expect("Done")
+        self._expect_done()
+
+    def dns_resolve(self, hostname, server=None, port=53):
+        cmd = f'dns resolve {hostname}'
+        if server is not None:
+            cmd += f' {server} {port}'
+
+        self.send_command(cmd)
+        self.simulator.go(10)
+        output = self._expect_command_output(cmd)
+        dns_resp = output[0]
+        # example output: "DNS response for host1.default.service.arpa. - fd00:db8:0:0:fd3d:d471:1e8c:b60 TTL:7190 "
+        #                 " fd00:db8:0:0:0:ff:fe00:9000 TTL:7190"
+        addrs = dns_resp.strip().split(' - ')[1].split(' ')
+        ip = [item.strip() for item in addrs[::2]]
+        ttl = [int(item.split('TTL:')[1]) for item in addrs[1::2]]
+
+        return list(zip(ip, ttl))
+
+    def dns_resolve_service(self, instance, service, server=None, port=53):
+        """
+        Resolves the service instance and returns the instance information as a dict.
+
+        Example return value:
+            {
+                'port': 12345,
+                'priority': 0,
+                'weight': 0,
+                'host': 'ins1._ipps._tcp.default.service.arpa.',
+                'address': '2001::1',
+                'txt_data': 'a=00, b=02bb',
+                'srv_ttl': 7100,
+                'txt_ttl': 7100,
+                'aaaa_ttl': 7100,
+            }
+        """
+        cmd = f'dns service {instance} {service}'
+        if server is not None:
+            cmd += f' {server} {port}'
+
+        self.send_command(cmd)
+        self.simulator.go(10)
+        output = self._expect_command_output(cmd)
+
+        # Example output:
+        # DNS service resolution response for ins2 for service _ipps._tcp.default.service.arpa.
+        # Port:22222, Priority:2, Weight:2, TTL:7155
+        # Host:host2.default.service.arpa.
+        # HostAddress:0:0:0:0:0:0:0:0 TTL:0
+        # TXT:[a=00, b=02bb] TTL:7155
+        # Done
+
+        m = re.match(
+            r'.*Port:(\d+), Priority:(\d+), Weight:(\d+), TTL:(\d+)\s+Host:(.*?)\s+HostAddress:(\S+) TTL:(\d+)\s+TXT:\[(.*?)\] TTL:(\d+)',
+            '\r'.join(output))
+        if m:
+            port, priority, weight, srv_ttl, hostname, address, aaaa_ttl, txt_data, txt_ttl = m.groups()
+            return {
+                'port': int(port),
+                'priority': int(priority),
+                'weight': int(weight),
+                'host': hostname,
+                'address': address,
+                'txt_data': txt_data,
+                'srv_ttl': int(srv_ttl),
+                'txt_ttl': int(txt_ttl),
+                'aaaa_ttl': int(aaaa_ttl),
+            }
+        else:
+            raise Exception('dns resolve service failed: %s.%s' % (instance, service))
+
+    @staticmethod
+    def __parse_hex_string(hexstr: str) -> bytes:
+        assert (len(hexstr) % 2 == 0)
+        return bytes(int(hexstr[i:i + 2], 16) for i in range(0, len(hexstr), 2))
+
+    def dns_browse(self, service_name, server=None, port=53):
+        """
+        Browse the service and returns the instances.
+
+        Example return value:
+            {
+                'ins1': {
+                    'port': 12345,
+                    'priority': 1,
+                    'weight': 1,
+                    'host': 'ins1._ipps._tcp.default.service.arpa.',
+                    'address': '2001::1',
+                    'txt_data': 'a=00, b=11cf',
+                    'srv_ttl': 7100,
+                    'txt_ttl': 7100,
+                    'aaaa_ttl': 7100,
+                },
+                'ins2': {
+                    'port': 12345,
+                    'priority': 2,
+                    'weight': 2,
+                    'host': 'ins2._ipps._tcp.default.service.arpa.',
+                    'address': '2001::2',
+                    'txt_data': 'a=01, b=23dd',
+                    'srv_ttl': 7100,
+                    'txt_ttl': 7100,
+                    'aaaa_ttl': 7100,
+                }
+            }
+        """
+        cmd = f'dns browse {service_name}'
+        if server is not None:
+            cmd += f' {server} {port}'
+
+        self.send_command(cmd)
+        self.simulator.go(10)
+        output = '\n'.join(self._expect_command_output(cmd))
+
+        # Example output:
+        # ins2
+        #     Port:22222, Priority:2, Weight:2, TTL:7175
+        #     Host:host2.default.service.arpa.
+        #     HostAddress:fd00:db8:0:0:3205:28dd:5b87:6a63 TTL:7175
+        #     TXT:[a=00, b=11cf] TTL:7175
+        # ins1
+        #     Port:11111, Priority:1, Weight:1, TTL:7170
+        #     Host:host1.default.service.arpa.
+        #     HostAddress:fd00:db8:0:0:39f4:d9:eb4f:778 TTL:7170
+        #     TXT:[a=01, b=23dd] TTL:7170
+        # Done
+
+        result = {}
+        for ins, port, priority, weight, srv_ttl, hostname, address, aaaa_ttl, txt_data, txt_ttl in re.findall(
+                r'(.*?)\s+Port:(\d+), Priority:(\d+), Weight:(\d+), TTL:(\d+)\s*Host:(\S+)\s+HostAddress:(\S+) TTL:(\d+)\s+TXT:\[(.*?)\] TTL:(\d+)',
+                output):
+            result[ins] = {
+                'port': int(port),
+                'priority': int(priority),
+                'weight': int(weight),
+                'host': hostname,
+                'address': address,
+                'txt_data': txt_data,
+                'srv_ttl': int(srv_ttl),
+                'txt_ttl': int(txt_ttl),
+                'aaaa_ttl': int(aaaa_ttl),
+            }
+
+        return result
 
 
 class Node(NodeImpl, OtCli):
@@ -1993,6 +2609,18 @@
     PING_RESPONSE_PATTERN = re.compile(r'\d+ bytes from .*:.*')
     ETH_DEV = config.BACKBONE_IFNAME
 
+    def enable_ether(self):
+        """Enable the ethernet interface.
+        """
+
+        self.bash(f'ifconfig {self.ETH_DEV} up')
+
+    def disable_ether(self):
+        """Disable the ethernet interface.
+        """
+
+        self.bash(f'ifconfig {self.ETH_DEV} down')
+
     def get_ether_addrs(self):
         output = self.bash(f'ip -6 addr list dev {self.ETH_DEV}')
 
@@ -2044,12 +2672,22 @@
         return resp_count
 
     def _getBackboneGua(self) -> Optional[str]:
-        for ip6Addr in self.get_addrs():
-            if re.match(config.BACKBONE_PREFIX_REGEX_PATTERN, ip6Addr, re.I):
-                return ip6Addr
+        for addr in self.get_addrs():
+            if re.match(config.BACKBONE_PREFIX_REGEX_PATTERN, addr, re.I):
+                return addr
 
         return None
 
+    def _getInfraUla(self) -> Optional[str]:
+        """ Returns the ULA addresses autoconfigured on the infra link.
+        """
+        addrs = []
+        for addr in self.get_addrs():
+            if re.match(config.ONLINK_PREFIX_REGEX_PATTERN, addr, re.I):
+                addrs.append(addr)
+
+        return addrs
+
     def ping(self, *args, **kwargs):
         backbone = kwargs.pop('backbone', False)
         if backbone:
@@ -2072,6 +2710,66 @@
                   (self.ETH_DEV, self.ETH_DEV))
         self.bash(f'ip -6 neigh list dev {self.ETH_DEV}')
 
+    def discover_mdns_service(self, instance, name, host_name, timeout=2):
+        """ Discover/resolve the mDNS service on ethernet.
+
+        :param instance: the service instance name.
+        :param name: the service name in format of '<service-name>.<protocol>'.
+        :param host_name: the host name this service points to. The domain
+                          should not be included.
+        :param timeout: timeout value in seconds before returning.
+        :return: a dict of service properties or None.
+
+        The return value is a dict with the same key/values of srp_server_get_service
+        except that we don't have a `deleted` field here.
+        """
+
+        self.bash(f'dns-sd -Z {name} local. > /tmp/{name} 2>&1 &')
+        self.bash(f'dns-sd -G v6 {host_name}.local. > /tmp/{host_name} 2>&1 &')
+        time.sleep(timeout)
+
+        self.bash('pkill dns-sd')
+        addresses = []
+        service = {}
+
+        logging.debug(self.bash(f'cat /tmp/{host_name}'))
+        logging.debug(self.bash(f'cat /tmp/{name}'))
+
+        # example output in the host file:
+        # Timestamp     A/R Flags if Hostname                               Address                                     TTL
+        # 9:38:09.274  Add     23 48 my-host.local.                         2001:0000:0000:0000:0000:0000:0000:0002%<0>  120
+        #
+        for line in self.bash(f'cat /tmp/{host_name}'):
+            elements = line.split()
+            fullname = f'{host_name}.local.'
+            if fullname not in elements:
+                continue
+            addresses.append(elements[elements.index(fullname) + 1].split('%')[0])
+
+        logging.debug(f'addresses of {host_name}: {addresses}')
+
+        # example output of in the service file:
+        # _ipps._tcp                                      PTR     my-service._ipps._tcp
+        # my-service._ipps._tcp                           SRV     0 0 12345 my-host.local. ; Replace with unicast FQDN of target host
+        # my-service._ipps._tcp                           TXT     ""
+        #
+        for line in self.bash(f'cat /tmp/{name}'):
+            elements = line.split()
+            if not elements or elements[0] != f'{instance}.{name}':
+                continue
+            if elements[1] == 'SRV':
+                service['fullname'] = elements[0]
+                service['instance'] = instance
+                service['name'] = name
+                service['priority'] = int(elements[2])
+                service['weight'] = int(elements[3])
+                service['port'] = int(elements[4])
+                service['host_fullname'] = elements[5]
+                assert (service['host_fullname'] == f'{host_name}.local.')
+                service['host'] = host_name
+                service['addresses'] = addresses
+                return service if service['addresses'] else None
+
 
 class OtbrNode(LinuxHost, NodeImpl, OtbrDocker):
     is_otbr = True
@@ -2097,9 +2795,12 @@
         self.name = name or ('Host%d' % nodeid)
         super().__init__(nodeid, **kwargs)
 
-    def start(self):
+    def start(self, start_radvd=True, prefix=config.DOMAIN_PREFIX, slaac=False):
         self._setup_sysctl()
-        self._service_radvd_start()
+        if start_radvd:
+            self._service_radvd_start(prefix, slaac)
+        else:
+            self._service_radvd_stop()
 
     def stop(self):
         self._service_radvd_stop()
@@ -2110,6 +2811,17 @@
     def __repr__(self):
         return f'Host<{self.nodeid}>'
 
+    def get_matched_ula_addresses(self, prefix):
+        """Get the IPv6 addresses that matches given prefix.
+        """
+
+        addrs = []
+        for addr in self.get_ip6_address(config.ADDRESS_TYPE.ONLINK_ULA):
+            if addr.startswith(prefix.split('::')[0]):
+                addrs.append(addr)
+
+        return addrs
+
     def get_ip6_address(self, address_type: config.ADDRESS_TYPE):
         """Get specific type of IPv6 address configured on thread device.
 
@@ -2119,14 +2831,16 @@
         Returns:
             IPv6 address string.
         """
-        assert address_type == config.ADDRESS_TYPE.BACKBONE_GUA
+        assert address_type in [config.ADDRESS_TYPE.BACKBONE_GUA, config.ADDRESS_TYPE.ONLINK_ULA]
 
         if address_type == config.ADDRESS_TYPE.BACKBONE_GUA:
             return self._getBackboneGua()
+        if address_type == config.ADDRESS_TYPE.ONLINK_ULA:
+            return self._getInfraUla()
         else:
             return None
 
-    def _service_radvd_start(self):
+    def _service_radvd_start(self, prefix, slaac):
         self.bash("""cat >/etc/radvd.conf <<EOF
 interface eth0
 {
@@ -2139,12 +2853,12 @@
 	prefix %s
 	{
 		AdvOnLink on;
-		AdvAutonomous off;
+		AdvAutonomous %s;
 		AdvRouterAddr off;
 	};
 };
 EOF
-""" % config.DOMAIN_PREFIX)
+""" % (prefix, 'on' if slaac else 'off'))
         self.bash('service radvd start')
         self.bash('service radvd status')  # Make sure radvd service is running
 
diff --git a/tests/scripts/thread-cert/pcap.py b/tests/scripts/thread-cert/pcap.py
index 905a5dd..267f147 100644
--- a/tests/scripts/thread-cert/pcap.py
+++ b/tests/scripts/thread-cert/pcap.py
@@ -28,19 +28,25 @@
 #
 """ Module to provide codec utilities for .pcap formatters. """
 
+import os
 import struct
 import time
 
-DLT_IEEE802_15_4 = 195
+# https://www.tcpdump.org/linktypes.html
+DLT_IEEE802_15_4_WITHFCS = 195
+DLT_IEEE802_15_4_TAP = 283
 PCAP_MAGIC_NUMBER = 0xA1B2C3D4
 PCAP_VERSION_MAJOR = 2
 PCAP_VERSION_MINOR = 4
 
+PACKET_VERIFICATION = int(os.getenv('PACKET_VERIFICATION', 0))
+
 
 class PcapCodec(object):
     """ Utility class for .pcap formatters. """
 
     def __init__(self, filename):
+        self._dlt = DLT_IEEE802_15_4_WITHFCS if PACKET_VERIFICATION else DLT_IEEE802_15_4_TAP
         self._pcap_file = open('%s.pcap' % filename, 'wb')
         self._pcap_file.write(self.encode_header())
 
@@ -54,14 +60,28 @@
             0,
             0,
             256,
-            DLT_IEEE802_15_4,
+            self._dlt,
         )
 
     def encode_frame(self, frame, sec, usec):
         """ Returns a pcap encapsulation of the given frame. """
+        length = 0
+
+        if self._dlt == DLT_IEEE802_15_4_TAP:
+            # Append TLVs according to 802.15.4 TAP specification:
+            # https://github.com/jkcko/ieee802.15.4-tap
+            pcap_tap_fcs_tlv = struct.pack("<HHL", 0, 1, 1)
+            pcap_tap_channel_tlv = struct.pack("<HHHH", 3, 3, frame[0], 0)
+            length = 4 + len(pcap_tap_fcs_tlv) + len(pcap_tap_channel_tlv)
+            pcap_tap_header = struct.pack("<HH", 0, length)
+
         frame = frame[1:]
-        length = len(frame)
+        length += len(frame)
         pcap_frame = struct.pack("<LLLL", sec, usec, length, length)
+
+        if self._dlt == DLT_IEEE802_15_4_TAP:
+            pcap_frame += pcap_tap_header + pcap_tap_fcs_tlv + pcap_tap_channel_tlv
+
         pcap_frame += frame
         return pcap_frame
 
diff --git a/tests/scripts/thread-cert/pktverify/consts.py b/tests/scripts/thread-cert/pktverify/consts.py
index 31f142c..114440a 100644
--- a/tests/scripts/thread-cert/pktverify/consts.py
+++ b/tests/scripts/thread-cert/pktverify/consts.py
@@ -352,6 +352,8 @@
 CSL_DEFAULT_PERIOD_IN_SECOND = 0.5
 US_PER_TEN_SYMBOLS = 160
 CSL_IE_ID = 0x1a
+CSL_DEFAULT_TIMEOUT = 30
+CSL_DEFAULT_CHANNEL = 12
 
 # Thread Version TLV value
 THREAD_VERSION_1_2 = 3
diff --git a/tests/scripts/thread-cert/simulator.py b/tests/scripts/thread-cert/simulator.py
index 4dbe37e..2505010 100644
--- a/tests/scripts/thread-cert/simulator.py
+++ b/tests/scripts/thread-cert/simulator.py
@@ -144,12 +144,15 @@
 
     BLOCK_TIMEOUT = 10
 
-    RADIO_ONLY = os.getenv('RADIO_DEVICE') is not None
     NCP_SIM = os.getenv('NODE_TYPE', 'sim') == 'ncp-sim'
 
+    _message_factory = None
+
     def __init__(self, use_message_factory=True):
         super(VirtualTime, self).__init__()
         self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2 * 1024 * 1024)
+        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 2 * 1024 * 1024)
 
         ip = '127.0.0.1'
         self.port = self.BASE_PORT + (self.PORT_OFFSET * (self.MAX_NODES + 1))
@@ -242,7 +245,7 @@
         return (addr[0], addr[1] - self.BASE_PORT)
 
     def _core_addr_from(self, nodeid):
-        if self.RADIO_ONLY:
+        if self._nodes[nodeid].is_posix:
             return ('127.0.0.1', self.BASE_PORT + self.port + nodeid)
         else:
             return ('127.0.0.1', self.port + nodeid)
@@ -497,7 +500,7 @@
 
     def go(self, duration, nodeid=None):
         assert self.current_time == self._pause_time
-        duration = int(duration) * 1000000
+        duration = int(duration * 1000000)
         dbg_print('running for %d us' % duration)
         self._pause_time += duration
         if nodeid:
diff --git a/tests/scripts/thread-cert/sniffer_transport.py b/tests/scripts/thread-cert/sniffer_transport.py
index 756c5a4..a45731a 100644
--- a/tests/scripts/thread-cert/sniffer_transport.py
+++ b/tests/scripts/thread-cert/sniffer_transport.py
@@ -128,6 +128,8 @@
 
         self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
         self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
+        self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 2 * 1024 * 1024)
+        self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 2 * 1024 * 1024)
         self._socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
                                 socket.inet_aton(self.RADIO_GROUP) + socket.inet_aton('127.0.0.1'))
         self._socket.bind(self._nodeid_to_address(0))
diff --git a/tests/scripts/thread-cert/test_coap_block.py b/tests/scripts/thread-cert/test_coap_block.py
new file mode 100755
index 0000000..27c98bf
--- /dev/null
+++ b/tests/scripts/thread-cert/test_coap_block.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2020, The OpenThread Authors.
+#  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.
+#
+
+import unittest
+
+import pexpect
+import config
+import thread_cert
+
+LEADER = 1
+ROUTER = 2
+
+
+class TestCoapBlockTransfer(thread_cert.TestCase):
+    """
+    Test suite for CoAP Block-Wise Transfers (RFC7959).
+    """
+
+    SUPPORT_NCP = False
+
+    TOPOLOGY = {
+        LEADER: {
+            'mode': 'rdn',
+            'panid': 0xface,
+            'whitelist': [ROUTER]
+        },
+        ROUTER: {
+            'mode': 'rdn',
+            'panid': 0xface,
+            'router_selection_jitter': 1,
+            'whitelist': [LEADER]
+        },
+    }
+
+    def _do_transfer_test(self, method):
+        self.nodes[LEADER].start()
+        self.simulator.go(5)
+        self.assertEqual(self.nodes[LEADER].get_state(), 'leader')
+
+        self.nodes[ROUTER].start()
+        self.simulator.go(5)
+        self.assertEqual(self.nodes[ROUTER].get_state(), 'router')
+
+        mleid = self.nodes[LEADER].get_ip6_address(config.ADDRESS_TYPE.ML_EID)
+
+        self.nodes[LEADER].coap_start()
+        self.nodes[LEADER].coap_set_resource_path_block('test', 10)
+
+        self.nodes[ROUTER].coap_start()
+
+        if method == 'GET':
+            response = self.nodes[ROUTER].coap_get_block(mleid, 'test', size=32)
+            response_payload = response['payload']
+            self.assertIsNotNone(response_payload)
+
+        if method == 'PUT':
+            self.nodes[ROUTER].coap_put_block(mleid, 'test', size=256, count=2)
+            request = self.nodes[ROUTER].coap_wait_request()
+            request_payload = request['payload']
+            self.assertIsNotNone(request_payload)
+
+        if method == 'POST':
+            self.nodes[ROUTER].coap_post_block(mleid, 'test', size=1024, count=2)
+            request = self.nodes[ROUTER].coap_wait_request()
+            request_payload = request['payload']
+            self.assertIsNotNone(request_payload)
+
+        self.simulator.go(10)
+
+        self.nodes[ROUTER].coap_stop()
+        self.nodes[LEADER].coap_stop()
+
+    def test_get(self):
+        """
+        Test block-wise transfer using GET method.
+        """
+        for trial in range(0, 3):
+            try:
+                self._do_transfer_test(method='GET')
+                break
+            except (AssertionError, pexpect.exceptions.TIMEOUT):
+                continue
+
+    def test_put(self):
+        """
+        Test block-wise transfer using PUT method.
+        """
+        for trial in range(0, 3):
+            try:
+                self._do_transfer_test(method='PUT')
+                break
+            except (AssertionError, pexpect.exceptions.TIMEOUT):
+                continue
+
+    def test_post(self):
+        """
+        Test block-wise transfer using POST method.
+        """
+        for trial in range(0, 3):
+            try:
+                self._do_transfer_test(method='POST')
+                break
+            except (AssertionError, pexpect.exceptions.TIMEOUT):
+                continue
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/scripts/thread-cert/test_dataset_updater.py b/tests/scripts/thread-cert/test_dataset_updater.py
new file mode 100755
index 0000000..10a8527
--- /dev/null
+++ b/tests/scripts/thread-cert/test_dataset_updater.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+import unittest
+
+import config
+import thread_cert
+
+LEADER = 1
+ROUTER = 2
+MED = 3
+SED = 4
+
+
+class TestDatasetUpdater(thread_cert.TestCase):
+    SUPPORT_NCP = False
+    USE_MESSAGE_FACTORY = False
+
+    TOPOLOGY = {
+        LEADER: {
+            'mode': 'rdn',
+            'channel': 11,
+            'panid': 0xface,
+        },
+        ROUTER: {
+            'mode': 'rdn',
+            'channel': 11,
+            'panid': 0xface,
+            'router_selection_jitter': 1,
+        },
+        MED: {
+            'mode': 'rn',
+            'channel': 11,
+            'panid': 0xface,
+            'allowlist': [ROUTER],
+        },
+        SED: {
+            'mode': '-',
+            'channel': 11,
+            'panid': 0xface,
+            'timeout': config.DEFAULT_CHILD_TIMEOUT,
+            'allowlist': [ROUTER],
+        },
+    }
+
+    def test(self):
+        self.nodes[LEADER].start()
+        self.simulator.go(5)
+        self.assertEqual(self.nodes[LEADER].get_state(), 'leader')
+
+        self.nodes[ROUTER].start()
+        self.simulator.go(5)
+        self.assertEqual(self.nodes[ROUTER].get_state(), 'router')
+
+        self.nodes[MED].start()
+        self.simulator.go(5)
+        self.assertEqual(self.nodes[MED].get_state(), 'child')
+
+        self.nodes[SED].start()
+        self.simulator.go(5)
+        self.assertEqual(self.nodes[SED].get_state(), 'child')
+
+        self.verify_state(11)
+
+        # update initiated by LEADER
+        self.nodes[LEADER].start_dataset_updater(channel=12)
+        self.simulator.go(120)
+        self.verify_state(12)
+
+        # update initiated by ROUTER
+        self.nodes[ROUTER].start_dataset_updater(channel=13)
+        self.simulator.go(120)
+        self.verify_state(13)
+
+        # update initiated by LEADER overridden by ROUTER
+        self.nodes[LEADER].start_dataset_updater(channel=14)
+        self.simulator.go(20)
+        self.nodes[ROUTER].start_dataset_updater(channel=15)
+        self.simulator.go(120)
+        self.verify_state(15)
+
+        # update initiated by ROUTER overridden by LEADER
+        self.nodes[ROUTER].start_dataset_updater(channel=16)
+        self.simulator.go(10)
+        self.nodes[LEADER].start_dataset_updater(channel=17)
+        self.simulator.go(120)
+        self.verify_state(17)
+
+    def verify_state(self, channel):
+        self.assertEqual(self.nodes[LEADER].get_channel(), channel)
+        self.assertEqual(self.nodes[ROUTER].get_channel(), channel)
+        self.assertEqual(self.nodes[MED].get_channel(), channel)
+        self.assertEqual(self.nodes[SED].get_channel(), channel)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/scripts/thread-cert/test_dnssd.py b/tests/scripts/thread-cert/test_dnssd.py
new file mode 100755
index 0000000..d4b80d6
--- /dev/null
+++ b/tests/scripts/thread-cert/test_dnssd.py
@@ -0,0 +1,169 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+import ipaddress
+import typing
+import unittest
+
+import thread_cert
+
+SERVER = 1
+CLIENT1 = 2
+CLIENT2 = 3
+
+DOMAIN = 'default.service.arpa.'
+SERVICE = '_ipps._tcp'
+
+#
+# Topology:
+# LEADER -- CLIENT1
+#   |
+# CLIENT2
+#
+
+
+class TestDnssd(thread_cert.TestCase):
+    SUPPORT_NCP = False
+    USE_MESSAGE_FACTORY = False
+
+    TOPOLOGY = {
+        SERVER: {
+            'mode': 'rdn',
+            'panid': 0xface,
+        },
+        CLIENT1: {
+            'mode': 'rdn',
+            'panid': 0xface,
+            'router_selection_jitter': 1,
+        },
+        CLIENT2: {
+            'mode': 'rdn',
+            'panid': 0xface,
+            'router_selection_jitter': 1,
+        },
+    }
+
+    def test(self):
+        self.nodes[SERVER].start()
+        self.simulator.go(5)
+        self.assertEqual(self.nodes[SERVER].get_state(), 'leader')
+        self.nodes[SERVER].srp_server_set_enabled(True)
+
+        self.nodes[CLIENT1].start()
+        self.simulator.go(5)
+        self.assertEqual(self.nodes[CLIENT1].get_state(), 'router')
+
+        self.nodes[CLIENT2].start()
+        self.simulator.go(5)
+        self.assertEqual(self.nodes[CLIENT1].get_state(), 'router')
+
+        client1_addrs = [self.nodes[CLIENT1].get_mleid(), self.nodes[CLIENT1].get_rloc()]
+        self._config_srp_client_services(CLIENT1, 'ins1', 'host1', 11111, 1, 1, client1_addrs)
+
+        client2_addrs = [self.nodes[CLIENT2].get_mleid(), self.nodes[CLIENT2].get_rloc()]
+        self._config_srp_client_services(CLIENT2, 'ins2', 'host2', 22222, 2, 2, client2_addrs)
+
+        # Test AAAA query using DNS client
+        answers = self.nodes[CLIENT1].dns_resolve(f"host1.{DOMAIN}", self.nodes[SERVER].get_mleid(), 53)
+        self.assertEqual(set(ipaddress.IPv6Address(ip) for ip, _ in answers),
+                         set(map(ipaddress.IPv6Address, client1_addrs)))
+
+        answers = self.nodes[CLIENT1].dns_resolve(f"host2.{DOMAIN}", self.nodes[SERVER].get_mleid(), 53)
+        self.assertEqual(set(ipaddress.IPv6Address(ip) for ip, _ in answers),
+                         set(map(ipaddress.IPv6Address, client2_addrs)))
+
+        service_instances = self.nodes[CLIENT1].dns_browse(f'{SERVICE}.{DOMAIN}', self.nodes[SERVER].get_mleid(), 53)
+        self.assertEqual({'ins1', 'ins2'}, set(service_instances.keys()), service_instances)
+
+        instance1_verify_info = {
+            'port': 11111,
+            'priority': 1,
+            'weight': 1,
+            'host': 'host1.default.service.arpa.',
+            'address': client1_addrs,
+            'txt_data': '',
+            'srv_ttl': lambda x: x > 0,
+            'txt_ttl': lambda x: x > 0,
+            'aaaa_ttl': lambda x: x > 0,
+        }
+
+        instance2_verify_info = {
+            'port': 22222,
+            'priority': 2,
+            'weight': 2,
+            'host': 'host2.default.service.arpa.',
+            'address': client2_addrs,
+            'txt_data': '',
+            'srv_ttl': lambda x: x > 0,
+            'txt_ttl': lambda x: x > 0,
+            'aaaa_ttl': lambda x: x > 0,
+        }
+
+        self._assert_service_instance_equal(service_instances['ins1'], instance1_verify_info)
+        self._assert_service_instance_equal(service_instances['ins2'], instance2_verify_info)
+
+        service_instance = self.nodes[CLIENT1].dns_resolve_service('ins1', f'{SERVICE}.{DOMAIN}',
+                                                                   self.nodes[SERVER].get_mleid(), 53)
+        self._assert_service_instance_equal(service_instance, instance1_verify_info)
+
+        service_instance = self.nodes[CLIENT1].dns_resolve_service('ins2', f'{SERVICE}.{DOMAIN}',
+                                                                   self.nodes[SERVER].get_mleid(), 53)
+        self._assert_service_instance_equal(service_instance, instance2_verify_info)
+
+    def _assert_service_instance_equal(self, instance, info):
+        for f in ('port', 'priority', 'weight', 'host', 'txt_data'):
+            self.assertEqual(instance[f], info[f], instance)
+
+        verify_addresses = info['address']
+        if not isinstance(verify_addresses, typing.Collection):
+            verify_addresses = [verify_addresses]
+        self.assertIn(ipaddress.IPv6Address(instance['address']), map(ipaddress.IPv6Address, verify_addresses),
+                      instance)
+
+        for ttl_f in ('srv_ttl', 'txt_ttl', 'aaaa_ttl'):
+            check_ttl = info[ttl_f]
+            if not callable(check_ttl):
+                check_ttl = lambda x: x == check_ttl
+
+            self.assertTrue(check_ttl(instance[ttl_f]), instance)
+
+    def _config_srp_client_services(self, client, instancename, hostname, port, priority, weight, addrs):
+        self.nodes[client].netdata_show()
+        srp_server_port = self.nodes[client].get_srp_server_port()
+
+        self.nodes[client].srp_client_start(self.nodes[SERVER].get_mleid(), srp_server_port)
+        self.nodes[client].srp_client_set_host_name(hostname)
+        self.nodes[client].srp_client_set_host_address(*addrs)
+        self.nodes[client].srp_client_add_service(instancename, SERVICE, port, priority, weight)
+
+        self.simulator.go(5)
+        self.assertEqual(self.nodes[client].srp_client_get_host_state(), 'Registered')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/scripts/thread-cert/test_reset.py b/tests/scripts/thread-cert/test_reset.py
index 2265279..155c97a 100755
--- a/tests/scripts/thread-cert/test_reset.py
+++ b/tests/scripts/thread-cert/test_reset.py
@@ -27,6 +27,7 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
+import config
 import unittest
 import thread_cert
 
@@ -70,39 +71,27 @@
         self.simulator.go(7)
         self.assertEqual(self.nodes[ED].get_state(), 'child')
 
-        leader_addrs = self.nodes[LEADER].get_addrs()
-        router_addrs = self.nodes[ROUTER].get_addrs()
-
         for i in range(0, 1010):
-            self.assertTrue(self.nodes[ED].ping(leader_addrs[0]))
+            self.assertTrue(self.nodes[ED].ping(self.nodes[LEADER].get_ip6_address(config.ADDRESS_TYPE.RLOC)))
         self.simulator.go(1)
 
-        # 1 - Leader
         self.nodes[LEADER].reset()
         self.nodes[LEADER].start()
         self.simulator.go(7)
         self.assertEqual(self.nodes[LEADER].get_state(), 'leader')
 
-        for addr in router_addrs:
-            self.assertTrue(self.nodes[LEADER].ping(addr))
-
-        # 2 - Router
         self.nodes[ROUTER].reset()
         self.nodes[ROUTER].start()
         self.simulator.go(7)
         self.assertEqual(self.nodes[ROUTER].get_state(), 'router')
 
-        for addr in leader_addrs:
-            self.assertTrue(self.nodes[ROUTER].ping(addr))
-
-        # 3 - Child
         self.nodes[ED].reset()
+        self.nodes[LEADER].add_allowlist(self.nodes[ROUTER].get_addr64())
+        self.nodes[LEADER].enable_allowlist()
         self.nodes[ED].start()
         self.simulator.go(7)
         self.assertEqual(self.nodes[ED].get_state(), 'child')
-
-        for addr in router_addrs:
-            self.assertTrue(self.nodes[ED].ping(addr))
+        self.assertTrue(self.nodes[ED].ping(self.nodes[LEADER].get_ip6_address(config.ADDRESS_TYPE.RLOC)))
 
 
 if __name__ == '__main__':
diff --git a/tests/scripts/thread-cert/test_srp_auto_start_mode.py b/tests/scripts/thread-cert/test_srp_auto_start_mode.py
new file mode 100755
index 0000000..1a28397
--- /dev/null
+++ b/tests/scripts/thread-cert/test_srp_auto_start_mode.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+import ipaddress
+import unittest
+
+import command
+import thread_cert
+
+# Test description:
+#   This test verifies SRP client auto-start functionality that SRP client can
+#   correctly discover and connect to SRP server.
+#
+# Topology:
+#
+#   CLIENT (leader) -- SERVER1 (router)
+#      |
+#      |
+#   SERVER2 (router)
+#
+
+CLIENT = 1
+SERVER1 = 2
+SERVER2 = 3
+
+
+class SrpAutoStartMode(thread_cert.TestCase):
+    USE_MESSAGE_FACTORY = False
+    SUPPORT_NCP = False
+
+    TOPOLOGY = {
+        CLIENT: {
+            'name': 'SRP_CLIENT',
+            'masterkey': '00112233445566778899aabbccddeeff',
+            'mode': 'rdn',
+            'panid': 0xface
+        },
+        SERVER1: {
+            'name': 'SRP_SERVER1',
+            'masterkey': '00112233445566778899aabbccddeeff',
+            'mode': 'rdn',
+            'panid': 0xface,
+            'router_selection_jitter': 1
+        },
+        SERVER2: {
+            'name': 'SRP_SERVER2',
+            'masterkey': '00112233445566778899aabbccddeeff',
+            'mode': 'rdn',
+            'panid': 0xface,
+            'router_selection_jitter': 1
+        },
+    }
+
+    def test(self):
+        client = self.nodes[CLIENT]
+        server1 = self.nodes[SERVER1]
+        server2 = self.nodes[SERVER2]
+
+        #
+        # 0. Start the server & client devices.
+        #
+
+        client.srp_server_set_enabled(False)
+        client.start()
+        self.simulator.go(5)
+        self.assertEqual(client.get_state(), 'leader')
+
+        server1.srp_server_set_enabled(True)
+        server2.srp_server_set_enabled(False)
+        server1.start()
+        server2.start()
+        self.simulator.go(5)
+        self.assertEqual(server1.get_state(), 'router')
+        self.assertEqual(server2.get_state(), 'router')
+
+        #
+        # 1. Enable auto start mode on client and check that server1 is used.
+        #
+
+        self.assertEqual(client.srp_client_get_state(), 'Disabled')
+        client.srp_client_enable_auto_start_mode()
+        self.assertEqual(client.srp_client_get_auto_start_mode(), 'Enabled')
+        self.simulator.go(2)
+
+        self.assertEqual(client.srp_client_get_state(), 'Enabled')
+        self.assertTrue(server1.has_ipaddr(client.srp_client_get_server_address()))
+
+        #
+        # 2. Disable server1 and check client is stopped/disabled.
+        #
+
+        server1.srp_server_set_enabled(False)
+        self.simulator.go(5)
+        self.assertEqual(client.srp_client_get_state(), 'Disabled')
+
+        #
+        # 3. Enable server2 and check client starts again.
+        #
+
+        server2.srp_server_set_enabled(True)
+        self.simulator.go(5)
+        self.assertEqual(client.srp_client_get_state(), 'Enabled')
+        server2_address = client.srp_client_get_server_address()
+
+        #
+        # 4. Enable both servers and check client stays with server2.
+        #
+
+        server1.srp_server_set_enabled(True)
+        self.simulator.go(2)
+        self.assertEqual(client.srp_client_get_state(), 'Enabled')
+        self.assertEqual(client.srp_client_get_server_address(), server2_address)
+
+        #
+        # 5. Disable server2 and check client switches to server1.
+        #
+
+        server2.srp_server_set_enabled(False)
+        self.simulator.go(5)
+        self.assertEqual(client.srp_client_get_state(), 'Enabled')
+        self.assertNotEqual(client.srp_client_get_server_address(), server2_address)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/scripts/thread-cert/test_srp_lease.py b/tests/scripts/thread-cert/test_srp_lease.py
new file mode 100755
index 0000000..baf45af
--- /dev/null
+++ b/tests/scripts/thread-cert/test_srp_lease.py
@@ -0,0 +1,191 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+import ipaddress
+import unittest
+
+import command
+import thread_cert
+
+# Test description:
+#   This test verifies the SRP server and client properly handle SRP host
+#   and service instance lease.
+#
+# Topology:
+#     LEADER (SRP server)
+#       |
+#       |
+#     ROUTER (SRP client)
+#
+
+SERVER = 1
+CLIENT = 2
+LEASE = 10  # Seconds
+KEY_LEASE = 20  # Seconds
+
+
+class SrpRegisterSingleService(thread_cert.TestCase):
+    USE_MESSAGE_FACTORY = False
+    SUPPORT_NCP = False
+
+    TOPOLOGY = {
+        SERVER: {
+            'name': 'SRP_SERVER',
+            'masterkey': '00112233445566778899aabbccddeeff',
+            'mode': 'rdn',
+            'panid': 0xface
+        },
+        CLIENT: {
+            'name': 'SRP_CLIENT',
+            'masterkey': '00112233445566778899aabbccddeeff',
+            'mode': 'rdn',
+            'panid': 0xface,
+            'router_selection_jitter': 1
+        },
+    }
+
+    def test(self):
+        server = self.nodes[SERVER]
+        client = self.nodes[CLIENT]
+
+        #
+        # 0. Start the server and client devices.
+        #
+
+        server.srp_server_set_enabled(True)
+        server.srp_server_set_lease_range(LEASE, LEASE, KEY_LEASE, KEY_LEASE)
+        server.start()
+        self.simulator.go(5)
+        self.assertEqual(server.get_state(), 'leader')
+        self.simulator.go(5)
+
+        client.srp_server_set_enabled(False)
+        client.start()
+        self.simulator.go(5)
+        self.assertEqual(client.get_state(), 'router')
+
+        #
+        # 1. Register a single service and verify that it works.
+        #
+
+        client.srp_client_set_host_name('my-host')
+        client.srp_client_set_host_address('2001::1')
+        client.srp_client_start(server.get_addrs()[0], client.get_srp_server_port())
+        client.srp_client_add_service('my-service', '_ipps._tcp', 12345)
+        self.simulator.go(2)
+
+        self.check_host_and_service(server, client)
+
+        #
+        # 2. Stop the client and wait for the service instance LEASE to expire.
+        #
+
+        client.srp_client_stop()
+        self.simulator.go(LEASE + 1)
+
+        # The SRP server should remove the host and service but retain their names
+        # since the the KEY LEASE hasn't expired yet.
+        self.assertEqual(server.srp_server_get_host('my-host')['deleted'], 'true')
+        self.assertEqual(server.srp_server_get_service('my-service', '_ipps._tcp')['deleted'], 'true')
+
+        # Start the client again, the same service should be successfully registered.
+        client.srp_client_start(server.get_addrs()[0], client.get_srp_server_port())
+        self.simulator.go(2)
+
+        self.check_host_and_service(server, client)
+
+        #
+        # 3. Stop the client and wait for the KEY LEASE to expire.
+        #    The host and service instance should be fully removed by the SRP server.
+        #
+
+        client.srp_client_stop()
+        self.simulator.go(KEY_LEASE + 1)
+
+        # The host and service are expected to be fully removed.
+        self.assertEqual(len(server.srp_server_get_hosts()), 0)
+        self.assertEqual(len(server.srp_server_get_services()), 0)
+
+        # Start the client again, the same service should be successfully registered.
+        client.srp_client_start(server.get_addrs()[0], client.get_srp_server_port())
+        self.simulator.go(2)
+
+        self.check_host_and_service(server, client)
+
+    def check_host_and_service(self, server, client):
+        """Check that we have properly registered host and service instance.
+        """
+
+        client_services = client.srp_client_get_services()
+        print(client_services)
+        self.assertEqual(len(client_services), 1)
+        client_service = client_services[0]
+
+        # Verify that the client possesses correct service resources.
+        self.assertEqual(client_service['instance'], 'my-service')
+        self.assertEqual(client_service['name'], '_ipps._tcp')
+        self.assertEqual(int(client_service['port']), 12345)
+        self.assertEqual(int(client_service['priority']), 0)
+        self.assertEqual(int(client_service['weight']), 0)
+
+        # Verify that the client received a SUCCESS response for the server.
+        self.assertEqual(client_service['state'], 'Registered')
+
+        # Wait for a KEY LEASE period to make sure that the client has refreshed
+        # the host and service instance.
+        self.simulator.go(KEY_LEASE + 1)
+
+        server_services = server.srp_server_get_services()
+        print(server_services)
+        self.assertEqual(len(server_services), 1)
+        server_service = server_services[0]
+
+        # Verify that the server accepted the SRP registration and stored
+        # the same service resources.
+        self.assertEqual(server_service['deleted'], 'false')
+        self.assertEqual(server_service['instance'], client_service['instance'])
+        self.assertEqual(server_service['name'], client_service['name'])
+        self.assertEqual(int(server_service['port']), int(client_service['port']))
+        self.assertEqual(int(server_service['priority']), int(client_service['priority']))
+        self.assertEqual(int(server_service['weight']), int(client_service['weight']))
+        self.assertEqual(server_service['host'], 'my-host')
+
+        server_hosts = server.srp_server_get_hosts()
+        print(server_hosts)
+        self.assertEqual(len(server_hosts), 1)
+        server_host = server_hosts[0]
+
+        self.assertEqual(server_host['deleted'], 'false')
+        self.assertEqual(server_host['fullname'], server_service['host_fullname'])
+        self.assertEqual(len(server_host['addresses']), 1)
+        self.assertEqual(ipaddress.ip_address(server_host['addresses'][0]), ipaddress.ip_address('2001::1'))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/scripts/thread-cert/test_srp_name_conflicts.py b/tests/scripts/thread-cert/test_srp_name_conflicts.py
new file mode 100755
index 0000000..1e28518
--- /dev/null
+++ b/tests/scripts/thread-cert/test_srp_name_conflicts.py
@@ -0,0 +1,279 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+import ipaddress
+import unittest
+
+import command
+import thread_cert
+
+# Test description:
+#   This test verifies if the SRP server can handle name conflicts correctly.
+#
+# Topology:
+#            LEADER (SRP server)
+#           /      \
+#          /        \
+#         /          \
+#     ROUTER1      ROUTER2
+#
+
+SERVER = 1
+CLIENT1 = 2
+CLIENT2 = 3
+
+
+class SrpNameConflicts(thread_cert.TestCase):
+    USE_MESSAGE_FACTORY = False
+    SUPPORT_NCP = False
+
+    TOPOLOGY = {
+        SERVER: {
+            'name': 'SRP_SERVER',
+            'masterkey': '00112233445566778899aabbccddeeff',
+            'mode': 'rdn',
+            'panid': 0xface
+        },
+        CLIENT1: {
+            'name': 'SRP_CLIENT1',
+            'masterkey': '00112233445566778899aabbccddeeff',
+            'mode': 'rdn',
+            'panid': 0xface,
+            'router_selection_jitter': 1
+        },
+        CLIENT2: {
+            'name': 'SRP_CLIENT2',
+            'masterkey': '00112233445566778899aabbccddeeff',
+            'mode': 'rdn',
+            'panid': 0xface,
+            'router_selection_jitter': 1
+        },
+    }
+
+    def test(self):
+        server = self.nodes[SERVER]
+        client_1 = self.nodes[CLIENT1]
+        client_2 = self.nodes[CLIENT2]
+
+        #
+        # 0. Start the server & client devices.
+        #
+
+        server.srp_server_set_enabled(True)
+        server.start()
+        self.simulator.go(5)
+        self.assertEqual(server.get_state(), 'leader')
+        self.simulator.go(5)
+
+        client_1.srp_server_set_enabled(False)
+        client_1.start()
+        self.simulator.go(5)
+        self.assertEqual(client_1.get_state(), 'router')
+
+        client_2.srp_server_set_enabled(False)
+        client_2.start()
+        self.simulator.go(5)
+        self.assertEqual(client_2.get_state(), 'router')
+
+        #
+        # 1. Register a single service and verify that it works.
+        #
+
+        client_1.srp_client_set_host_name('my-host-1')
+        client_1.srp_client_set_host_address('2001::1')
+        client_1.srp_client_start(server.get_addrs()[0], client_1.get_srp_server_port())
+        client_1.srp_client_add_service('my-service-1', '_ipps._tcp', 12345)
+        self.simulator.go(2)
+
+        # Verify that the client possesses correct service resources.
+        client_1_service = client_1.srp_client_get_services()[0]
+        self.assertEqual(client_1_service['instance'], 'my-service-1')
+        self.assertEqual(client_1_service['name'], '_ipps._tcp')
+        self.assertEqual(int(client_1_service['port']), 12345)
+        self.assertEqual(int(client_1_service['priority']), 0)
+        self.assertEqual(int(client_1_service['weight']), 0)
+
+        # Verify that the client receives a SUCCESS response for the server.
+        self.assertEqual(client_1_service['state'], 'Registered')
+
+        # Verify that the server accepts the SRP registration and stored
+        # the same service resources.
+        server_service = server.srp_server_get_services()[0]
+        self.assertEqual(server_service['deleted'], 'false')
+        self.assertEqual(server_service['instance'], client_1_service['instance'])
+        self.assertEqual(server_service['name'], client_1_service['name'])
+        self.assertEqual(int(server_service['port']), int(client_1_service['port']))
+        self.assertEqual(int(server_service['priority']), int(client_1_service['priority']))
+        self.assertEqual(int(server_service['weight']), int(client_1_service['weight']))
+        self.assertEqual(server_service['host'], 'my-host-1')
+
+        server_host = server.srp_server_get_hosts()[0]
+        self.assertEqual(server_host['deleted'], 'false')
+        self.assertEqual(server_host['fullname'], server_service['host_fullname'])
+        self.assertEqual(len(server_host['addresses']), 1)
+        self.assertEqual(ipaddress.ip_address(server_host['addresses'][0]), ipaddress.ip_address('2001::1'))
+
+        #
+        # 2. Register with the same host name from the second client and it should fail.
+        #
+
+        client_2.srp_client_set_host_name('my-host-1')
+        client_2.srp_client_set_host_address('2001::2')
+        client_2.srp_client_start(server.get_addrs()[0], client_2.get_srp_server_port())
+        client_2.srp_client_add_service('my-service-2', '_ipps._tcp', 12345)
+        self.simulator.go(2)
+
+        # It is expected that the registration will be rejected.
+        client_2_service = client_2.srp_client_get_services()[0]
+        self.assertNotEqual(client_2_service['state'], 'Registered')
+        self.assertNotEqual(client_2.srp_client_get_host_state(), 'Registered')
+
+        self.assertEqual(len(server.srp_server_get_services()), 1)
+        self.assertEqual(len(server.srp_server_get_hosts()), 1)
+
+        client_2.srp_client_clear_host()
+        client_2.srp_client_stop()
+
+        #
+        # 3. Register with the same service name from the second client and it should fail.
+        #
+
+        client_2.srp_client_set_host_name('my-host-2')
+        client_2.srp_client_set_host_address('2001::2')
+        client_2.srp_client_start(server.get_addrs()[0], client_2.get_srp_server_port())
+        client_2.srp_client_add_service('my-service-1', '_ipps._tcp', 12345)
+        self.simulator.go(2)
+
+        # It is expected that the registration will be rejected.
+        client_2_service = client_2.srp_client_get_services()[0]
+        self.assertNotEqual(client_2_service['state'], 'Registered')
+        self.assertNotEqual(client_2.srp_client_get_host_state(), 'Registered')
+
+        self.assertEqual(len(server.srp_server_get_services()), 1)
+        self.assertEqual(len(server.srp_server_get_hosts()), 1)
+
+        client_2.srp_client_clear_host()
+        client_2.srp_client_stop()
+
+        #
+        # 4. Register with different host & service instance name, it should succeed.
+        #
+
+        client_2.srp_client_set_host_name('my-host-2')
+        client_2.srp_client_set_host_address('2001::2')
+        client_2.srp_client_start(server.get_addrs()[0], client_2.get_srp_server_port())
+        client_2.srp_client_add_service('my-service-2', '_ipps._tcp', 12345)
+        self.simulator.go(2)
+
+        # It is expected that the registration will be accepted.
+        client_2_service = client_2.srp_client_get_services()[0]
+        self.assertEqual(client_2_service['state'], 'Registered')
+        self.assertEqual(client_2.srp_client_get_host_state(), 'Registered')
+
+        self.assertEqual(len(server.srp_server_get_services()), 2)
+        self.assertEqual(len(server.srp_server_get_hosts()), 2)
+        self.assertEqual(server.srp_server_get_host('my-host-2')['deleted'], 'false')
+        self.assertEqual(server.srp_server_get_service('my-service-2', '_ipps._tcp')['deleted'], 'false')
+
+        # Remove the host and all services registered on the SRP server.
+        client_2.srp_client_remove_host(remove_key=True)
+        self.simulator.go(2)
+
+        client_2.srp_client_clear_host()
+        client_2.srp_client_stop()
+
+        #
+        # 5. Register with the same service instance name before its KEY LEASE expires,
+        #    it is expected to fail.
+        #
+
+        # Remove the service instance from SRP server but retains its name.
+        client_1.srp_client_remove_service('my-service-1', '_ipps._tcp')
+        self.simulator.go(2)
+
+        client_2.srp_client_set_host_name('my-host-2')
+        client_2.srp_client_set_host_address('2001::2')
+        client_2.srp_client_start(server.get_addrs()[0], client_2.get_srp_server_port())
+        client_2.srp_client_add_service('my-service-1', '_ipps._tcp', 12345)
+        self.simulator.go(2)
+
+        # It is expected that the registration will be rejected.
+        client_2_service = client_2.srp_client_get_services()[0]
+        self.assertNotEqual(client_2_service['state'], 'Registered')
+        self.assertNotEqual(client_2.srp_client_get_host_state(), 'Registered')
+
+        # The service 'my-service-1' is removed but its name is retained.
+        # This is why we can see the service record on the SRP server.
+        self.assertEqual(len(server.srp_server_get_services()), 1)
+        self.assertEqual(len(server.srp_server_get_hosts()), 1)
+        self.assertEqual(server.srp_server_get_host('my-host-1')['deleted'], 'false')
+        self.assertEqual(server.srp_server_get_service('my-service-1', '_ipps._tcp')['deleted'], 'true')
+
+        client_2.srp_client_clear_host()
+        client_2.srp_client_stop()
+
+        #
+        # 6. The service instance name can be re-used by another client when
+        #    the service has been permanently removed (the KEY resource is
+        #    removed) from the host.
+        #
+
+        # Client 1 adds back the service, it should success.
+        client_1.srp_client_add_service('my-service-1', '_ipps._tcp', 12345)
+        self.simulator.go(2)
+        self.assertEqual(len(server.srp_server_get_services()), 1)
+        self.assertEqual(len(server.srp_server_get_hosts()), 1)
+        self.assertEqual(server.srp_server_get_host('my-host-1')['deleted'], 'false')
+        self.assertEqual(server.srp_server_get_service('my-service-1', '_ipps._tcp')['deleted'], 'false')
+
+        # Permanently removes the service instance.
+        client_1.srp_client_remove_host(remove_key=True)
+        self.simulator.go(2)
+        self.assertEqual(len(server.srp_server_get_services()), 0)
+        self.assertEqual(len(server.srp_server_get_hosts()), 0)
+
+        # Client 2 registers the same host & service instance name with Client 1.
+        client_2.srp_client_stop()
+        client_2.srp_client_clear_host()
+        client_2.srp_client_set_host_name('my-host-1')
+        client_2.srp_client_set_host_address('2001::2')
+        client_2.srp_client_start(server.get_addrs()[0], client_2.get_srp_server_port())
+        client_2.srp_client_add_service('my-service-1', '_ipps._tcp', 12345)
+        self.simulator.go(2)
+
+        # It is expected that client 2 will success because those names has been
+        # released by client 1.
+        self.assertEqual(len(server.srp_server_get_services()), 1)
+        self.assertEqual(len(server.srp_server_get_hosts()), 1)
+        self.assertEqual(server.srp_server_get_host('my-host-1')['deleted'], 'false')
+        self.assertEqual(server.srp_server_get_service('my-service-1', '_ipps._tcp')['deleted'], 'false')
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/scripts/thread-cert/test_srp_register_single_service.py b/tests/scripts/thread-cert/test_srp_register_single_service.py
new file mode 100755
index 0000000..6241cac
--- /dev/null
+++ b/tests/scripts/thread-cert/test_srp_register_single_service.py
@@ -0,0 +1,221 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2021, The OpenThread Authors.
+#  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.
+#
+
+import ipaddress
+import unittest
+
+import command
+import thread_cert
+
+# Test description:
+#   This test verifies basic SRP function that a service can be registered to
+#   and unregistered from the SRP server.
+#
+# Topology:
+#     LEADER (SRP server)
+#       |
+#       |
+#     ROUTER (SRP client)
+#
+
+SERVER = 1
+CLIENT = 2
+
+
+class SrpRegisterSingleService(thread_cert.TestCase):
+    USE_MESSAGE_FACTORY = False
+    SUPPORT_NCP = False
+
+    TOPOLOGY = {
+        SERVER: {
+            'name': 'SRP_SERVER',
+            'masterkey': '00112233445566778899aabbccddeeff',
+            'mode': 'rdn',
+            'panid': 0xface
+        },
+        CLIENT: {
+            'name': 'SRP_CLIENT',
+            'masterkey': '00112233445566778899aabbccddeeff',
+            'mode': 'rdn',
+            'panid': 0xface,
+            'router_selection_jitter': 1
+        },
+    }
+
+    def test(self):
+        server = self.nodes[SERVER]
+        client = self.nodes[CLIENT]
+
+        #
+        # 0. Start the server & client devices.
+        #
+
+        server.srp_server_set_enabled(True)
+        server.start()
+        self.simulator.go(5)
+        self.assertEqual(server.get_state(), 'leader')
+        self.simulator.go(5)
+
+        client.srp_server_set_enabled(False)
+        client.start()
+        self.simulator.go(5)
+        self.assertEqual(client.get_state(), 'router')
+
+        #
+        # 1. Register a single service and verify that it works.
+        #
+
+        client.srp_client_set_host_name('my-host')
+        client.srp_client_set_host_address('2001::1')
+        client.srp_client_start(server.get_addrs()[0], client.get_srp_server_port())
+        client.srp_client_add_service('my-service', '_ipps._tcp', 12345, 0, 0, ['abc', 'def=', 'xyz=XYZ'])
+        self.simulator.go(2)
+
+        self.check_host_and_service(server, client, '2001::1')
+
+        #
+        # 2. Unregister a service but retain the name. The service name should be
+        #    retained on the server.
+        #
+
+        client.srp_client_remove_service('my-service', '_ipps._tcp')
+        self.simulator.go(2)
+
+        client_services = client.srp_client_get_services()
+        print(client_services)
+        self.assertEqual(len(client_services), 0)
+
+        server_services = server.srp_server_get_services()
+        print(server_services)
+        self.assertEqual(len(server_services), 1)
+        server_service = server_services[0]
+
+        # Verify that the service has been successfully removed on the server side.
+        self.assertEqual(server_service['deleted'], 'true')
+
+        server_hosts = server.srp_server_get_hosts()
+        print(server_hosts)
+        self.assertEqual(len(server_hosts), 1)
+        server_host = server_hosts[0]
+
+        # The registered host should not be touched.
+        self.assertEqual(server_host['deleted'], 'false')
+        self.assertEqual(server_host['name'], 'my-host')
+        self.assertEqual(len(server_host['addresses']), 1)
+        self.assertEqual(ipaddress.ip_address(server_host['addresses'][0]), ipaddress.ip_address('2001::1'))
+
+        #
+        # 3. Register the same service again. It should succeed and the name should be
+        #    reused.
+        #
+
+        client.srp_client_add_service('my-service', '_ipps._tcp', 12345, 0, 0, ['abc', 'def=', 'xyz=XYZ'])
+        self.simulator.go(2)
+
+        self.check_host_and_service(server, client, '2001::1')
+
+        #
+        # 4. Update the SRP host address and make sure it will succeed.
+        #
+
+        client.srp_client_set_host_address('2001::2')
+        self.simulator.go(2)
+
+        self.check_host_and_service(server, client, '2001::2')
+
+        #
+        # 5. Fully remove the host and all its service instances.
+        #
+
+        client.srp_client_remove_host(remove_key=True)
+        self.simulator.go(2)
+
+        client_services = client.srp_client_get_services()
+        print(client_services)
+        self.assertEqual(len(client_services), 0)
+
+        print(client.srp_client_get_host_state())
+
+        server_services = server.srp_server_get_services()
+        print(server_services)
+        self.assertEqual(len(server_services), 0)
+
+        server_hosts = server.srp_server_get_hosts()
+        print(server_hosts)
+        self.assertEqual(len(server_hosts), 0)
+
+    def check_host_and_service(self, server, client, host_addr):
+        """Check that we have properly registered host and service instance.
+        """
+
+        client_services = client.srp_client_get_services()
+        print(client_services)
+        self.assertEqual(len(client_services), 1)
+        client_service = client_services[0]
+
+        # Verify that the client possesses correct service resources.
+        self.assertEqual(client_service['instance'], 'my-service')
+        self.assertEqual(client_service['name'], '_ipps._tcp')
+        self.assertEqual(int(client_service['port']), 12345)
+        self.assertEqual(int(client_service['priority']), 0)
+        self.assertEqual(int(client_service['weight']), 0)
+
+        # Verify that the client received a SUCCESS response for the server.
+        self.assertEqual(client_service['state'], 'Registered')
+
+        server_services = server.srp_server_get_services()
+        self.assertEqual(len(server_services), 1)
+        server_service = server_services[0]
+
+        # Verify that the server accepted the SRP registration and stores
+        # the same service resources.
+        self.assertEqual(server_service['deleted'], 'false')
+        self.assertEqual(server_service['instance'], client_service['instance'])
+        self.assertEqual(server_service['name'], client_service['name'])
+        self.assertEqual(int(server_service['port']), int(client_service['port']))
+        self.assertEqual(int(server_service['priority']), int(client_service['priority']))
+        self.assertEqual(int(server_service['weight']), int(client_service['weight']))
+        # We output value of TXT entry as HEX string.
+        print(server_service['TXT'])
+        self.assertEqual(server_service['TXT'], ['abc', 'def=', 'xyz=58595a'])
+        self.assertEqual(server_service['host'], 'my-host')
+
+        server_hosts = server.srp_server_get_hosts()
+        print(server_hosts)
+        self.assertEqual(len(server_hosts), 1)
+        server_host = server_hosts[0]
+
+        self.assertEqual(server_host['deleted'], 'false')
+        self.assertEqual(server_host['fullname'], server_service['host_fullname'])
+        self.assertEqual(len(server_host['addresses']), 1)
+        self.assertEqual(ipaddress.ip_address(server_host['addresses'][0]), ipaddress.ip_address(host_addr))
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/scripts/thread-cert/thread_cert.py b/tests/scripts/thread-cert/thread_cert.py
index ed2393d..23f7775 100644
--- a/tests/scripts/thread-cert/thread_cert.py
+++ b/tests/scripts/thread-cert/thread_cert.py
@@ -27,6 +27,7 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
+import binascii
 import json
 import logging
 import os
@@ -157,6 +158,7 @@
             if node.is_host:
                 continue
 
+            self.nodes[i].set_masterkey(binascii.hexlify(config.DEFAULT_MASTER_KEY).decode())
             self.nodes[i].set_panid(params['panid'])
             self.nodes[i].set_mode(params['mode'])
 
@@ -184,6 +186,8 @@
                 self.nodes[i].set_timeout(params['timeout'])
 
             if 'active_dataset' in params:
+                if 'master_key' not in params['active_dataset']:
+                    params['active_dataset']['master_key'] = binascii.hexlify(config.DEFAULT_MASTER_KEY).decode()
                 self.nodes[i].set_active_dataset(params['active_dataset']['timestamp'],
                                                  panid=params['active_dataset'].get('panid'),
                                                  channel=params['active_dataset'].get('channel'),
diff --git a/tests/scripts/thread-cert/v1_2_LowPower_5_3_01_SSEDAttachment.py b/tests/scripts/thread-cert/v1_2_LowPower_5_3_01_SSEDAttachment.py
index 623cc5d..15bafe9 100755
--- a/tests/scripts/thread-cert/v1_2_LowPower_5_3_01_SSEDAttachment.py
+++ b/tests/scripts/thread-cert/v1_2_LowPower_5_3_01_SSEDAttachment.py
@@ -61,6 +61,7 @@
         SSED_1: {
             'version': '1.2',
             'name': 'SSED_1',
+            'is_mtd': True,
             'mode': '-',
             'panid': 0xface,
             'allowlist': [LEADER],
@@ -97,6 +98,8 @@
                                                 size=128,
                                                 timeout=timeout * 2))
 
+        self.simulator.go(5)
+
     def verify(self, pv):
         pkts = pv.pkts
         pv.summary.show()
diff --git a/tests/scripts/thread-cert/v1_2_test_csl_transmission.py b/tests/scripts/thread-cert/v1_2_test_csl_transmission.py
index 352ad47..f0c46e4 100755
--- a/tests/scripts/thread-cert/v1_2_test_csl_transmission.py
+++ b/tests/scripts/thread-cert/v1_2_test_csl_transmission.py
@@ -31,14 +31,11 @@
 
 import mle
 import thread_cert
+from pktverify import consts
 
 LEADER = 1
 SSED_1 = 2
 
-CSL_PERIOD = 500 * 6.25  # 500ms
-CSL_TIMEOUT = 30  # 30s
-CSL_CHANNEL = 12
-
 
 class SSED_CslTransmission(thread_cert.TestCase):
     TOPOLOGY = {
@@ -47,6 +44,7 @@
         },
         SSED_1: {
             'version': '1.2',
+            'is_mtd': True,
             'mode': '-',
         },
     }
@@ -54,8 +52,8 @@
 
     def test(self):
 
-        self.nodes[SSED_1].set_csl_period(CSL_PERIOD)
-        self.nodes[SSED_1].set_csl_timeout(CSL_TIMEOUT)
+        self.nodes[SSED_1].set_csl_period(consts.CSL_DEFAULT_PERIOD)
+        self.nodes[SSED_1].set_csl_timeout(consts.CSL_DEFAULT_TIMEOUT)
 
         self.nodes[SSED_1].get_csl_info()
 
@@ -68,24 +66,26 @@
         self.assertEqual(self.nodes[SSED_1].get_state(), 'child')
 
         print('SSED rloc:%s' % self.nodes[SSED_1].get_rloc())
-        self.assertTrue(self.nodes[LEADER].ping(self.nodes[SSED_1].get_rloc()))
+        # Set ping timeout as two CSL periods.
+        timeout = 2 * consts.CSL_DEFAULT_PERIOD_IN_SECOND
+        self.assertTrue(self.nodes[LEADER].ping(self.nodes[SSED_1].get_rloc(), timeout=timeout))
         self.simulator.go(5)
 
         ssed_messages = self.simulator.get_messages_sent_by(SSED_1)
         msg = ssed_messages.next_mle_message(mle.CommandType.CHILD_UPDATE_REQUEST)
         msg.assertMleMessageDoesNotContainTlv(mle.CslChannel)
 
-        self.nodes[SSED_1].set_csl_channel(CSL_CHANNEL)
+        self.nodes[SSED_1].set_csl_channel(consts.CSL_DEFAULT_CHANNEL)
         self.simulator.go(1)
         ssed_messages = self.simulator.get_messages_sent_by(SSED_1)
         msg = ssed_messages.next_mle_message(mle.CommandType.CHILD_UPDATE_REQUEST)
         msg.assertMleMessageContainsTlv(mle.CslChannel)
-        self.assertTrue(self.nodes[LEADER].ping(self.nodes[SSED_1].get_rloc()))
+        self.assertTrue(self.nodes[LEADER].ping(self.nodes[SSED_1].get_rloc(), timeout=timeout))
         self.simulator.go(5)
 
         self.nodes[SSED_1].set_csl_channel(0)
         self.simulator.go(1)
-        self.assertTrue(self.nodes[LEADER].ping(self.nodes[SSED_1].get_rloc()))
+        self.assertTrue(self.nodes[LEADER].ping(self.nodes[SSED_1].get_rloc(), timeout=timeout))
         self.simulator.go(5)
 
         ssed_messages = self.simulator.get_messages_sent_by(SSED_1)
@@ -93,7 +93,7 @@
         msg.assertMleMessageDoesNotContainTlv(mle.CslChannel)
 
         self.nodes[SSED_1].set_csl_period(0)
-        self.assertFalse(self.nodes[LEADER].ping(self.nodes[SSED_1].get_rloc()))
+        self.assertFalse(self.nodes[LEADER].ping(self.nodes[SSED_1].get_rloc(), timeout=timeout))
         self.simulator.go(2)
 
         self.nodes[SSED_1].set_pollperiod(1000)
diff --git a/tests/toranj/openthread-core-toranj-config-posix.h b/tests/toranj/openthread-core-toranj-config-posix.h
index fae2095..fb41137 100644
--- a/tests/toranj/openthread-core-toranj-config-posix.h
+++ b/tests/toranj/openthread-core-toranj-config-posix.h
@@ -62,6 +62,21 @@
 #define OPENTHREAD_CONFIG_POSIX_APP_TREL_INTERFACE_NAME "trel"
 
 /**
+ * @def OPENTHREAD_CONFIG_POSIX_TREL_USE_NETLINK_SOCKET
+ *
+ * Defines whether the TREL UDP6 platform uses netlink socket to add/remove addresses on the TREL netif or `ioctl()`
+ * command.
+ *
+ * When netlink is used Duplicate Address Detection (DAD) is disabled when a new address is added on the netif.
+ *
+ */
+#ifdef __linux__
+#define OPENTHREAD_CONFIG_POSIX_TREL_USE_NETLINK_SOCKET 1
+#else
+#define OPENTHREAD_CONFIG_POSIX_TREL_USE_NETLINK_SOCKET 0
+#endif
+
+/**
  * @def OPENTHREAD_POSIX_CONFIG_RCP_PTY_ENABLE
  *
  * Define as 1 to enable PTY device support in POSIX app.
diff --git a/tests/toranj/openthread-core-toranj-config.h b/tests/toranj/openthread-core-toranj-config.h
index 0d0c328..2dc25c9 100644
--- a/tests/toranj/openthread-core-toranj-config.h
+++ b/tests/toranj/openthread-core-toranj-config.h
@@ -313,12 +313,12 @@
 #define OPENTHREAD_CONFIG_LOG_PLATFORM 1
 
 /**
- * @def OPENTHREAD_CONFIG_NCP_UART_ENABLE
+ * @def OPENTHREAD_CONFIG_NCP_HDLC_ENABLE
  *
- * Define to 1 to enable NCP UART support.
+ * Define to 1 to enable NCP HDLC support.
  *
  */
-#define OPENTHREAD_CONFIG_NCP_UART_ENABLE 1
+#define OPENTHREAD_CONFIG_NCP_HDLC_ENABLE 1
 
 /**
  * @def OPENTHREAD_CONFIG_NCP_TX_BUFFER_SIZE
@@ -470,6 +470,30 @@
  */
 #define OPENTHREAD_CONFIG_DNS_CLIENT_ENABLE 1
 
+/**
+ * @def OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+ *
+ * Define to 1 to enable SRP Client support.
+ *
+ */
+#define OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE 1
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
+ *
+ * Define to 1 to enable SRP Server support.
+ *
+ */
+#define OPENTHREAD_CONFIG_SRP_SERVER_ENABLE 1
+
+/**
+ * @def OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_CHANGE_ENABLE
+ *
+ * Define to 1 for the SRP client implementation to provides APIs to allow domain name to be set/changed.
+ *
+ */
+#define OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE 1
+
 #if OPENTHREAD_RADIO
 /**
  * @def OPENTHREAD_CONFIG_MAC_SOFTWARE_ACK_TIMEOUT_ENABLE
diff --git a/tests/toranj/test-002-form.py b/tests/toranj/test-002-form.py
index 969d9ae..34841af 100644
--- a/tests/toranj/test-002-form.py
+++ b/tests/toranj/test-002-form.py
@@ -53,13 +53,11 @@
 # Test implementation
 
 # default values after reset
-DEFAULT_KEY = '[00112233445566778899AABBCCDDEEFF]'
 DEFAULT_NAME = '"OpenThread"'
 DEFAULT_PANID = '0xFFFF'
 DEFAULT_XPANID = '0xDEAD00BEEF00CAFE'
 
 verify(node.get(wpan.WPAN_STATE) == wpan.STATE_OFFLINE)
-verify(node.get(wpan.WPAN_KEY) == DEFAULT_KEY)
 verify(node.get(wpan.WPAN_NAME) == DEFAULT_NAME)
 verify(node.get(wpan.WPAN_PANID) == DEFAULT_PANID)
 verify(node.get(wpan.WPAN_XPANID) == DEFAULT_XPANID)
@@ -70,7 +68,6 @@
 verify(node.get(wpan.WPAN_STATE) == wpan.STATE_ASSOCIATED)
 verify(node.get(wpan.WPAN_NODE_TYPE) == wpan.NODE_TYPE_LEADER)
 verify(node.get(wpan.WPAN_NAME) == '"asha"')
-verify(node.get(wpan.WPAN_KEY) != DEFAULT_KEY)
 verify(node.get(wpan.WPAN_PANID) != DEFAULT_PANID)
 verify(node.get(wpan.WPAN_XPANID) != DEFAULT_XPANID)
 
@@ -83,7 +80,6 @@
 verify(node.get(wpan.WPAN_STATE) == wpan.STATE_ASSOCIATED)
 verify(node.get(wpan.WPAN_NAME) == '"ahura"')
 verify(node.get(wpan.WPAN_CHANNEL) == '20')
-verify(node.get(wpan.WPAN_KEY) != DEFAULT_KEY)
 verify(node.get(wpan.WPAN_PANID) != DEFAULT_PANID)
 verify(node.get(wpan.WPAN_XPANID) != DEFAULT_XPANID)
 
diff --git a/tests/toranj/wpan.py b/tests/toranj/wpan.py
index 0780a97..1153405 100644
--- a/tests/toranj/wpan.py
+++ b/tests/toranj/wpan.py
@@ -1185,7 +1185,7 @@
 
         m = re.match(
             r'\t"([0-9a-fA-F:]+)\s*prefix_len:(\d+)\s+origin:(\w*)\s+stable:(\w*).* \[' +
-            r'on-mesh:(\d)\s+def-route:(\d)\s+config:(\d)\s+dhcp:(\d)\s+slaac:(\d)\s+pref:(\d)\s+prio:(\w*)\]' +
+            r'on-mesh:(\d)\s+def-route:(\d)\s+config:(\d)\s+dhcp:(\d)\s+slaac:(\d)\s+pref:(\d)\s+.*prio:(\w*)\]' +
             r'\s+rloc:(0x[0-9a-fA-F]+)', text)
         verify(m is not None)
         data = m.groups()
diff --git a/tests/unit/test_checksum.cpp b/tests/unit/test_checksum.cpp
index 0398e7d..b823281 100644
--- a/tests/unit/test_checksum.cpp
+++ b/tests/unit/test_checksum.cpp
@@ -197,7 +197,7 @@
 
         CorruptMessage(*message);
 
-        VerifyOrQuit(Checksum::VerifyMessageChecksum(*message, messageInfo, Ip6::kProtoUdp) != OT_ERROR_NONE,
+        VerifyOrQuit(Checksum::VerifyMessageChecksum(*message, messageInfo, Ip6::kProtoUdp) != kErrorNone,
                      "Checksum passed on corrupted message");
 
         message->Free();
@@ -271,7 +271,7 @@
 
         CorruptMessage(*message);
 
-        VerifyOrQuit(Checksum::VerifyMessageChecksum(*message, messageInfo, Ip6::kProtoIcmp6) != OT_ERROR_NONE,
+        VerifyOrQuit(Checksum::VerifyMessageChecksum(*message, messageInfo, Ip6::kProtoIcmp6) != kErrorNone,
                      "Checksum passed on corrupted message");
 
         message->Free();
diff --git a/tests/unit/test_child.cpp b/tests/unit/test_child.cpp
index 7f23363..40ee48b 100644
--- a/tests/unit/test_child.cpp
+++ b/tests/unit/test_child.cpp
@@ -92,7 +92,7 @@
     {
         Ip6::Address address;
 
-        VerifyOrQuit(aChild.GetMeshLocalIp6Address(address) == OT_ERROR_NOT_FOUND,
+        VerifyOrQuit(aChild.GetMeshLocalIp6Address(address) == kErrorNotFound,
                      "Child::GetMeshLocalIp6Address() returned an address not in the expected list");
     }
 
@@ -263,7 +263,7 @@
 
     for (uint8_t index = 0; index < numAddresses; index++)
     {
-        VerifyOrQuit(child.AddIp6Address(addresses[index]) == OT_ERROR_ALREADY,
+        VerifyOrQuit(child.AddIp6Address(addresses[index]) == kErrorAlready,
                      "AddIp6Address() did not fail when adding same address");
         VerifyChildIp6Addresses(child, numAddresses, addresses);
     }
@@ -278,7 +278,7 @@
         SuccessOrQuit(child.RemoveIp6Address(addresses[index]), "RemoveIp6Address() failed");
         VerifyChildIp6Addresses(child, numAddresses - 1 - index, &addresses[index + 1]);
 
-        VerifyOrQuit(child.RemoveIp6Address(addresses[index]) == OT_ERROR_NOT_FOUND,
+        VerifyOrQuit(child.RemoveIp6Address(addresses[index]) == kErrorNotFound,
                      "RemoveIp6Address() did not fail when removing an address not on the list");
     }
 
@@ -298,7 +298,7 @@
         SuccessOrQuit(child.RemoveIp6Address(addresses[index]), "RemoveIp6Address() failed");
         VerifyChildIp6Addresses(child, index, &addresses[0]);
 
-        VerifyOrQuit(child.RemoveIp6Address(addresses[index]) == OT_ERROR_NOT_FOUND,
+        VerifyOrQuit(child.RemoveIp6Address(addresses[index]) == kErrorNotFound,
                      "RemoveIp6Address() did not fail when removing an address not on the list");
     }
 
@@ -318,7 +318,7 @@
 
         SuccessOrQuit(child.RemoveIp6Address(addresses[indexToRemove]), "RemoveIp6Address() failed");
 
-        VerifyOrQuit(child.RemoveIp6Address(addresses[indexToRemove]) == OT_ERROR_NOT_FOUND,
+        VerifyOrQuit(child.RemoveIp6Address(addresses[indexToRemove]) == kErrorNotFound,
                      "RemoveIp6Address() did not fail when removing an address not on the list");
 
         {
diff --git a/tests/unit/test_child_table.cpp b/tests/unit/test_child_table.cpp
index fd66c65..664f46a 100644
--- a/tests/unit/test_child_table.cpp
+++ b/tests/unit/test_child_table.cpp
@@ -295,7 +295,7 @@
     uint16_t testNumAllowedChildren = 2;
 
     ChildTable *table;
-    otError     error;
+    Error       error;
 
     sInstance = testInitInstance();
     VerifyOrQuit(sInstance != nullptr, "Null instance");
@@ -364,17 +364,17 @@
     printf("Test Get/SetMaxChildrenAllowed");
 
     error = table->SetMaxChildrenAllowed(kMaxChildren - 1);
-    VerifyOrQuit(error == OT_ERROR_INVALID_STATE, "SetMaxChildrenAllowed() should fail when table is not empty");
+    VerifyOrQuit(error == kErrorInvalidState, "SetMaxChildrenAllowed() should fail when table is not empty");
 
     table->Clear();
     error = table->SetMaxChildrenAllowed(kMaxChildren + 1);
-    VerifyOrQuit(error == OT_ERROR_INVALID_ARGS, "SetMaxChildrenAllowed() did not fail with an invalid arg");
+    VerifyOrQuit(error == kErrorInvalidArgs, "SetMaxChildrenAllowed() did not fail with an invalid arg");
 
     error = table->SetMaxChildrenAllowed(0);
-    VerifyOrQuit(error == OT_ERROR_INVALID_ARGS, "SetMaxChildrenAllowed() did not fail with an invalid arg");
+    VerifyOrQuit(error == kErrorInvalidArgs, "SetMaxChildrenAllowed() did not fail with an invalid arg");
 
     error = table->SetMaxChildrenAllowed(testNumAllowedChildren);
-    VerifyOrQuit(error == OT_ERROR_NONE, "SetMaxChildrenAllowed() failed");
+    VerifyOrQuit(error == kErrorNone, "SetMaxChildrenAllowed() failed");
     VerifyOrQuit(table->GetMaxChildrenAllowed() == testNumAllowedChildren, "GetMaxChildrenAllowed() failed");
 
     for (uint16_t num = 0; num < testNumAllowedChildren; num++)
diff --git a/tests/unit/test_dns.cpp b/tests/unit/test_dns.cpp
index 2450149..64011e5 100644
--- a/tests/unit/test_dns.cpp
+++ b/tests/unit/test_dns.cpp
@@ -34,7 +34,7 @@
 #include "test_util.hpp"
 
 #include "common/instance.hpp"
-#include "net/dns_headers.hpp"
+#include "net/dns_types.hpp"
 
 namespace ot {
 
@@ -42,9 +42,8 @@
 {
     enum
     {
-        kMaxSize   = 300,
-        kLabelSize = 64,
-        kNameSize  = 256,
+        kMaxSize       = 300,
+        kMaxNameLength = Dns::Name::kMaxNameSize - 1,
     };
 
     struct TestName
@@ -62,9 +61,9 @@
     uint8_t      buffer[kMaxSize];
     uint16_t     len;
     uint16_t     offset;
-    char         label[kLabelSize];
+    char         label[Dns::Name::kMaxLabelSize];
     uint8_t      labelLength;
-    char         name[kNameSize];
+    char         name[Dns::Name::kMaxNameSize];
 
     static const uint8_t kEncodedName1[] = {7, 'e', 'x', 'a', 'm', 'p', 'l', 'e', 3, 'c', 'o', 'm', 0};
     static const uint8_t kEncodedName2[] = {3, 'f', 'o', 'o', 1, 'a', 2, 'b', 'b', 3, 'e', 'd', 'u', 0};
@@ -88,6 +87,18 @@
         {nullptr, sizeof(kEncodedName4), kEncodedName4, kLabels4, "."},
     };
 
+    static const char *kMaxLengthNames[] = {
+        "HereIsSomeoneHidden.MyHoldFromMeTaken.FromSelfHasMeDriven.MyLeadFromMeTaken."
+        "HereIsSomeoneHidden.AsLifeSweeterThanLife.TakesMeToGardenOfSoul.MyFortFromMeTaken."
+        "HereIsSomeoneHidden.LikeSugarInSugarCane.ASweetSugarTrader.MyShopFromMeTaken."
+        "SorcererAndMagicia.",
+
+        "HereIsSomeoneHidden.MyHoldFromMeTaken.FromSelfHasMeDriven.MyLeadFromMeTaken."
+        "HereIsSomeoneHidden.AsLifeSweeterThanLife.TakesMeToGardenOfSoul.MyFortFromMeTaken."
+        "HereIsSomeoneHidden.LikeSugarInSugarCane.ASweetSugarTrader.MyShopFromMeTaken."
+        "SorcererAndMagicia",
+    };
+
     static const char *kInvalidNames[] = {
         "foo..bar",
         "..",
@@ -104,8 +115,24 @@
         "SorcererAndMagician.NoEyesCanEverSee.AnArtfulConjurer.MySenseFromMeTaken."
         "MyEyesWillNeverSee.BeautiesOfTheWholeWorld.BeholdWhoseVisionFine.MySightFromMeTaken"
         "PoemByRumiMolana",
+
+        // Long name of 255 characters which ends with a dot
+        "HereIsSomeoneHidden.MyHoldFromMeTaken.FromSelfHasMeDriven.MyLeadFromMeTaken."
+        "HereIsSomeoneHidden.AsLifeSweeterThanLife.TakesMeToGardenOfSoul.MyFortFromMeTaken."
+        "HereIsSomeoneHidden.LikeSugarInSugarCane.ASweetSugarTrader.MyShopFromMeTaken."
+        "SorcererAndMagician.",
+
+        // Long name of 254 characters which does not end with a dot
+        "HereIsSomeoneHidden.MyHoldFromMeTaken.FromSelfHasMeDriven.MyLeadFromMeTaken."
+        "HereIsSomeoneHidden.AsLifeSweeterThanLife.TakesMeToGardenOfSoul.MyFortFromMeTaken."
+        "HereIsSomeoneHidden.LikeSugarInSugarCane.ASweetSugarTrader.MyShopFromMeTaken."
+        "SorcererAndMagician",
+
     };
 
+    static const char kBadLabel[] = "badlabel";
+    static const char kBadName[]  = "bad.name";
+
     printf("================================================================\n");
     printf("TestDnsName()\n");
 
@@ -115,6 +142,45 @@
     messagePool = &instance->Get<MessagePool>();
     VerifyOrQuit((message = messagePool->New(Message::kTypeIp6, 0)) != nullptr, "Message::New failed");
 
+    message->SetOffset(0);
+
+    printf("----------------------------------------------------------------\n");
+    printf("Verify domain name match:\n");
+
+    {
+        const char *subDomain;
+        const char *domain;
+
+        subDomain = "my-service._ipps._tcp.local.";
+        domain    = "local.";
+        VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed");
+
+        subDomain = "my-service._ipps._tcp.local";
+        domain    = "local.";
+        VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed");
+
+        subDomain = "my-service._ipps._tcp.local.";
+        domain    = "local";
+        VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed");
+
+        subDomain = "my-service._ipps._tcp.local";
+        domain    = "local";
+        VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed");
+
+        subDomain = "my-service._ipps._tcp.default.service.arpa.";
+        domain    = "default.service.arpa.";
+        VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed");
+
+        subDomain = "my-service._ipps._tcp.default.service.arpa.";
+        domain    = "service.arpa.";
+        VerifyOrQuit(Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() failed");
+
+        // Verify it doesn't match a portion of a label.
+        subDomain = "my-service._ipps._tcp.default.service.arpa.";
+        domain    = "vice.arpa.";
+        VerifyOrQuit(!Dns::Name::IsSubDomainOf(subDomain, domain), "Name::IsSubDomainOf() succeed");
+    }
+
     printf("----------------------------------------------------------------\n");
     printf("Append names, check encoded bytes, parse name and read labels:\n");
 
@@ -143,7 +209,7 @@
         for (uint8_t index = 0; test.mLabels[index] != nullptr; index++)
         {
             labelLength = sizeof(label);
-            SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, 0, label, labelLength), "Name::ReadLabel() failed");
+            SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength), "Name::ReadLabel() failed");
 
             printf("Label[%d] = \"%s\"\n", index, label);
 
@@ -152,12 +218,12 @@
         }
 
         labelLength = sizeof(label);
-        VerifyOrQuit(Dns::Name::ReadLabel(*message, offset, 0, label, labelLength) == OT_ERROR_NOT_FOUND,
+        VerifyOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength) == kErrorNotFound,
                      "Name::ReadLabel() failed at end of the name");
 
         // Read entire name
         offset = 0;
-        SuccessOrQuit(Dns::Name::ReadName(*message, offset, 0, name, sizeof(name)), "Name::ReadName() failed");
+        SuccessOrQuit(Dns::Name::ReadName(*message, offset, name, sizeof(name)), "Name::ReadName() failed");
 
         printf("Read name =\"%s\"\n", name);
 
@@ -167,12 +233,82 @@
         // Read entire name with different name buffer sizes (just right and one byte off the expected size)
         offset = 0;
         SuccessOrQuit(
-            Dns::Name::ReadName(*message, offset, 0, name, static_cast<uint16_t>(strlen(test.mExpectedReadName) + 1)),
+            Dns::Name::ReadName(*message, offset, name, static_cast<uint16_t>(strlen(test.mExpectedReadName) + 1)),
             "Name::ReadName() failed with exact name buffer size");
         offset = 0;
-        VerifyOrQuit(Dns::Name::ReadName(*message, offset, 0, name,
-                                         static_cast<uint16_t>(strlen(test.mExpectedReadName))) == OT_ERROR_NO_BUFS,
+        VerifyOrQuit(Dns::Name::ReadName(*message, offset, name,
+                                         static_cast<uint16_t>(strlen(test.mExpectedReadName))) == kErrorNoBufs,
                      "Name::ReadName() did not fail with too small name buffer size");
+
+        // Compare labels one by one.
+        offset = 0;
+
+        for (uint8_t index = 0; test.mLabels[index] != nullptr; index++)
+        {
+            uint16_t startOffset = offset;
+
+            SuccessOrQuit(Dns::Name::CompareLabel(*message, offset, test.mLabels[index]),
+                          "Name::CompareLabel() failed");
+            VerifyOrQuit(offset != startOffset, "Name::CompareLabel() did not change offset");
+
+            VerifyOrQuit(Dns::Name::CompareLabel(*message, startOffset, kBadLabel) == kErrorNotFound,
+                         "Name::CompareLabel() did not fail with incorrect label");
+        }
+
+        // Compare the whole name.
+        offset = 0;
+        SuccessOrQuit(Dns::Name::CompareName(*message, offset, test.mExpectedReadName), "Name::CompareName() failed");
+        VerifyOrQuit(offset == len, "Name::CompareName() returned incorrect offset");
+
+        offset = 0;
+        VerifyOrQuit(Dns::Name::CompareName(*message, offset, kBadName) == kErrorNotFound,
+                     "Name::CompareName() did not fail with incorrect name");
+        VerifyOrQuit(offset == len, "Name::CompareName() returned incorrect offset");
+
+        // Remove the terminating '.' in expected name and verify
+        // that it can still be used by `CompareName()`.
+        offset = 0;
+        strcpy(name, test.mExpectedReadName);
+        name[strlen(name) - 1] = '\0';
+        SuccessOrQuit(Dns::Name::CompareName(*message, offset, name), "Name::CompareName() failed with root");
+        VerifyOrQuit(offset == len, "Name::CompareName() returned incorrect offset");
+
+        if (strlen(name) >= 1)
+        {
+            name[strlen(name) - 1] = '\0';
+            offset                 = 0;
+            VerifyOrQuit(Dns::Name::CompareName(*message, offset, name) == kErrorNotFound,
+                         "Name::CompareName() did not fail with invalid name");
+            VerifyOrQuit(offset == len, "Name::CompareName() returned incorrect offset");
+        }
+
+        // Compare the name with itself read from message.
+        offset = 0;
+        SuccessOrQuit(Dns::Name::CompareName(*message, offset, *message, offset),
+                      "Name::CompareName() with itself failed");
+        VerifyOrQuit(offset == len, "Name::CompareName() returned incorrect offset");
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Max length names:\n");
+
+    for (const char *&maxLengthName : kMaxLengthNames)
+    {
+        if (maxLengthName[strlen(maxLengthName) - 1] == '.')
+        {
+            VerifyOrQuit(strlen(maxLengthName) == kMaxNameLength, "invalid max length string");
+        }
+        else
+        {
+            VerifyOrQuit(strlen(maxLengthName) == kMaxNameLength - 1, "invalid max length string");
+        }
+
+        IgnoreError(message->SetLength(0));
+
+        printf("\"%s\"\n", maxLengthName);
+
+        VerifyOrQuit(Dns::Name::AppendName(maxLengthName, *message) == kErrorNone,
+                     "Name::AppendName() failed with max length name");
     }
 
     printf("----------------------------------------------------------------\n");
@@ -184,7 +320,7 @@
 
         printf("\"%s\"\n", invalidName);
 
-        VerifyOrQuit(Dns::Name::AppendName(invalidName, *message) == OT_ERROR_INVALID_ARGS,
+        VerifyOrQuit(Dns::Name::AppendName(invalidName, *message) == kErrorInvalidArgs,
                      "Name::AppendName() did not fail with an invalid name");
     }
 
@@ -266,6 +402,8 @@
     static const char kExpectedReadName2[] = "FOO.F.ISI.ARPA.";
     static const char kExpectedReadName3[] = "ISI.ARPA.";
 
+    static const char kBadName[] = "bad.name";
+
     Instance *   instance;
     MessagePool *messagePool;
     Message *    message;
@@ -295,6 +433,8 @@
         SuccessOrQuit(message->Append(index), "Message::Append() failed");
     }
 
+    message->SetOffset(kHeaderOffset);
+
     name1Offset = message->GetLength();
     SuccessOrQuit(Dns::Name::AppendName(kName, *message), "Name::AppendName() failed");
 
@@ -348,8 +488,7 @@
     for (const char *nameLabel : kName1Labels)
     {
         labelLength = sizeof(label);
-        SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, kHeaderOffset, label, labelLength),
-                      "Name::ReadLabel() failed");
+        SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength), "Name::ReadLabel() failed");
 
         printf("label: \"%s\"\n", label);
         VerifyOrQuit(strcmp(label, nameLabel) == 0, "Name::ReadLabel() did not get expected label");
@@ -357,15 +496,40 @@
     }
 
     labelLength = sizeof(label);
-    VerifyOrQuit(Dns::Name::ReadLabel(*message, offset, kHeaderOffset, label, labelLength) == OT_ERROR_NOT_FOUND,
+    VerifyOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength) == kErrorNotFound,
                  "Name::ReadLabel() failed at end of the name");
 
     offset = name1Offset;
-    SuccessOrQuit(Dns::Name::ReadName(*message, offset, kHeaderOffset, name, sizeof(name)), "Name::ReadName() failed");
+    SuccessOrQuit(Dns::Name::ReadName(*message, offset, name, sizeof(name)), "Name::ReadName() failed");
     printf("Read name =\"%s\"\n", name);
     VerifyOrQuit(strcmp(name, kExpectedReadName1) == 0, "Name::ReadName() did not return expected name");
     VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::ReadName() returned incorrect offset");
 
+    offset = name1Offset;
+
+    for (const char *nameLabel : kName1Labels)
+    {
+        SuccessOrQuit(Dns::Name::CompareLabel(*message, offset, nameLabel), "Name::ComapreLabel() failed");
+    }
+
+    offset = name1Offset;
+    SuccessOrQuit(Dns::Name::CompareName(*message, offset, kExpectedReadName1), "Name::CompareName() failed");
+    VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::CompareName() returned incorrect offset");
+
+    offset = name1Offset;
+    VerifyOrQuit(Dns::Name::CompareName(*message, offset, kBadName) == kErrorNotFound,
+                 "Name::CompareName() did not fail with incorrect name");
+    VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::CompareName() returned incorrect offset");
+
+    offset = name1Offset;
+    SuccessOrQuit(Dns::Name::CompareName(*message, offset, *message, offset), "Name::CompareName() with itself failed");
+    VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::CompareName() returned incorrect offset");
+
+    offset = name1Offset;
+    VerifyOrQuit(Dns::Name::CompareName(*message, offset, *message, name2Offset) == kErrorNotFound,
+                 "Name::CompareName() did not fail with mismatching name");
+    VerifyOrQuit(offset == name1Offset + sizeof(kEncodedName), "Name::CompareName() returned incorrect offset");
+
     printf("----------------------------------------------------------------\n");
     printf("Read and parse compressed name-2 \"FOO.F.ISI.ARPA\"\n");
 
@@ -381,8 +545,7 @@
     for (const char *nameLabel : kName2Labels)
     {
         labelLength = sizeof(label);
-        SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, kHeaderOffset, label, labelLength),
-                      "Name::ReadLabel() failed");
+        SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength), "Name::ReadLabel() failed");
 
         printf("label: \"%s\"\n", label);
         VerifyOrQuit(strcmp(label, nameLabel) == 0, "Name::ReadLabel() did not get expected label");
@@ -390,15 +553,40 @@
     }
 
     labelLength = sizeof(label);
-    VerifyOrQuit(Dns::Name::ReadLabel(*message, offset, kHeaderOffset, label, labelLength) == OT_ERROR_NOT_FOUND,
+    VerifyOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength) == kErrorNotFound,
                  "Name::ReadLabel() failed at end of the name");
 
     offset = name2Offset;
-    SuccessOrQuit(Dns::Name::ReadName(*message, offset, kHeaderOffset, name, sizeof(name)), "Name::ReadName() failed");
+    SuccessOrQuit(Dns::Name::ReadName(*message, offset, name, sizeof(name)), "Name::ReadName() failed");
     printf("Read name =\"%s\"\n", name);
     VerifyOrQuit(strcmp(name, kExpectedReadName2) == 0, "Name::ReadName() did not return expected name");
     VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::ReadName() returned incorrect offset");
 
+    offset = name2Offset;
+
+    for (const char *nameLabel : kName2Labels)
+    {
+        SuccessOrQuit(Dns::Name::CompareLabel(*message, offset, nameLabel), "Name::ComapreLabel() failed");
+    }
+
+    offset = name2Offset;
+    SuccessOrQuit(Dns::Name::CompareName(*message, offset, kExpectedReadName2), "Name::CompareName() failed");
+    VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::CompareName() returned incorrect offset");
+
+    offset = name2Offset;
+    VerifyOrQuit(Dns::Name::CompareName(*message, offset, kBadName) == kErrorNotFound,
+                 "Name::CompareName() did not fail with incorrect name");
+    VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::CompareName() returned incorrect offset");
+
+    offset = name2Offset;
+    SuccessOrQuit(Dns::Name::CompareName(*message, offset, *message, offset), "Name::CompareName() with itself failed");
+    VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::CompareName() returned incorrect offset");
+
+    offset = name2Offset;
+    VerifyOrQuit(Dns::Name::CompareName(*message, offset, *message, name3Offset) == kErrorNotFound,
+                 "Name::CompareName() did not fail with mismatching name");
+    VerifyOrQuit(offset == name2Offset + kName2EncodedSize, "Name::CompareName() returned incorrect offset");
+
     printf("----------------------------------------------------------------\n");
     printf("Read and parse compressed name-3 \"ISI.ARPA\"\n");
 
@@ -414,8 +602,7 @@
     for (const char *nameLabel : kName3Labels)
     {
         labelLength = sizeof(label);
-        SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, kHeaderOffset, label, labelLength),
-                      "Name::ReadLabel() failed");
+        SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength), "Name::ReadLabel() failed");
 
         printf("label: \"%s\"\n", label);
         VerifyOrQuit(strcmp(label, nameLabel) == 0, "Name::ReadLabel() did not get expected label");
@@ -423,15 +610,40 @@
     }
 
     labelLength = sizeof(label);
-    VerifyOrQuit(Dns::Name::ReadLabel(*message, offset, kHeaderOffset, label, labelLength) == OT_ERROR_NOT_FOUND,
+    VerifyOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength) == kErrorNotFound,
                  "Name::ReadLabel() failed at end of the name");
 
     offset = name3Offset;
-    SuccessOrQuit(Dns::Name::ReadName(*message, offset, kHeaderOffset, name, sizeof(name)), "Name::ReadName() failed");
+    SuccessOrQuit(Dns::Name::ReadName(*message, offset, name, sizeof(name)), "Name::ReadName() failed");
     printf("Read name =\"%s\"\n", name);
     VerifyOrQuit(strcmp(name, kExpectedReadName3) == 0, "Name::ReadName() did not return expected name");
     VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::ReadName() returned incorrect offset");
 
+    offset = name3Offset;
+
+    for (const char *nameLabel : kName3Labels)
+    {
+        SuccessOrQuit(Dns::Name::CompareLabel(*message, offset, nameLabel), "Name::ComapreLabel() failed");
+    }
+
+    offset = name3Offset;
+    SuccessOrQuit(Dns::Name::CompareName(*message, offset, kExpectedReadName3), "Name::CompareName() failed");
+    VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::CompareName() returned incorrect offset");
+
+    offset = name3Offset;
+    VerifyOrQuit(Dns::Name::CompareName(*message, offset, kBadName) == kErrorNotFound,
+                 "Name::CompareName() did not fail with incorrect name");
+    VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::CompareName() returned incorrect offset");
+
+    offset = name3Offset;
+    SuccessOrQuit(Dns::Name::CompareName(*message, offset, *message, offset), "Name::CompareName() with itself failed");
+    VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::CompareName() returned incorrect offset");
+
+    offset = name3Offset;
+    VerifyOrQuit(Dns::Name::CompareName(*message, offset, *message, name4Offset) == kErrorNotFound,
+                 "Name::CompareName() did not fail with mismatching name");
+    VerifyOrQuit(offset == name3Offset + kName3EncodedSize, "Name::CompareName() returned incorrect offset");
+
     printf("----------------------------------------------------------------\n");
     printf("Read and parse the uncompressed name-4 \"Human\\.Readable.F.ISI.ARPA\"\n");
 
@@ -447,8 +659,7 @@
     for (const char *nameLabel : kName4Labels)
     {
         labelLength = sizeof(label);
-        SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, kHeaderOffset, label, labelLength),
-                      "Name::ReadLabel() failed");
+        SuccessOrQuit(Dns::Name::ReadLabel(*message, offset, label, labelLength), "Name::ReadLabel() failed");
 
         printf("label: \"%s\"\n", label);
         VerifyOrQuit(strcmp(label, nameLabel) == 0, "Name::ReadLabel() did not get expected label");
@@ -457,9 +668,636 @@
 
     // `ReadName()` for name-4 should fails due to first label containing dot char.
     offset = name4Offset;
-    VerifyOrQuit(Dns::Name::ReadName(*message, offset, kHeaderOffset, name, sizeof(name)) == OT_ERROR_PARSE,
+    VerifyOrQuit(Dns::Name::ReadName(*message, offset, name, sizeof(name)) == kErrorParse,
                  "Name::ReadName() did not fail with invalid label");
 
+    offset = name4Offset;
+
+    for (const char *nameLabel : kName4Labels)
+    {
+        SuccessOrQuit(Dns::Name::CompareLabel(*message, offset, nameLabel), "Name::ComapreLabel() failed");
+    }
+
+    offset = name4Offset;
+    SuccessOrQuit(Dns::Name::CompareName(*message, offset, *message, offset), "Name::CompareName() with itself failed");
+
+    offset = name4Offset;
+    VerifyOrQuit(Dns::Name::CompareName(*message, offset, *message, name1Offset) == kErrorNotFound,
+                 "Name::CompareName() did not fail with mismatching name");
+
+    message->Free();
+    testFreeInstance(instance);
+}
+
+void TestHeaderAndResourceRecords(void)
+{
+    enum
+    {
+        kHeaderOffset    = 0,
+        kQuestionCount   = 1,
+        kAnswerCount     = 2,
+        kAdditionalCount = 5,
+        kTtl             = 7200,
+        kTxtTtl          = 7300,
+        kSrvPort         = 1234,
+        kSrvPriority     = 1,
+        kSrvWeight       = 2,
+        kMaxSize         = 600,
+    };
+
+    const char    kMessageString[]  = "DnsMessage";
+    const char    kDomainName[]     = "example.com.";
+    const char    kServiceLabels[]  = "_service._udp";
+    const char    kServiceName[]    = "_service._udp.example.com.";
+    const char    kInstance1Label[] = "inst1";
+    const char    kInstance2Label[] = "instance2";
+    const char    kInstance1Name[]  = "inst1._service._udp.example.com.";
+    const char    kInstance2Name[]  = "instance2._service._udp.example.com.";
+    const char    kHostName[]       = "host.example.com.";
+    const uint8_t kTxtData[]        = {9, 'k', 'e', 'y', '=', 'v', 'a', 'l', 'u', 'e', 0};
+    const char    kHostAddress[]    = "fd00::abcd:";
+
+    const char *kInstanceLabels[] = {kInstance1Label, kInstance2Label};
+    const char *kInstanceNames[]  = {kInstance1Name, kInstance2Name};
+
+    Instance *          instance;
+    MessagePool *       messagePool;
+    Message *           message;
+    Dns::Header         header;
+    uint16_t            messageId;
+    uint16_t            headerOffset;
+    uint16_t            offset;
+    uint16_t            numRecords;
+    uint16_t            len;
+    uint16_t            serviceNameOffset;
+    uint16_t            hostNameOffset;
+    uint16_t            answerSectionOffset;
+    uint16_t            additionalSectionOffset;
+    uint16_t            index;
+    Dns::PtrRecord      ptrRecord;
+    Dns::SrvRecord      srvRecord;
+    Dns::TxtRecord      txtRecord;
+    Dns::AaaaRecord     aaaaRecord;
+    Dns::ResourceRecord record;
+    Ip6::Address        hostAddress;
+
+    char    label[Dns::Name::kMaxLabelSize];
+    char    name[Dns::Name::kMaxNameSize];
+    uint8_t buffer[kMaxSize];
+
+    printf("================================================================\n");
+    printf("TestHeaderAndResourceRecords()\n");
+
+    instance = static_cast<Instance *>(testInitInstance());
+    VerifyOrQuit(instance != nullptr, "Null OpenThread instance");
+
+    messagePool = &instance->Get<MessagePool>();
+    VerifyOrQuit((message = messagePool->New(Message::kTypeIp6, 0)) != nullptr, "Message::New failed");
+
+    printf("----------------------------------------------------------------\n");
+    printf("Preparing the message\n");
+
+    SuccessOrQuit(message->Append(kMessageString), "Message::Append() failed");
+
+    // Header
+
+    headerOffset = message->GetLength();
+    SuccessOrQuit(header.SetRandomMessageId(), "Header::SetRandomMessageId() failed");
+    messageId = header.GetMessageId();
+    header.SetType(Dns::Header::kTypeResponse);
+    header.SetQuestionCount(kQuestionCount);
+    header.SetAnswerCount(kAnswerCount);
+    header.SetAdditionalRecordCount(kAdditionalCount);
+    SuccessOrQuit(message->Append(header), "Message::Append() failed");
+    message->SetOffset(headerOffset);
+
+    // Question section
+
+    serviceNameOffset = message->GetLength() - headerOffset;
+    SuccessOrQuit(Dns::Name::AppendMultipleLabels(kServiceLabels, *message), "AppendMultipleLabels() failed");
+    SuccessOrQuit(Dns::Name::AppendName(kDomainName, *message), "AppendName() failed");
+    SuccessOrQuit(message->Append(Dns::Question(Dns::ResourceRecord::kTypePtr)), "Message::Append() failed");
+
+    // Answer section
+
+    answerSectionOffset = message->GetLength();
+
+    for (const char *instanceLabel : kInstanceLabels)
+    {
+        SuccessOrQuit(Dns::Name::AppendPointerLabel(serviceNameOffset, *message), "AppendPointerLabel() failed");
+        ptrRecord.Init();
+        ptrRecord.SetTtl(kTtl);
+        offset = message->GetLength();
+        SuccessOrQuit(message->Append(ptrRecord), "Message::Append() failed");
+        SuccessOrQuit(Dns::Name::AppendLabel(instanceLabel, *message), "AppendLabel failed");
+        SuccessOrQuit(Dns::Name::AppendPointerLabel(serviceNameOffset, *message), "AppendPointerLabel() failed");
+        ptrRecord.SetLength(message->GetLength() - offset - sizeof(Dns::ResourceRecord));
+        message->Write(offset, ptrRecord);
+    }
+
+    // Additional section
+
+    additionalSectionOffset = message->GetLength();
+
+    for (const char *instanceName : kInstanceNames)
+    {
+        uint16_t instanceNameOffset = message->GetLength() - headerOffset;
+
+        // SRV record
+        SuccessOrQuit(Dns::Name::AppendName(instanceName, *message), "AppendName() failed");
+        srvRecord.Init();
+        srvRecord.SetTtl(kTtl);
+        srvRecord.SetPort(kSrvPort);
+        srvRecord.SetWeight(kSrvWeight);
+        srvRecord.SetPriority(kSrvPriority);
+        offset = message->GetLength();
+        SuccessOrQuit(message->Append(srvRecord), "Message::Append() failed");
+        hostNameOffset = message->GetLength() - headerOffset;
+        SuccessOrQuit(Dns::Name::AppendName(kHostName, *message), "AppendName() failed");
+        srvRecord.SetLength(message->GetLength() - offset - sizeof(Dns::ResourceRecord));
+        message->Write(offset, srvRecord);
+
+        // TXT record
+        SuccessOrQuit(Dns::Name::AppendPointerLabel(instanceNameOffset, *message), "AppendPointerLabel() failed");
+        txtRecord.Init();
+        txtRecord.SetTtl(kTxtTtl);
+        txtRecord.SetLength(sizeof(kTxtData));
+        SuccessOrQuit(message->Append(txtRecord), "Message::Append() failed");
+        SuccessOrQuit(message->Append(kTxtData), "Message::Append() failed");
+    }
+
+    SuccessOrQuit(hostAddress.FromString(kHostAddress), "Address::FromString() failed");
+    SuccessOrQuit(Dns::Name::AppendPointerLabel(hostNameOffset, *message), "AppendPointerLabel() failed");
+    aaaaRecord.Init();
+    aaaaRecord.SetTtl(kTtl);
+    aaaaRecord.SetAddress(hostAddress);
+    SuccessOrQuit(message->Append(aaaaRecord), "Message::Append()");
+
+    // Dump the entire message
+
+    VerifyOrQuit(message->GetLength() < kMaxSize, "Message is too long");
+    SuccessOrQuit(message->Read(0, buffer, message->GetLength()), "Message::Read() failed");
+    DumpBuffer("message", buffer, message->GetLength());
+
+    printf("----------------------------------------------------------------\n");
+    printf("Parse and verify the message\n");
+
+    offset = 0;
+    VerifyOrQuit(message->Compare(offset, kMessageString), "Message header does not match");
+    offset += sizeof(kMessageString);
+
+    // Header
+
+    VerifyOrQuit(offset == headerOffset, "headerOffset is incorrect");
+    SuccessOrQuit(message->Read(offset, header), "Message::Read() failed");
+    offset += sizeof(header);
+
+    VerifyOrQuit(header.GetMessageId() == messageId, "Header::GetMessageId() failed");
+    VerifyOrQuit(header.GetType() == Dns::Header::kTypeResponse, "Header::GetType() failed");
+    VerifyOrQuit(header.GetQuestionCount() == kQuestionCount, "Header::GetQuestionCount() failed");
+    VerifyOrQuit(header.GetAnswerCount() == kAnswerCount, "Header::GetAnswerCount() failed");
+    VerifyOrQuit(header.GetAdditionalRecordCount() == kAdditionalCount, "Header::GetAdditionalRecordCount() failed");
+
+    printf("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \n");
+    printf("Question Section\n");
+
+    SuccessOrQuit(Dns::Name::CompareName(*message, offset, kServiceName), "Question name does not match");
+    VerifyOrQuit(message->Compare(offset, Dns::Question(Dns::ResourceRecord::kTypePtr)), "Question does not match");
+    offset += sizeof(Dns::Question);
+
+    printf("PTR for \"%s\"\n", kServiceName);
+
+    printf("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \n");
+    printf("Answer Section\n");
+
+    VerifyOrQuit(offset == answerSectionOffset, "answer section offset is incorrect");
+
+    for (const char *instanceName : kInstanceNames)
+    {
+        SuccessOrQuit(Dns::Name::CompareName(*message, offset, kServiceName), "ServiceName is incorrect");
+        SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, ptrRecord), "ReadRecord() failed");
+        VerifyOrQuit(ptrRecord.GetTtl() == kTtl, "Read PTR is incorrect");
+
+        SuccessOrQuit(ptrRecord.ReadPtrName(*message, offset, name, sizeof(name)), "ReadName() failed");
+        VerifyOrQuit(strcmp(name, instanceName) == 0, "Inst1 name is incorrect");
+
+        printf("    \"%s\" PTR %u %d \"%s\"\n", kServiceName, ptrRecord.GetTtl(), ptrRecord.GetLength(), name);
+    }
+
+    VerifyOrQuit(offset == additionalSectionOffset, "offset is incorrect after answer section parse");
+
+    offset = answerSectionOffset;
+    SuccessOrQuit(Dns::ResourceRecord::ParseRecords(*message, offset, kAnswerCount), "ParseRecords() failed");
+    VerifyOrQuit(offset == additionalSectionOffset, "offset is incorrect after answer section parse");
+
+    printf("Use FindRecord() to find and iterate through all the records:\n");
+
+    offset     = answerSectionOffset;
+    numRecords = kAnswerCount;
+
+    while (numRecords > 0)
+    {
+        uint16_t prevNumRecords = numRecords;
+
+        SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, numRecords, Dns::Name(kServiceName)),
+                      "FindRecord failed");
+        VerifyOrQuit(numRecords == prevNumRecords - 1, "Incorrect num records");
+        SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, ptrRecord), "ReadRecord() failed");
+        VerifyOrQuit(ptrRecord.GetTtl() == kTtl, "Read PTR is incorrect");
+        SuccessOrQuit(ptrRecord.ReadPtrName(*message, offset, label, sizeof(label), name, sizeof(name)),
+                      "ReadName() failed");
+        printf("    \"%s\" PTR %u %d inst:\"%s\" at \"%s\"\n", kServiceName, ptrRecord.GetTtl(), ptrRecord.GetLength(),
+               label, name);
+    }
+
+    VerifyOrQuit(offset == additionalSectionOffset, "offset is incorrect after answer section parse");
+    VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, numRecords, Dns::Name(kServiceName)) ==
+                     kErrorNotFound,
+                 "FindRecord did not fail with no records");
+
+    // Use `ReadRecord()` with a non-matching record type. Verify that it correct skips over the record.
+
+    offset     = answerSectionOffset;
+    numRecords = kAnswerCount;
+
+    while (numRecords > 0)
+    {
+        SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, numRecords, Dns::Name(kServiceName)),
+                      "FindRecord failed");
+        VerifyOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, srvRecord) == kErrorNotFound,
+                     "ReadRecord() did not fail with non-matching type");
+    }
+
+    VerifyOrQuit(offset == additionalSectionOffset, "offset is incorrect after answer section parse");
+
+    // Use `FindRecord` with a non-matching name. Verify that it correctly skips over all records.
+
+    offset     = answerSectionOffset;
+    numRecords = kAnswerCount;
+    VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, numRecords, Dns::Name(kInstance1Name)) ==
+                     kErrorNotFound,
+                 "FindRecord did not fail with non-matching name");
+    VerifyOrQuit(numRecords == 0, "Incorrect num records");
+    VerifyOrQuit(offset == additionalSectionOffset, "offset is incorrect after answer section parse");
+
+    printf("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \n");
+    printf("Additional Section\n");
+
+    for (const char *instanceName : kInstanceNames)
+    {
+        // SRV record
+        SuccessOrQuit(Dns::Name::CompareName(*message, offset, instanceName), "Instance is incorrect");
+        SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, srvRecord), "ReadRecord() failed");
+        VerifyOrQuit(srvRecord.GetTtl() == kTtl, "Read SRV is incorrect");
+        VerifyOrQuit(srvRecord.GetPort() == kSrvPort, "Read SRV port is incorrect");
+        VerifyOrQuit(srvRecord.GetWeight() == kSrvWeight, "Read SRV weight is incorrect");
+        VerifyOrQuit(srvRecord.GetPriority() == kSrvPriority, "Read SRV priority is incorrect");
+        SuccessOrQuit(srvRecord.ReadTargetHostName(*message, offset, name, sizeof(name)),
+                      "ReadTargetHostName() failed");
+        VerifyOrQuit(strcmp(name, kHostName) == 0, "Inst1 name is incorrect");
+        printf("    \"%s\" SRV %u %d %d %d %d \"%s\"\n", instanceName, srvRecord.GetTtl(), srvRecord.GetLength(),
+               srvRecord.GetPort(), srvRecord.GetWeight(), srvRecord.GetPriority(), name);
+
+        // TXT record
+        SuccessOrQuit(Dns::Name::CompareName(*message, offset, instanceName), "Instance is incorrect");
+        SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, txtRecord), "ReadRecord() failed");
+        VerifyOrQuit(txtRecord.GetTtl() == kTxtTtl, "Read TXT is incorrect");
+        len = sizeof(buffer);
+        SuccessOrQuit(txtRecord.ReadTxtData(*message, offset, buffer, len), "ReadTxtData() failed");
+        VerifyOrQuit(len == sizeof(kTxtData), "TXT data length is not valid");
+        VerifyOrQuit(memcmp(buffer, kTxtData, len) == 0, "TXT data is not valid");
+        printf("    \"%s\" TXT %u %d \"%s\"\n", instanceName, txtRecord.GetTtl(), txtRecord.GetLength(),
+               reinterpret_cast<const char *>(buffer));
+    }
+
+    SuccessOrQuit(Dns::Name::CompareName(*message, offset, kHostName), "HostName is incorrect");
+    SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, aaaaRecord), "ReadRecord() failed");
+    VerifyOrQuit(aaaaRecord.GetTtl() == kTtl, "Read AAAA is incorrect");
+    VerifyOrQuit(aaaaRecord.GetAddress() == hostAddress, "Read host address is incorrect");
+    printf("    \"%s\" AAAA %u %d \"%s\"\n", kHostName, aaaaRecord.GetTtl(), aaaaRecord.GetLength(),
+           aaaaRecord.GetAddress().ToString().AsCString());
+
+    VerifyOrQuit(offset == message->GetLength(), "offset is incorrect after additional section parse");
+
+    // Use `ParseRecords()` to parse all records
+    offset = additionalSectionOffset;
+    SuccessOrQuit(Dns::ResourceRecord::ParseRecords(*message, offset, kAdditionalCount), "ParseRecords() failed");
+    VerifyOrQuit(offset == message->GetLength(), "offset is incorrect after additional section parse");
+
+    printf("Use FindRecord() to search for specific name:\n");
+
+    for (const char *instanceName : kInstanceNames)
+    {
+        offset     = additionalSectionOffset;
+        numRecords = kAdditionalCount;
+
+        SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, numRecords, Dns::Name(instanceName)),
+                      "FindRecord failed");
+        SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, srvRecord), "ReadRecord() failed");
+        SuccessOrQuit(Dns::Name::ParseName(*message, offset), "ParseName() failed");
+        printf("    \"%s\" SRV %u %d %d %d %d\n", instanceName, srvRecord.GetTtl(), srvRecord.GetLength(),
+               srvRecord.GetPort(), srvRecord.GetWeight(), srvRecord.GetPriority());
+
+        SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, numRecords, Dns::Name(instanceName)),
+                      "FindRecord failed");
+        SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, txtRecord), "ReadRecord() failed");
+        offset += txtRecord.GetLength();
+        printf("    \"%s\" TXT %u %d\n", instanceName, txtRecord.GetTtl(), txtRecord.GetLength());
+
+        VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, numRecords, Dns::Name(instanceName)) ==
+                         kErrorNotFound,
+                     "FindRecord() did not fail with no more records");
+
+        VerifyOrQuit(offset == message->GetLength(), "offset is incorrect after additional section parse");
+    }
+
+    offset     = additionalSectionOffset;
+    numRecords = kAdditionalCount;
+    SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, numRecords, Dns::Name(kHostName)),
+                  "FindRecord() failed");
+    SuccessOrQuit(Dns::ResourceRecord::ReadRecord(*message, offset, record), "ReadRecord() failed");
+    VerifyOrQuit(record.GetType() == Dns::ResourceRecord::kTypeAaaa, "Read record has incorrect type");
+    offset += record.GetLength();
+    VerifyOrQuit(offset == message->GetLength(), "offset is incorrect after additional section parse");
+
+    printf("Use FindRecord() to search for specific records:\n");
+    printf(" Answer Section\n");
+
+    for (index = 0; index < OT_ARRAY_LENGTH(kInstanceNames); index++)
+    {
+        offset = answerSectionOffset;
+        SuccessOrQuit(
+            Dns::ResourceRecord::FindRecord(*message, offset, kAnswerCount, index, Dns::Name(kServiceName), ptrRecord),
+            "FindRecord() failed");
+
+        printf("   index:%d -> \"%s\" PTR %u %d\n", index, kServiceName, ptrRecord.GetTtl(), ptrRecord.GetLength());
+    }
+
+    // Check `FindRecord()` failure with non-matching name, record type, or bad index.
+
+    offset = answerSectionOffset;
+    VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAnswerCount, index, Dns::Name(kServiceName),
+                                                 ptrRecord) == kErrorNotFound,
+                 "FindRecord() did not fail with bad index");
+    VerifyOrQuit(offset == answerSectionOffset, "FindRecord() changed offset on failure");
+
+    offset = answerSectionOffset;
+    VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAnswerCount, index, Dns::Name(kInstance1Name),
+                                                 ptrRecord) == kErrorNotFound,
+                 "FindRecord() did not fail with bad index");
+    VerifyOrQuit(offset == answerSectionOffset, "FindRecord() changed offset on failure");
+
+    offset = answerSectionOffset;
+    VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAnswerCount, index, Dns::Name(kServiceName),
+                                                 txtRecord) == kErrorNotFound,
+                 "FindRecord() did not fail with bad index");
+    VerifyOrQuit(offset == answerSectionOffset, "FindRecord() changed offset on failure");
+
+    printf(" Additional Section\n");
+
+    for (const char *instanceName : kInstanceNames)
+    {
+        // There is a single SRV and TXT entry for each instance
+        offset = additionalSectionOffset;
+        SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, /* aIndex */ 0,
+                                                      Dns::Name(instanceName), srvRecord),
+                      "FindRecord() failed");
+        printf("    \"%s\" SRV %u %d %d %d %d \n", instanceName, srvRecord.GetTtl(), srvRecord.GetLength(),
+               srvRecord.GetPort(), srvRecord.GetWeight(), srvRecord.GetPriority());
+
+        offset = additionalSectionOffset;
+        SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, /* aIndex */ 0,
+                                                      Dns::Name(instanceName), txtRecord),
+                      "FindRecord() failed");
+        printf("    \"%s\" TXT %u %d\n", instanceName, txtRecord.GetTtl(), txtRecord.GetLength());
+
+        offset = additionalSectionOffset;
+        VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, /* aIndex */ 1,
+                                                     Dns::Name(instanceName), srvRecord) == kErrorNotFound,
+                     "FindRecord() did not fail with bad index");
+
+        offset = additionalSectionOffset;
+        VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, /* aIndex */ 1,
+                                                     Dns::Name(instanceName), txtRecord) == kErrorNotFound,
+                     "FindRecord() did not fail with bad index");
+    }
+
+    for (index = 0; index < kAdditionalCount; index++)
+    {
+        offset = additionalSectionOffset;
+        // Find record with empty name (matching any) and any type.
+        SuccessOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, index, Dns::Name(), record),
+                      "FindRecord() failed");
+    }
+
+    offset = additionalSectionOffset;
+    VerifyOrQuit(Dns::ResourceRecord::FindRecord(*message, offset, kAdditionalCount, index, Dns::Name(), record) ==
+                     kErrorNotFound,
+                 "FindRecord() did not fail with bad index");
+
+    message->Free();
+    testFreeInstance(instance);
+}
+
+void TestDnsTxtEntry(void)
+{
+    enum
+    {
+        kMaxTxtDataSize = 255,
+    };
+
+    struct EncodedTxtData
+    {
+        const uint8_t *mData;
+        uint8_t        mLength;
+    };
+
+    const char    kKey1[]   = "key";
+    const uint8_t kValue1[] = {'v', 'a', 'l', 'u', 'e'};
+
+    const char    kKey2[]   = "E";
+    const uint8_t kValue2[] = {'m', 'c', '^', '2'};
+
+    const char    kKey3[]   = "space key";
+    const uint8_t kValue3[] = {'=', 0, '='};
+
+    const char    kKey4[]   = "123456789"; // Max recommended length key
+    const uint8_t kValue4[] = {0};
+
+    const char    kKey5[]   = "1234567890"; // Longer than recommended key
+    const uint8_t kValue5[] = {'a'};
+
+    const char kKey6[] = "boolKey";  // Should be encoded as "boolKey" (without `=`).
+    const char kKey7[] = "emptyKey"; // Should be encoded as "emptyKey=".
+
+    // Invalid key
+    const char kShortKey[] = "";
+
+    const uint8_t kEncodedTxt1[] = {9, 'k', 'e', 'y', '=', 'v', 'a', 'l', 'u', 'e'};
+    const uint8_t kEncodedTxt2[] = {6, 'E', '=', 'm', 'c', '^', '2'};
+    const uint8_t kEncodedTxt3[] = {13, 's', 'p', 'a', 'c', 'e', ' ', 'k', 'e', 'y', '=', '=', 0, '='};
+    const uint8_t kEncodedTxt4[] = {11, '1', '2', '3', '4', '5', '6', '7', '8', '9', '=', 0};
+    const uint8_t kEncodedTxt5[] = {12, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '=', 'a'};
+    const uint8_t kEncodedTxt6[] = {7, 'b', 'o', 'o', 'l', 'K', 'e', 'y'};
+    const uint8_t kEncodedTxt7[] = {9, 'e', 'm', 'p', 't', 'y', 'K', 'e', 'y', '='};
+
+    const uint8_t kInvalidEncodedTxt1[] = {4, 'a', '=', 'b'}; // Incorrect length
+
+    // Special encoded txt data with zero strings and string starting
+    // with '=' (missing key) whcih should be skipped over silently.
+    const uint8_t kSpecialEncodedTxt[] = {0, 0, 3, 'A', '=', 'B', 2, '=', 'C', 3, 'D', '=', 'E', 3, '=', '1', '2'};
+
+    const Dns::TxtEntry kTxtEntries[] = {
+        Dns::TxtEntry(kKey1, kValue1, sizeof(kValue1)),
+        Dns::TxtEntry(kKey2, kValue2, sizeof(kValue2)),
+        Dns::TxtEntry(kKey3, kValue3, sizeof(kValue3)),
+        Dns::TxtEntry(kKey4, kValue4, sizeof(kValue4)),
+        Dns::TxtEntry(kKey5, kValue5, sizeof(kValue5)),
+        Dns::TxtEntry(kKey6, nullptr, 0),
+        Dns::TxtEntry(kKey7, kValue1, 0),
+    };
+
+    const EncodedTxtData kEncodedTxtData[] = {
+        {kEncodedTxt1, sizeof(kEncodedTxt1)}, {kEncodedTxt2, sizeof(kEncodedTxt2)},
+        {kEncodedTxt3, sizeof(kEncodedTxt3)}, {kEncodedTxt4, sizeof(kEncodedTxt4)},
+        {kEncodedTxt5, sizeof(kEncodedTxt5)}, {kEncodedTxt6, sizeof(kEncodedTxt6)},
+        {kEncodedTxt7, sizeof(kEncodedTxt7)}};
+
+    Instance *              instance;
+    MessagePool *           messagePool;
+    Message *               message;
+    uint8_t                 txtData[kMaxTxtDataSize];
+    uint16_t                txtDataLength;
+    uint8_t                 index;
+    Dns::TxtEntry           txtEntry;
+    Dns::TxtEntry::Iterator iterator;
+
+    printf("================================================================\n");
+    printf("TestDnsTxtEntry()\n");
+
+    instance = static_cast<Instance *>(testInitInstance());
+    VerifyOrQuit(instance != nullptr, "Null OpenThread instance");
+
+    messagePool = &instance->Get<MessagePool>();
+    VerifyOrQuit((message = messagePool->New(Message::kTypeIp6, 0)) != nullptr, "Message::New failed");
+
+    SuccessOrQuit(Dns::TxtEntry::AppendEntries(kTxtEntries, OT_ARRAY_LENGTH(kTxtEntries), *message),
+                  "TxtEntry::AppendEntries() failed");
+
+    txtDataLength = message->GetLength();
+    VerifyOrQuit(txtDataLength < kMaxTxtDataSize, "TXT data is too long");
+
+    SuccessOrQuit(message->Read(0, txtData, txtDataLength), "Failed to read txt data from message");
+    DumpBuffer("txt data", txtData, txtDataLength);
+
+    index = 0;
+    for (const EncodedTxtData &encodedData : kEncodedTxtData)
+    {
+        VerifyOrQuit(memcmp(&txtData[index], encodedData.mData, encodedData.mLength) == 0,
+                     "TxtData is incorrectly encoded");
+        index += encodedData.mLength;
+    }
+
+    iterator.Init(txtData, txtDataLength);
+
+    for (const Dns::TxtEntry &expectedTxtEntry : kTxtEntries)
+    {
+        uint8_t expectedKeyLength = static_cast<uint8_t>(strlen(expectedTxtEntry.mKey));
+
+        SuccessOrQuit(iterator.GetNextEntry(txtEntry), "TxtEntry::GetNextEntry() failed");
+        printf("key:\"%s\" valueLen:%d\n", txtEntry.mKey != nullptr ? txtEntry.mKey : "(null)", txtEntry.mValueLength);
+
+        if (expectedKeyLength > Dns::TxtEntry::kMaxKeyLength)
+        {
+            // When the key is longer than recommended max key length,
+            // the full encoded string is returned in `mValue` and
+            // `mValueLength` and `mKey` should be set to  `nullptr`.
+
+            VerifyOrQuit(txtEntry.mKey == nullptr, "TxtEntry key does not match expected value for long key");
+            VerifyOrQuit(txtEntry.mValueLength == expectedKeyLength + expectedTxtEntry.mValueLength + sizeof(char),
+                         "TxtEntry value length is incorrect for long key");
+            VerifyOrQuit(memcmp(txtEntry.mValue, expectedTxtEntry.mKey, expectedKeyLength) == 0,
+                         "txtEntry value does match expected content");
+            VerifyOrQuit(txtEntry.mValue[expectedKeyLength] == static_cast<uint8_t>('='),
+                         "txtEntry value does match expected content");
+            VerifyOrQuit(memcmp(&txtEntry.mValue[expectedKeyLength + sizeof(uint8_t)], expectedTxtEntry.mValue,
+                                expectedTxtEntry.mValueLength) == 0,
+                         "txtEntry value does match expected content");
+            continue;
+        }
+
+        VerifyOrQuit(strcmp(txtEntry.mKey, expectedTxtEntry.mKey) == 0, "TxtEntry key does not match expected value");
+        VerifyOrQuit(txtEntry.mValueLength == expectedTxtEntry.mValueLength,
+                     "TxtEntry value length does not match expected value");
+
+        if (txtEntry.mValueLength != 0)
+        {
+            VerifyOrQuit(memcmp(txtEntry.mValue, expectedTxtEntry.mValue, txtEntry.mValueLength) == 0,
+                         "TxtEntry value does not match expected content");
+        }
+        else
+        {
+            // Ensure both `txtEntry.mKey` and `expectedTxtEntry.mKey` are
+            // null or both are non-null (for boolean or empty keys).
+            VerifyOrQuit((txtEntry.mKey == nullptr) == (expectedTxtEntry.mKey == nullptr),
+                         "TxtEntry value does not match expected value for bool or empty key");
+        }
+    }
+
+    VerifyOrQuit(iterator.GetNextEntry(txtEntry) == kErrorNotFound, "GetNextEntry() returned unexpected entry");
+    VerifyOrQuit(iterator.GetNextEntry(txtEntry) == kErrorNotFound, "GetNextEntry() succeeded after done");
+
+    // Verify `AppendEntries()` correctly rejecting invalid key
+    txtEntry.mValue       = kValue1;
+    txtEntry.mValueLength = sizeof(kValue1);
+    txtEntry.mKey         = kShortKey;
+    VerifyOrQuit(Dns::TxtEntry::AppendEntries(&txtEntry, 1, *message) == kErrorInvalidArgs,
+                 "AppendEntries() did not fail with invalid key");
+
+    // Verify appending empty txt data
+
+    SuccessOrQuit(message->SetLength(0), "Message::SetLength() failed");
+    SuccessOrQuit(Dns::TxtEntry::AppendEntries(nullptr, 0, *message), "AppendEntries() failed with empty array");
+    txtDataLength = message->GetLength();
+    VerifyOrQuit(txtDataLength == sizeof(uint8_t), "Data length is incorrect with empty array");
+    SuccessOrQuit(message->Read(0, txtData, txtDataLength), "Failed to read txt data from message");
+    VerifyOrQuit(txtData[0] == 0, "Data is invalid with empty array");
+
+    SuccessOrQuit(message->SetLength(0), "Message::SetLength() failed");
+    txtEntry.mKey         = nullptr;
+    txtEntry.mValue       = nullptr;
+    txtEntry.mValueLength = 0;
+    SuccessOrQuit(Dns::TxtEntry::AppendEntries(&txtEntry, 1, *message), "AppendEntries() failed with empty entry");
+    txtDataLength = message->GetLength();
+    VerifyOrQuit(txtDataLength == sizeof(uint8_t), "Data length is incorrect with empty entry");
+    SuccessOrQuit(message->Read(0, txtData, txtDataLength), "Failed to read txt data from message");
+    VerifyOrQuit(txtData[0] == 0, "Data is invalid with empty entry");
+
+    // Verify `Iterator` behavior with invalid txt data.
+
+    iterator.Init(kInvalidEncodedTxt1, sizeof(kInvalidEncodedTxt1));
+    VerifyOrQuit(iterator.GetNextEntry(txtEntry) == kErrorParse, "GetNextEntry() did not fail with invalid data");
+
+    // Verify `GetNextEntry()` correctly skipping over empty strings and
+    // strings starting with '=' (missing key) in encoded txt.
+    //
+    // kSpecialEncodedTxt:
+    // { 0, 3, 'A', '=', 'B', 2, '=', 'C', 3, 'D', '=', 'E', 3, '=', '1', '2' }
+
+    iterator.Init(kSpecialEncodedTxt, sizeof(kSpecialEncodedTxt));
+
+    // We should get "A=B` (or key="A", and value="B")
+    SuccessOrQuit(iterator.GetNextEntry(txtEntry), "GetNextEntry() failed");
+    VerifyOrQuit((txtEntry.mKey[0] == 'A') && (txtEntry.mKey[1] == '\0'), "GetNextEntry() got incorrect key");
+    VerifyOrQuit((txtEntry.mValueLength == 1) && (txtEntry.mValue[0] == 'B'), "GetNextEntry() got incorrect value");
+
+    // We should get "D=E` (or key="D", and value="E")
+    SuccessOrQuit(iterator.GetNextEntry(txtEntry), "GetNextEntry() failed");
+    VerifyOrQuit((txtEntry.mKey[0] == 'D') && (txtEntry.mKey[1] == '\0'), "GetNextEntry() got incorrect key");
+    VerifyOrQuit((txtEntry.mValueLength == 1) && (txtEntry.mValue[0] == 'E'), "GetNextEntry() got incorrect value");
+
+    VerifyOrQuit(iterator.GetNextEntry(txtEntry) == kErrorNotFound, "GetNextEntry() returned extra entry");
+
     message->Free();
     testFreeInstance(instance);
 }
@@ -470,6 +1308,8 @@
 {
     ot::TestDnsName();
     ot::TestDnsCompressedName();
+    ot::TestHeaderAndResourceRecords();
+    ot::TestDnsTxtEntry();
 
     printf("All tests passed\n");
     return 0;
diff --git a/tests/unit/test_ecdsa.cpp b/tests/unit/test_ecdsa.cpp
index b52bb04..7148f19 100644
--- a/tests/unit/test_ecdsa.cpp
+++ b/tests/unit/test_ecdsa.cpp
@@ -101,7 +101,7 @@
 
     printf("\nHash the message ----------------------------------------------------------\n");
     sha256.Start();
-    sha256.Update(kMessage, sizeof(kMessage));
+    sha256.Update(kMessage);
     sha256.Finish(hash);
 
     DumpBuffer("Hash", hash.GetBytes(), sizeof(hash));
@@ -153,7 +153,7 @@
 
     printf("\nHash the message ----------------------------------------------------------\n");
     sha256.Start();
-    sha256.Update(reinterpret_cast<const uint8_t *>(kMessage), sizeof(kMessage) - 1);
+    sha256.Update(kMessage, sizeof(kMessage) - 1);
     sha256.Finish(hash);
 
     DumpBuffer("Hash", hash.GetBytes(), sizeof(hash));
@@ -167,10 +167,9 @@
     printf("\nSignature was verified successfully.");
 
     sha256.Start();
-    sha256.Update(reinterpret_cast<const uint8_t *>(kMessage), sizeof(kMessage)); // include null char
+    sha256.Update(kMessage, sizeof(kMessage)); // include null char
     sha256.Finish(hash);
-    VerifyOrQuit(publicKey.Verify(hash, signature) != OT_ERROR_NONE,
-                 "PublicKey::Verify() passed for invalid signature");
+    VerifyOrQuit(publicKey.Verify(hash, signature) != kErrorNone, "PublicKey::Verify() passed for invalid signature");
     printf("\nSignature verification correctly failed with incorrect hash/signature.\n\n");
 
     testFreeInstance(instance);
diff --git a/tests/unit/test_flash.cpp b/tests/unit/test_flash.cpp
index 4710a30..6304da9 100644
--- a/tests/unit/test_flash.cpp
+++ b/tests/unit/test_flash.cpp
@@ -57,8 +57,8 @@
 
     // No records in settings
 
-    VerifyOrQuit(flash.Delete(0, 0) == OT_ERROR_NOT_FOUND, "Delete() failed");
-    VerifyOrQuit(flash.Get(0, 0, nullptr, nullptr) == OT_ERROR_NOT_FOUND, "Get() failed");
+    VerifyOrQuit(flash.Delete(0, 0) == kErrorNotFound, "Delete() failed");
+    VerifyOrQuit(flash.Get(0, 0, nullptr, nullptr) == kErrorNotFound, "Get() failed");
 
     // Multiple records with different keys
 
@@ -85,8 +85,8 @@
 
     for (uint16_t key = 0; key < 16; key++)
     {
-        VerifyOrQuit(flash.Delete(key, 0) == OT_ERROR_NOT_FOUND, "Delete() failed");
-        VerifyOrQuit(flash.Get(key, 0, nullptr, nullptr) == OT_ERROR_NOT_FOUND, "Get() failed");
+        VerifyOrQuit(flash.Delete(key, 0) == kErrorNotFound, "Delete() failed");
+        VerifyOrQuit(flash.Get(key, 0, nullptr, nullptr) == kErrorNotFound, "Get() failed");
     }
 
     // Multiple records with the same key
@@ -112,8 +112,8 @@
         SuccessOrQuit(flash.Delete(0, 0), "Delete() failed");
     }
 
-    VerifyOrQuit(flash.Delete(0, 0) == OT_ERROR_NOT_FOUND, "Delete() failed");
-    VerifyOrQuit(flash.Get(0, 0, nullptr, nullptr) == OT_ERROR_NOT_FOUND, "Get() failed");
+    VerifyOrQuit(flash.Delete(0, 0) == kErrorNotFound, "Delete() failed");
+    VerifyOrQuit(flash.Get(0, 0, nullptr, nullptr) == kErrorNotFound, "Get() failed");
 
     // Multiple records with the same key
 
@@ -145,8 +145,8 @@
         SuccessOrQuit(flash.Delete(0, 0), "Delete() failed");
     }
 
-    VerifyOrQuit(flash.Delete(0, 0) == OT_ERROR_NOT_FOUND, "Delete() failed");
-    VerifyOrQuit(flash.Get(0, 0, nullptr, nullptr) == OT_ERROR_NOT_FOUND, "Get() failed");
+    VerifyOrQuit(flash.Delete(0, 0) == kErrorNotFound, "Delete() failed");
+    VerifyOrQuit(flash.Get(0, 0, nullptr, nullptr) == kErrorNotFound, "Get() failed");
 
     // Wipe()
 
@@ -161,8 +161,8 @@
 
     for (uint16_t key = 0; key < 16; key++)
     {
-        VerifyOrQuit(flash.Delete(key, 0) == OT_ERROR_NOT_FOUND, "Delete() failed");
-        VerifyOrQuit(flash.Get(key, 0, nullptr, nullptr) == OT_ERROR_NOT_FOUND, "Get() failed");
+        VerifyOrQuit(flash.Delete(key, 0) == kErrorNotFound, "Delete() failed");
+        VerifyOrQuit(flash.Get(key, 0, nullptr, nullptr) == kErrorNotFound, "Get() failed");
     }
 
     // Test swap
diff --git a/tests/unit/test_heap.cpp b/tests/unit/test_heap.cpp
index 651d60a..813d200 100644
--- a/tests/unit/test_heap.cpp
+++ b/tests/unit/test_heap.cpp
@@ -38,6 +38,8 @@
 #include "test_platform.h"
 #include "test_util.h"
 
+#if !OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
+
 /**
  * Verifies single variable allocating and freeing.
  *
@@ -171,9 +173,13 @@
     TestAllocateMultiple();
 }
 
+#endif // !OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
+
 int main(void)
 {
+#if !OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
     RunTimerTests();
     printf("All tests passed\n");
+#endif // !OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
     return 0;
 }
diff --git a/tests/unit/test_hmac_sha256.cpp b/tests/unit/test_hmac_sha256.cpp
index 676f966..1623b70 100644
--- a/tests/unit/test_hmac_sha256.cpp
+++ b/tests/unit/test_hmac_sha256.cpp
@@ -29,60 +29,266 @@
 #include <openthread/config.h>
 
 #include "common/debug.hpp"
+#include "common/message.hpp"
 #include "crypto/hmac_sha256.hpp"
+#include "crypto/sha256.hpp"
 
 #include "test_platform.h"
 #include "test_util.h"
 
-void TestHmacSha256(void)
+namespace ot {
+
+void TestSha256(void)
 {
-    static const struct
+    const char kData1[] = "abc";
+
+    const otCryptoSha256Hash kHash1 = {{
+        0xba, 0x78, 0x16, 0xbf, 0x8f, 0x01, 0xcf, 0xea, 0x41, 0x41, 0x40, 0xde, 0x5d, 0xae, 0x22, 0x23,
+        0xb0, 0x03, 0x61, 0xa3, 0x96, 0x17, 0x7a, 0x9c, 0xb4, 0x10, 0xff, 0x61, 0xf2, 0x00, 0x15, 0xad,
+    }};
+
+    const char kData2[] = "";
+
+    const otCryptoSha256Hash kHash2 = {{
+        0xe3, 0xb0, 0xc4, 0x42, 0x98, 0xfc, 0x1c, 0x14, 0x9a, 0xfb, 0xf4, 0xc8, 0x99, 0x6f, 0xb9, 0x24,
+        0x27, 0xae, 0x41, 0xe4, 0x64, 0x9b, 0x93, 0x4c, 0xa4, 0x95, 0x99, 0x1b, 0x78, 0x52, 0xb8, 0x55,
+    }};
+
+    const char kData3[] = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
+
+    const otCryptoSha256Hash kHash3 = {{
+        0x24, 0x8d, 0x6a, 0x61, 0xd2, 0x06, 0x38, 0xb8, 0xe5, 0xc0, 0x26, 0x93, 0x0c, 0x3e, 0x60, 0x39,
+        0xa3, 0x3c, 0xe4, 0x59, 0x64, 0xff, 0x21, 0x67, 0xf6, 0xec, 0xed, 0xd4, 0x19, 0xdb, 0x06, 0xc1,
+    }};
+
+    const char kData4[] = "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmno"
+                          "pqrsmnopqrstnopqrstu";
+
+    const otCryptoSha256Hash kHash4 = {{
+        0xcf, 0x5b, 0x16, 0xa7, 0x78, 0xaf, 0x83, 0x80, 0x03, 0x6c, 0xe5, 0x9e, 0x7b, 0x04, 0x92, 0x37,
+        0x0b, 0x24, 0x9b, 0x11, 0xe8, 0xf0, 0x7a, 0x51, 0xaf, 0xac, 0x45, 0x03, 0x7a, 0xfe, 0xe9, 0xd1,
+    }};
+
+    struct TestCase
     {
-        const char *       key;
-        const char *       data;
-        otCryptoSha256Hash hash;
-    } tests[] = {
-        {
-            "\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b",
-            "Hi There",
-            {{
-                0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0x0b, 0xf1, 0x2b,
-                0x88, 0x1d, 0xc2, 0x00, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32, 0xcf, 0xf7,
-            }},
-        },
-        {
-            nullptr,
-            nullptr,
-            {},
-        },
+        const char *       mData; // (null-terminated string).
+        otCryptoSha256Hash mHash;
     };
 
-    otInstance *instance = testInitInstance();
+    static const TestCase kTestCases[] = {
+        {kData1, kHash1},
+        {kData2, kHash2},
+        {kData3, kHash3},
+        {kData4, kHash4},
+    };
 
-    // Make sure hmac is destructed before freeing instance.
+    printf("TestSha256\n");
+
+    Instance *   instance = testInitInstance();
+    MessagePool *messagePool;
+    Message *    message;
+    uint16_t     offsets[OT_ARRAY_LENGTH(kTestCases)];
+    uint8_t      index;
+
+    VerifyOrQuit(instance != nullptr, "Null OpenThread instance");
+
+    messagePool = &instance->Get<MessagePool>();
+    VerifyOrQuit((message = messagePool->New(Message::kTypeIp6, 0)) != nullptr, "Message::New failed");
+
+    for (const TestCase &testCase : kTestCases)
     {
-        ot::Crypto::HmacSha256       hmac;
-        ot::Crypto::HmacSha256::Hash hash;
+        Crypto::Sha256       sha256;
+        Crypto::Sha256::Hash hash;
 
-        VerifyOrQuit(instance != nullptr, "Null OpenThread instance");
+        sha256.Start();
+        sha256.Update(testCase.mData, static_cast<uint16_t>(strlen(testCase.mData)));
+        sha256.Finish(hash);
 
-        for (int i = 0; tests[i].key != nullptr; i++)
-        {
-            hmac.Start(reinterpret_cast<const uint8_t *>(tests[i].key), static_cast<uint16_t>(strlen(tests[i].key)));
-            hmac.Update(reinterpret_cast<const uint8_t *>(tests[i].data), static_cast<uint16_t>(strlen(tests[i].data)));
-            hmac.Finish(hash);
+        VerifyOrQuit(hash == static_cast<const Crypto::HmacSha256::Hash &>(testCase.mHash), "HMAC-SHA-256 failed");
+    }
 
-            VerifyOrQuit(hash == static_cast<const ot::Crypto::HmacSha256::Hash &>(tests[i].hash),
-                         "HMAC-SHA-256 failed");
-        }
+    // Append all test case `mData` in the message.
+
+    index = 0;
+
+    for (const TestCase &testCase : kTestCases)
+    {
+        SuccessOrQuit(message->Append("Hello"), "Message::Append() failed");
+        offsets[index++] = message->GetLength();
+        SuccessOrQuit(message->AppendBytes(testCase.mData, static_cast<uint16_t>(strlen(testCase.mData))),
+                      "Message::AppendBytes() failed");
+        SuccessOrQuit(message->Append("There!"), "Message::Append() failed");
+    }
+
+    index = 0;
+
+    for (const TestCase &testCase : kTestCases)
+    {
+        Crypto::Sha256       sha256;
+        Crypto::Sha256::Hash hash;
+
+        sha256.Start();
+        sha256.Update(*message, offsets[index++], static_cast<uint16_t>(strlen(testCase.mData)));
+        sha256.Finish(hash);
+
+        VerifyOrQuit(hash == static_cast<const Crypto::HmacSha256::Hash &>(testCase.mHash), "HMAC-SHA-256 failed");
     }
 
     testFreeInstance(instance);
 }
 
+void TestHmacSha256(void)
+{
+    struct TestCase
+    {
+        const void *       mKey;
+        uint16_t           mKeyLength;
+        const void *       mData;
+        uint16_t           mDataLength;
+        otCryptoSha256Hash mHash;
+    };
+
+    // Test-cases from RFC 4231.
+
+    const uint8_t kKey1[] = {
+        0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+        0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b,
+    };
+
+    const char kData1[] = "Hi There";
+
+    const otCryptoSha256Hash kHash1 = {{
+        0xb0, 0x34, 0x4c, 0x61, 0xd8, 0xdb, 0x38, 0x53, 0x5c, 0xa8, 0xaf, 0xce, 0xaf, 0x0b, 0xf1, 0x2b,
+        0x88, 0x1d, 0xc2, 0x00, 0xc9, 0x83, 0x3d, 0xa7, 0x26, 0xe9, 0x37, 0x6c, 0x2e, 0x32, 0xcf, 0xf7,
+    }};
+
+    const char kKey2[]  = "Jefe";
+    const char kData2[] = "what do ya want for nothing?";
+
+    const otCryptoSha256Hash kHash2 = {{
+        0x5b, 0xdc, 0xc1, 0x46, 0xbf, 0x60, 0x75, 0x4e, 0x6a, 0x04, 0x24, 0x26, 0x08, 0x95, 0x75, 0xc7,
+        0x5a, 0x00, 0x3f, 0x08, 0x9d, 0x27, 0x39, 0x83, 0x9d, 0xec, 0x58, 0xb9, 0x64, 0xec, 0x38, 0x43,
+    }};
+
+    const uint8_t kKey3[] = {0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+                             0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa};
+
+    const uint8_t kData3[] = {
+        0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+        0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+        0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+    };
+
+    const otCryptoSha256Hash kHash3 = {{
+        0x77, 0x3e, 0xa9, 0x1e, 0x36, 0x80, 0x0e, 0x46, 0x85, 0x4d, 0xb8, 0xeb, 0xd0, 0x91, 0x81, 0xa7,
+        0x29, 0x59, 0x09, 0x8b, 0x3e, 0xf8, 0xc1, 0x22, 0xd9, 0x63, 0x55, 0x14, 0xce, 0xd5, 0x65, 0xfe,
+    }};
+
+    const uint8_t kKey4[] = {
+        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,
+    };
+
+    const uint8_t kData4[] = {
+        0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd,
+        0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd,
+        0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd,
+    };
+
+    const otCryptoSha256Hash kHash4 = {{
+        0x82, 0x55, 0x8a, 0x38, 0x9a, 0x44, 0x3c, 0x0e, 0xa4, 0xcc, 0x81, 0x98, 0x99, 0xf2, 0x08, 0x3a,
+        0x85, 0xf0, 0xfa, 0xa3, 0xe5, 0x78, 0xf8, 0x07, 0x7a, 0x2e, 0x3f, 0xf4, 0x67, 0x29, 0x66, 0x5b,
+    }};
+
+    const uint8_t kKey5[] = {
+        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, 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, 0xaa, 0xaa, 0xaa,
+    };
+
+    const char kData5[] = "This is a test using a larger than block-size key and a larger than block-size data. The "
+                          "key needs to be hashed before being used by the HMAC algorithm.";
+
+    const otCryptoSha256Hash kHash5 = {{
+        0x9b, 0x09, 0xff, 0xa7, 0x1b, 0x94, 0x2f, 0xcb, 0x27, 0x63, 0x5f, 0xbc, 0xd5, 0xb0, 0xe9, 0x44,
+        0xbf, 0xdc, 0x63, 0x64, 0x4f, 0x07, 0x13, 0x93, 0x8a, 0x7f, 0x51, 0x53, 0x5c, 0x3a, 0x35, 0xe2,
+    }};
+
+    static const TestCase kTestCases[] = {
+        {kKey1, sizeof(kKey1), kData1, sizeof(kData1) - 1, kHash1},
+        {kKey2, sizeof(kKey2) - 1, kData2, sizeof(kData2) - 1, kHash2},
+        {kKey3, sizeof(kKey3), kData3, sizeof(kData3), kHash3},
+        {kKey4, sizeof(kKey4), kData4, sizeof(kData4), kHash4},
+        {kKey5, sizeof(kKey5), kData5, sizeof(kData5) - 1, kHash5},
+    };
+
+    Instance *   instance = testInitInstance();
+    MessagePool *messagePool;
+    Message *    message;
+    uint16_t     offsets[OT_ARRAY_LENGTH(kTestCases)];
+    uint8_t      index;
+
+    printf("TestHmacSha256\n");
+
+    VerifyOrQuit(instance != nullptr, "Null OpenThread instance");
+
+    messagePool = &instance->Get<MessagePool>();
+    VerifyOrQuit((message = messagePool->New(Message::kTypeIp6, 0)) != nullptr, "Message::New failed");
+
+    for (const TestCase &testCase : kTestCases)
+    {
+        Crypto::HmacSha256       hmac;
+        Crypto::HmacSha256::Hash hash;
+
+        hmac.Start(reinterpret_cast<const uint8_t *>(testCase.mKey), testCase.mKeyLength);
+        hmac.Update(testCase.mData, testCase.mDataLength);
+        hmac.Finish(hash);
+
+        VerifyOrQuit(hash == static_cast<const Crypto::HmacSha256::Hash &>(testCase.mHash), "HMAC-SHA-256 failed");
+    }
+
+    // Append all test case `mData` in the message.
+
+    index = 0;
+
+    for (const TestCase &testCase : kTestCases)
+    {
+        SuccessOrQuit(message->Append("Hello"), "Message::Append() failed");
+        offsets[index++] = message->GetLength();
+        SuccessOrQuit(message->AppendBytes(testCase.mData, testCase.mDataLength), "Message::AppendBytes() failed");
+        SuccessOrQuit(message->Append("There"), "Message::Append() failed");
+    }
+
+    index = 0;
+
+    for (const TestCase &testCase : kTestCases)
+    {
+        Crypto::HmacSha256       hmac;
+        Crypto::HmacSha256::Hash hash;
+
+        hmac.Start(reinterpret_cast<const uint8_t *>(testCase.mKey), testCase.mKeyLength);
+        hmac.Update(*message, offsets[index++], testCase.mDataLength);
+        hmac.Finish(hash);
+
+        VerifyOrQuit(hash == static_cast<const Crypto::HmacSha256::Hash &>(testCase.mHash), "HMAC-SHA-256 failed");
+    }
+
+    message->Free();
+
+    testFreeInstance(instance);
+}
+
+} // namespace ot
+
 int main(void)
 {
-    TestHmacSha256();
+    ot::TestSha256();
+    ot::TestHmacSha256();
     printf("All tests passed\n");
     return 0;
 }
diff --git a/tests/unit/test_ip6_address.cpp b/tests/unit/test_ip6_address.cpp
index e4a3615..0e0b1ec 100644
--- a/tests/unit/test_ip6_address.cpp
+++ b/tests/unit/test_ip6_address.cpp
@@ -40,19 +40,19 @@
 {
     const char *  mString;
     const uint8_t mAddr[OT_IP6_ADDRESS_SIZE];
-    otError       mError;
+    ot::Error     mError;
 };
 
 static void checkAddressFromString(Ip6AddressStringTestVector *aTestVector)
 {
-    otError          error;
+    ot::Error        error;
     ot::Ip6::Address address;
 
     error = address.FromString(aTestVector->mString);
 
     VerifyOrQuit(error == aTestVector->mError, "Ip6::Address::FromString returned unexpected error code");
 
-    if (error == OT_ERROR_NONE)
+    if (error == ot::kErrorNone)
     {
         VerifyOrQuit(0 == memcmp(address.mFields.m8, aTestVector->mAddr, OT_IP6_ADDRESS_SIZE),
                      "Ip6::Address::FromString parsing failed");
@@ -65,73 +65,73 @@
         // Valid full IPv6 address.
         {"0102:0304:0506:0708:090a:0b0c:0d0e:0f00",
          {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00},
-         OT_ERROR_NONE},
+         ot::kErrorNone},
 
         // Valid full IPv6 address with mixed capital and small letters.
         {"0102:0304:0506:0708:090a:0B0C:0d0E:0F00",
          {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00},
-         OT_ERROR_NONE},
+         ot::kErrorNone},
 
         // Short prefix and full IID.
         {"fd11::abcd:e0e0:d10e:0001",
          {0xfd, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xab, 0xcd, 0xe0, 0xe0, 0xd1, 0x0e, 0x00, 0x01},
-         OT_ERROR_NONE},
+         ot::kErrorNone},
 
         // Valid IPv6 address with unnecessary :: symbol.
         {"fd11:1234:5678:abcd::abcd:e0e0:d10e:1000",
          {0xfd, 0x11, 0x12, 0x34, 0x56, 0x78, 0xab, 0xcd, 0xab, 0xcd, 0xe0, 0xe0, 0xd1, 0x0e, 0x10, 0x00},
-         OT_ERROR_NONE},
+         ot::kErrorNone},
 
         // Short multicast address.
         {"ff03::0b",
          {0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b},
-         OT_ERROR_NONE},
+         ot::kErrorNone},
 
         // Unspecified address.
-        {"::", {0}, OT_ERROR_NONE},
+        {"::", {0}, ot::kErrorNone},
 
         // Valid embedded IPv4 address.
         {"64:ff9b::100.200.15.4",
          {0x00, 0x64, 0xff, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0xc8, 0x0f, 0x04},
-         OT_ERROR_NONE},
+         ot::kErrorNone},
 
         // Valid embedded IPv4 address.
         {"2001:db8::abc:def1:127.0.0.1",
          {0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xbc, 0xde, 0xf1, 0x7f, 0x00, 0x00, 0x01},
-         OT_ERROR_NONE},
+         ot::kErrorNone},
 
         // Two :: should cause a parse error.
-        {"2001:db8::a::b", {0}, OT_ERROR_PARSE},
+        {"2001:db8::a::b", {0}, ot::kErrorParse},
 
         // The "g" and "h" are not the hex characters.
-        {"2001:db8::abcd:efgh", {0}, OT_ERROR_PARSE},
+        {"2001:db8::abcd:efgh", {0}, ot::kErrorParse},
 
         // Too many colons.
-        {"1:2:3:4:5:6:7:8:9", {0}, OT_ERROR_PARSE},
+        {"1:2:3:4:5:6:7:8:9", {0}, ot::kErrorParse},
 
         // Too many characters in a single part.
-        {"2001:db8::abc:def12:1:2", {0}, OT_ERROR_PARSE},
+        {"2001:db8::abc:def12:1:2", {0}, ot::kErrorParse},
 
         // Invalid embedded IPv4 address.
-        {"64:ff9b::123.231.0.257", {0}, OT_ERROR_PARSE},
+        {"64:ff9b::123.231.0.257", {0}, ot::kErrorParse},
 
         // Invalid embedded IPv4 address.
-        {"64:ff9b::1.22.33", {0}, OT_ERROR_PARSE},
+        {"64:ff9b::1.22.33", {0}, ot::kErrorParse},
 
         // Invalid embedded IPv4 address.
-        {"64:ff9b::1.22.33.44.5", {0}, OT_ERROR_PARSE},
+        {"64:ff9b::1.22.33.44.5", {0}, ot::kErrorParse},
 
         // Invalid embedded IPv4 address.
-        {".", {0}, OT_ERROR_PARSE},
+        {".", {0}, ot::kErrorParse},
 
         // Invalid embedded IPv4 address.
-        {":.", {0}, OT_ERROR_PARSE},
+        {":.", {0}, ot::kErrorParse},
 
         // Invalid embedded IPv4 address.
-        {"::.", {0}, OT_ERROR_PARSE},
+        {"::.", {0}, ot::kErrorParse},
 
         // Invalid embedded IPv4 address.
-        {":f:0:0:c:0:f:f:.", {0}, OT_ERROR_PARSE},
+        {":f:0:0:c:0:f:f:.", {0}, ot::kErrorParse},
     };
 
     for (Ip6AddressStringTestVector &testVector : testVectors)
@@ -252,6 +252,7 @@
             VerifyOrQuit(!address2.MatchesPrefix(prefix), "Address::MatchedPrefix() failed");
 
             VerifyOrQuit(prefix == prefix, "Prefix::operator==() failed");
+            VerifyOrQuit(!(prefix < prefix), "Prefix::operator<() failed");
 
             for (uint8_t subPrefixLength = 1; subPrefixLength <= prefixLength; subPrefixLength++)
             {
@@ -266,14 +267,37 @@
                     VerifyOrQuit(prefix == subPrefix, "Prefix::operator==() failed");
                     VerifyOrQuit(prefix.IsEqual(subPrefix.GetBytes(), subPrefix.GetLength()),
                                  "Prefix::IsEqual() failed");
+                    VerifyOrQuit(!(subPrefix < prefix), "Prefix::operator<() failed");
                 }
                 else
                 {
                     VerifyOrQuit(prefix != subPrefix, "Prefix::operator!= failed");
                     VerifyOrQuit(!prefix.IsEqual(subPrefix.GetBytes(), subPrefix.GetLength()),
                                  "Prefix::IsEqual() failed");
+                    VerifyOrQuit(subPrefix < prefix, "Prefix::operator<() failed");
                 }
             }
+
+            for (uint8_t bitNumber = 0; bitNumber < prefixLength; bitNumber++)
+            {
+                ot::Ip6::Prefix prefix2;
+                uint8_t         mask  = static_cast<uint8_t>(1U << (7 - (bitNumber & 7)));
+                uint8_t         index = (bitNumber / 8);
+                bool            isPrefixSmaller;
+
+                prefix2 = prefix;
+                VerifyOrQuit(prefix == prefix2, "Prefix::operator==() failed");
+
+                // Flip the `bitNumber` bit between `prefix` and `prefix2`
+
+                prefix2.mPrefix.mFields.m8[index] ^= mask;
+                VerifyOrQuit(prefix != prefix2, "Prefix::operator==() failed");
+
+                isPrefixSmaller = ((prefix.GetBytes()[index] & mask) == 0);
+
+                VerifyOrQuit((prefix < prefix2) == isPrefixSmaller, "Prefix::operator<() failed");
+                VerifyOrQuit((prefix2 < prefix) == !isPrefixSmaller, "Prefix::operator<() failed");
+            }
         }
     }
 }
diff --git a/tests/unit/test_linked_list.cpp b/tests/unit/test_linked_list.cpp
index 5664a00..dbb948b 100644
--- a/tests/unit/test_linked_list.cpp
+++ b/tests/unit/test_linked_list.cpp
@@ -118,18 +118,18 @@
     VerifyOrQuit(list.IsEmpty(), "LinkedList::IsEmpty() failed after init");
     VerifyOrQuit(list.GetHead() == nullptr, "LinkedList::GetHead() failed after init");
     VerifyOrQuit(list.Pop() == nullptr, "LinkedList::Pop() failed when empty");
-    VerifyOrQuit(list.Find(a, prev) == OT_ERROR_NOT_FOUND, "LinkedList::Find() succeeded for a missing entry");
+    VerifyOrQuit(list.Find(a, prev) == ot::kErrorNotFound, "LinkedList::Find() succeeded for a missing entry");
 
     VerifyLinkedListContent(&list, nullptr);
 
     list.Push(a);
     VerifyOrQuit(!list.IsEmpty(), "LinkedList::IsEmpty() failed");
     VerifyLinkedListContent(&list, &a, nullptr);
-    VerifyOrQuit(list.Find(b, prev) == OT_ERROR_NOT_FOUND, "LinkedList::Find() succeeded for a missing entry");
+    VerifyOrQuit(list.Find(b, prev) == ot::kErrorNotFound, "LinkedList::Find() succeeded for a missing entry");
 
     SuccessOrQuit(list.Add(b), "LinkedList::Add() failed");
     VerifyLinkedListContent(&list, &b, &a, nullptr);
-    VerifyOrQuit(list.Find(c, prev) == OT_ERROR_NOT_FOUND, "LinkedList::Find() succeeded for a missing entry");
+    VerifyOrQuit(list.Find(c, prev) == ot::kErrorNotFound, "LinkedList::Find() succeeded for a missing entry");
 
     list.Push(c);
     VerifyLinkedListContent(&list, &c, &b, &a, nullptr);
@@ -140,14 +140,14 @@
     SuccessOrQuit(list.Add(e), "LinkedList::Add() failed");
     VerifyLinkedListContent(&list, &e, &d, &c, &b, &a, nullptr);
 
-    VerifyOrQuit(list.Add(a) == OT_ERROR_ALREADY, "LinkedList::Add() did not detect duplicate");
-    VerifyOrQuit(list.Add(b) == OT_ERROR_ALREADY, "LinkedList::Add() did not detect duplicate");
-    VerifyOrQuit(list.Add(d) == OT_ERROR_ALREADY, "LinkedList::Add() did not detect duplicate");
-    VerifyOrQuit(list.Add(e) == OT_ERROR_ALREADY, "LinkedList::Add() did not detect duplicate");
+    VerifyOrQuit(list.Add(a) == ot::kErrorAlready, "LinkedList::Add() did not detect duplicate");
+    VerifyOrQuit(list.Add(b) == ot::kErrorAlready, "LinkedList::Add() did not detect duplicate");
+    VerifyOrQuit(list.Add(d) == ot::kErrorAlready, "LinkedList::Add() did not detect duplicate");
+    VerifyOrQuit(list.Add(e) == ot::kErrorAlready, "LinkedList::Add() did not detect duplicate");
 
     VerifyOrQuit(list.Pop() == &e, "LinkedList::Pop() failed");
     VerifyLinkedListContent(&list, &d, &c, &b, &a, nullptr);
-    VerifyOrQuit(list.Find(e, prev) == OT_ERROR_NOT_FOUND, "LinkedList::Find() succeeded for a missing entry");
+    VerifyOrQuit(list.Find(e, prev) == ot::kErrorNotFound, "LinkedList::Find() succeeded for a missing entry");
 
     VerifyOrQuit(list.FindMatching(d.GetName(), prev) == &d, "List::FindMatching() failed");
     VerifyOrQuit(prev == nullptr, "List::FindMatching() failed");
@@ -166,17 +166,17 @@
     SuccessOrQuit(list.Remove(c), "LinkedList::Remove() failed");
     VerifyLinkedListContent(&list, &e, &d, &b, &a, nullptr);
 
-    VerifyOrQuit(list.Remove(c) == OT_ERROR_NOT_FOUND, "LinkedList::Remove() failed");
+    VerifyOrQuit(list.Remove(c) == ot::kErrorNotFound, "LinkedList::Remove() failed");
     VerifyLinkedListContent(&list, &e, &d, &b, &a, nullptr);
-    VerifyOrQuit(list.Find(c, prev) == OT_ERROR_NOT_FOUND, "LinkedList::Find() succeeded for a missing entry");
+    VerifyOrQuit(list.Find(c, prev) == ot::kErrorNotFound, "LinkedList::Find() succeeded for a missing entry");
 
     SuccessOrQuit(list.Remove(e), "LinkedList::Remove() failed");
     VerifyLinkedListContent(&list, &d, &b, &a, nullptr);
-    VerifyOrQuit(list.Find(e, prev) == OT_ERROR_NOT_FOUND, "LinkedList::Find() succeeded for a missing entry");
+    VerifyOrQuit(list.Find(e, prev) == ot::kErrorNotFound, "LinkedList::Find() succeeded for a missing entry");
 
     SuccessOrQuit(list.Remove(a), "LinkedList::Remove() failed");
     VerifyLinkedListContent(&list, &d, &b, nullptr);
-    VerifyOrQuit(list.Find(a, prev) == OT_ERROR_NOT_FOUND, "LinkedList::Find() succeeded for a missing entry");
+    VerifyOrQuit(list.Find(a, prev) == ot::kErrorNotFound, "LinkedList::Find() succeeded for a missing entry");
 
     list.Push(a);
     list.Push(c);
@@ -225,11 +225,11 @@
     VerifyOrQuit(list.IsEmpty(), "LinkedList::IsEmpty() failed after Clear()");
     VerifyOrQuit(list.PopAfter(nullptr) == nullptr, "LinkedList::PopAfter() failed");
     VerifyLinkedListContent(&list, nullptr);
-    VerifyOrQuit(list.Find(a, prev) == OT_ERROR_NOT_FOUND, "LinkedList::Find() succeeded for a missing entry");
+    VerifyOrQuit(list.Find(a, prev) == ot::kErrorNotFound, "LinkedList::Find() succeeded for a missing entry");
     VerifyOrQuit(list.FindMatching(b.GetName(), prev) == nullptr, "LinkedList::FindMatching() succeeded when empty");
     VerifyOrQuit(list.FindMatching(c.GetId(), prev) == nullptr, "LinkedList::FindMatching() succeeded when empty");
     VerifyOrQuit(list.RemoveMatching(a.GetName()) == nullptr, "LinkedList::RemoveMatching() succeeded when empty");
-    VerifyOrQuit(list.Remove(a) == OT_ERROR_NOT_FOUND, "LinkedList::Remove() succeeded when empty");
+    VerifyOrQuit(list.Remove(a) == ot::kErrorNotFound, "LinkedList::Remove() succeeded when empty");
 }
 
 int main(void)
diff --git a/tests/unit/test_lowpan.cpp b/tests/unit/test_lowpan.cpp
index 540ac0f..b5a5700 100644
--- a/tests/unit/test_lowpan.cpp
+++ b/tests/unit/test_lowpan.cpp
@@ -179,7 +179,7 @@
         VerifyOrQuit(sLowpan->Compress(*message, aVector.mMacSource, aVector.mMacDestination, buffer) == aVector.mError,
                      "6lo: Lowpan:Compress failed");
 
-        if (aVector.mError == OT_ERROR_NONE)
+        if (aVector.mError == kErrorNone)
         {
             uint8_t compressBytes = static_cast<uint8_t>(buffer.GetWritePointer() - result);
 
@@ -209,7 +209,7 @@
 
         message->ReadBytes(0, result, message->GetLength());
 
-        if (aVector.mError == OT_ERROR_NONE)
+        if (aVector.mError == kErrorNone)
         {
             // Append payload to the IPv6 Packet.
             memcpy(result + message->GetLength(), iphc + decompressedBytes,
@@ -270,7 +270,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -295,7 +295,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -320,7 +320,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -345,7 +345,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -369,7 +369,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -396,7 +396,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -422,7 +422,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -448,7 +448,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -473,7 +473,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -498,7 +498,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -524,7 +524,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -550,7 +550,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -575,7 +575,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -600,7 +600,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -625,7 +625,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -651,7 +651,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -677,7 +677,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform decompression test only.
     Test(testVector, false, true);
@@ -702,7 +702,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -727,7 +727,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -752,7 +752,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -777,7 +777,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -803,7 +803,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -829,7 +829,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -856,7 +856,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression test only.
     Test(testVector, true, false);
@@ -881,7 +881,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform decompression tests.
     Test(testVector, true, true);
@@ -906,7 +906,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -931,7 +931,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -956,7 +956,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -981,7 +981,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1006,7 +1006,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1031,7 +1031,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1056,7 +1056,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1084,7 +1084,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(48);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1112,7 +1112,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(48);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1140,7 +1140,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(48);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1168,7 +1168,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(48);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1196,7 +1196,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(48);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1221,7 +1221,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(40);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform only decompression test.
     Test(testVector, false, true);
@@ -1250,7 +1250,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(48);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1279,7 +1279,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(48);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1308,7 +1308,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(48);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1337,7 +1337,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(48);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1366,7 +1366,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(48);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1397,7 +1397,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(56);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1428,7 +1428,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(64);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1459,7 +1459,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(64);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1491,7 +1491,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(56);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1528,7 +1528,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(96);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1557,7 +1557,7 @@
     // Set payload and error.
     testVector.SetPayload(sTestPayloadDefault, sizeof(sTestPayloadDefault));
     testVector.SetPayloadOffset(80);
-    testVector.SetError(OT_ERROR_NONE);
+    testVector.SetError(kErrorNone);
 
     // Perform compression and decompression tests.
     Test(testVector, true, true);
@@ -1576,7 +1576,7 @@
     testVector.SetIphcHeader(iphc, sizeof(iphc));
 
     // Set payload and error.
-    testVector.SetError(OT_ERROR_PARSE);
+    testVector.SetError(kErrorParse);
 
     // Perform decompression test.
     Test(testVector, false, true);
@@ -1595,7 +1595,7 @@
     testVector.SetIphcHeader(iphc, sizeof(iphc));
 
     // Set payload and error.
-    testVector.SetError(OT_ERROR_PARSE);
+    testVector.SetError(kErrorParse);
 
     // Perform decompression test.
     Test(testVector, false, true);
@@ -1614,7 +1614,7 @@
     testVector.SetIphcHeader(iphc, sizeof(iphc));
 
     // Set payload and error.
-    testVector.SetError(OT_ERROR_PARSE);
+    testVector.SetError(kErrorParse);
 
     // Perform decompression test.
     Test(testVector, false, true);
@@ -1633,7 +1633,7 @@
     testVector.SetIphcHeader(iphc, sizeof(iphc));
 
     // Set payload and error.
-    testVector.SetError(OT_ERROR_PARSE);
+    testVector.SetError(kErrorParse);
 
     // Perform decompression test.
     Test(testVector, false, true);
@@ -1652,7 +1652,7 @@
     testVector.SetIphcHeader(iphc, sizeof(iphc));
 
     // Set payload and error.
-    testVector.SetError(OT_ERROR_PARSE);
+    testVector.SetError(kErrorParse);
 
     // Perform decompression test.
     Test(testVector, false, true);
@@ -1671,7 +1671,7 @@
     testVector.SetIphcHeader(iphc, sizeof(iphc));
 
     // Set payload and error.
-    testVector.SetError(OT_ERROR_PARSE);
+    testVector.SetError(kErrorParse);
 
     // Perform decompression test.
     Test(testVector, false, true);
@@ -1690,7 +1690,7 @@
     testVector.SetIphcHeader(iphc, sizeof(iphc));
 
     // Set payload and error.
-    testVector.SetError(OT_ERROR_PARSE);
+    testVector.SetError(kErrorParse);
 
     // Perform decompression test.
     Test(testVector, false, true);
@@ -1709,7 +1709,7 @@
     testVector.SetIphcHeader(iphc, sizeof(iphc));
 
     // Set payload and error.
-    testVector.SetError(OT_ERROR_PARSE);
+    testVector.SetError(kErrorParse);
 
     // Perform decompression test.
     Test(testVector, false, true);
@@ -1728,7 +1728,7 @@
     testVector.SetIphcHeader(iphc, sizeof(iphc));
 
     // Set payload and error.
-    testVector.SetError(OT_ERROR_PARSE);
+    testVector.SetError(kErrorParse);
 
     // Perform decompression test.
     Test(testVector, false, true);
@@ -1866,7 +1866,7 @@
     VerifyOrQuit(meshHeader.GetDestination() == kDestAddr, "MeshHeader::GetDestination() failed after ParseFrom()");
     VerifyOrQuit(meshHeader.GetHopsLeft() == 1, "MeshHeader::GetHopsLeft() failed after ParseFrom()");
 
-    VerifyOrQuit(meshHeader.ParseFrom(frame, length - 1, headerLength) == OT_ERROR_PARSE,
+    VerifyOrQuit(meshHeader.ParseFrom(frame, length - 1, headerLength) == kErrorParse,
                  "MeshHeader::ParseFrom() did not fail with incorrect length");
 
     //- - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -1889,7 +1889,7 @@
     VerifyOrQuit(meshHeader.GetDestination() == kDestAddr, "MeshHeader::GetDestination() failed after ParseFrom()");
     VerifyOrQuit(meshHeader.GetHopsLeft() == 0x20, "MeshHeader::GetHopsLeft() failed after ParseFrom()");
 
-    VerifyOrQuit(meshHeader.ParseFrom(frame, length - 1, headerLength) == OT_ERROR_PARSE,
+    VerifyOrQuit(meshHeader.ParseFrom(frame, length - 1, headerLength) == kErrorParse,
                  "MeshHeader::ParseFrom() did not fail with incorrect length");
 
     //- - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -1903,7 +1903,7 @@
 
     VerifyOrQuit(meshHeader.WriteTo(frame) == sizeof(kMeshHeader1), "MeshHeader::WriteTo() failed");
 
-    VerifyOrQuit(meshHeader.ParseFrom(kMeshHeader3, sizeof(kMeshHeader3) - 1, headerLength) == OT_ERROR_PARSE,
+    VerifyOrQuit(meshHeader.ParseFrom(kMeshHeader3, sizeof(kMeshHeader3) - 1, headerLength) == kErrorParse,
                  "MeshHeader::ParseFrom() did not fail with incorrect length");
 }
 
@@ -1949,7 +1949,7 @@
     VerifyOrQuit(fragHeader.GetDatagramTag() == kTag, "FragmentHeader::GetDatagramTag() failed after ParseFrom()");
     VerifyOrQuit(fragHeader.GetDatagramOffset() == 0, "FragmentHeader::GetDatagramOffset() failed after ParseFrom()");
 
-    VerifyOrQuit(fragHeader.ParseFrom(frame, length - 1, headerLength) == OT_ERROR_PARSE,
+    VerifyOrQuit(fragHeader.ParseFrom(frame, length - 1, headerLength) == kErrorParse,
                  "FragmentHeader::ParseFrom() did not fail with incorrect length");
 
     //- - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -1980,7 +1980,7 @@
     VerifyOrQuit(fragHeader.GetDatagramOffset() == kOffset,
                  "FragmentHeader::GetDatagramOffset() failed after ParseFrom()");
 
-    VerifyOrQuit(fragHeader.ParseFrom(frame, length - 1, headerLength) == OT_ERROR_PARSE,
+    VerifyOrQuit(fragHeader.ParseFrom(frame, length - 1, headerLength) == kErrorParse,
                  "FragmentHeader::ParseFrom() did not fail with incorrect length");
 
     //- - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -1993,7 +1993,7 @@
     VerifyOrQuit(fragHeader.GetDatagramTag() == kTag, "FragmentHeader::GetDatagramTag() failed after ParseFrom()");
     VerifyOrQuit(fragHeader.GetDatagramOffset() == 0, "FragmentHeader::GetDatagramOffset() failed after ParseFrom()");
 
-    VerifyOrQuit(fragHeader.ParseFrom(frame, length - 1, headerLength) == OT_ERROR_PARSE,
+    VerifyOrQuit(fragHeader.ParseFrom(frame, length - 1, headerLength) == kErrorParse,
                  "FragmentHeader::ParseFrom() did not fail with incorrect length");
 
     //- - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -2002,21 +2002,21 @@
     memcpy(frame, kInvalidFragHeader1, length);
     VerifyOrQuit(!Lowpan::FragmentHeader::IsFragmentHeader(frame, length),
                  "IsFragmentHeader() did not detect invalid header");
-    VerifyOrQuit(fragHeader.ParseFrom(frame, length, headerLength) != OT_ERROR_NONE,
+    VerifyOrQuit(fragHeader.ParseFrom(frame, length, headerLength) != kErrorNone,
                  "FragmentHeader::ParseFrom() did not fail with invalid header");
 
     length = sizeof(kInvalidFragHeader2);
     memcpy(frame, kInvalidFragHeader2, length);
     VerifyOrQuit(!Lowpan::FragmentHeader::IsFragmentHeader(frame, length),
                  "IsFragmentHeader() did not detect invalid header");
-    VerifyOrQuit(fragHeader.ParseFrom(frame, length, headerLength) != OT_ERROR_NONE,
+    VerifyOrQuit(fragHeader.ParseFrom(frame, length, headerLength) != kErrorNone,
                  "FragmentHeader::ParseFrom() did not fail with invalid header");
 
     length = sizeof(kInvalidFragHeader3);
     memcpy(frame, kInvalidFragHeader3, length);
     VerifyOrQuit(!Lowpan::FragmentHeader::IsFragmentHeader(frame, length),
                  "IsFragmentHeader() did not detect invalid header");
-    VerifyOrQuit(fragHeader.ParseFrom(frame, length, headerLength) != OT_ERROR_NONE,
+    VerifyOrQuit(fragHeader.ParseFrom(frame, length, headerLength) != kErrorNone,
                  "FragmentHeader::ParseFrom() did not fail with invalid header");
 }
 
diff --git a/tests/unit/test_lowpan.hpp b/tests/unit/test_lowpan.hpp
index e069c9c..64c52d7 100644
--- a/tests/unit/test_lowpan.hpp
+++ b/tests/unit/test_lowpan.hpp
@@ -200,7 +200,7 @@
      * @param aError  Expected result.
      *
      */
-    void SetError(otError aError) { mError = aError; }
+    void SetError(Error aError) { mError = aError; }
 
     /**
      * This method initializes IPv6 Payload (uncompressed data).
@@ -274,7 +274,7 @@
      *
      */
     Payload     mPayload;
-    otError     mError;
+    Error       mError;
     const char *mTestName;
 };
 
diff --git a/tests/unit/test_mac_frame.cpp b/tests/unit/test_mac_frame.cpp
index ef6102b..abb9372 100644
--- a/tests/unit/test_mac_frame.cpp
+++ b/tests/unit/test_mac_frame.cpp
@@ -182,12 +182,12 @@
     SuccessOrQuit(networkName.Set(Mac::NameData(kName1, sizeof(kName1))), "NetworkName::Set() failed");
     CompareNetworkName(networkName, kName1);
 
-    VerifyOrQuit(networkName.Set(Mac::NameData(kName1, sizeof(kName1))) == OT_ERROR_ALREADY,
-                 "NetworkName::Set() accepted same name without returning OT_ERROR_ALREADY");
+    VerifyOrQuit(networkName.Set(Mac::NameData(kName1, sizeof(kName1))) == kErrorAlready,
+                 "NetworkName::Set() accepted same name without returning kErrorAlready");
     CompareNetworkName(networkName, kName1);
 
-    VerifyOrQuit(networkName.Set(Mac::NameData(kName1, sizeof(kName1) - 1)) == OT_ERROR_ALREADY,
-                 "NetworkName::Set() accepted same name without returning OT_ERROR_ALREADY");
+    VerifyOrQuit(networkName.Set(Mac::NameData(kName1, sizeof(kName1) - 1)) == kErrorAlready,
+                 "NetworkName::Set() accepted same name without returning kErrorAlready");
 
     SuccessOrQuit(networkName.Set(Mac::NameData(kName2, sizeof(kName2))), "NetworkName::Set() failed");
     CompareNetworkName(networkName, kName2);
@@ -198,15 +198,15 @@
     SuccessOrQuit(networkName.Set(Mac::NameData(kLongName, sizeof(kLongName))), "NetworkName::Set() failed");
     CompareNetworkName(networkName, kLongName);
 
-    VerifyOrQuit(networkName.Set(Mac::NameData(kLongName, sizeof(kLongName) - 1)) == OT_ERROR_ALREADY,
-                 "NetworkName::Set() accepted same name without returning OT_ERROR_ALREADY");
+    VerifyOrQuit(networkName.Set(Mac::NameData(kLongName, sizeof(kLongName) - 1)) == kErrorAlready,
+                 "NetworkName::Set() accepted same name without returning kErrorAlready");
 
     SuccessOrQuit(networkName.Set(Mac::NameData(nullptr, 0)), "NetworkName::Set() failed");
     CompareNetworkName(networkName, kEmptyName);
 
     SuccessOrQuit(networkName.Set(Mac::NameData(kName1, sizeof(kName1))), "NetworkName::Set() failed");
 
-    VerifyOrQuit(networkName.Set(Mac::NameData(kTooLongName, sizeof(kTooLongName))) == OT_ERROR_INVALID_ARGS,
+    VerifyOrQuit(networkName.Set(Mac::NameData(kTooLongName, sizeof(kTooLongName))) == kErrorInvalidArgs,
                  "NetworkName::Set() accepted an invalid (too long) name");
 
     CompareNetworkName(networkName, kName1);
@@ -310,7 +310,7 @@
     index   = 0;
     channel = Mac::ChannelMask::kChannelIteratorFirst;
 
-    while (aMask.GetNextChannel(channel) == OT_ERROR_NONE)
+    while (aMask.GetNextChannel(channel) == kErrorNone)
     {
         VerifyOrQuit(channel == aChannels[index++], "ChannelMask.GetNextChannel() failed");
     }
@@ -424,7 +424,7 @@
     uint8_t mac_cmd_psdu2[] = {0x6b, 0xaa, 0x8d, 0xce, 0xfa, 0x00, 0x68, 0x01, 0x68, 0x0d,
                                0x08, 0x00, 0x00, 0x00, 0x01, 0x04, 0x0d, 0xed, 0x0b, 0x35,
                                0x0c, 0x80, 0x3f, 0x04, 0x4b, 0x88, 0x89, 0xd6, 0x59, 0xe1};
-    otError error;
+    Error   error;
     uint8_t scf; // SecurityControlField
 #endif
 
@@ -469,11 +469,11 @@
     VerifyOrQuit(frame.IsDstPanIdPresent() == true, "Mac::Frame::IsDstPanIdPresent failed\n");
     VerifyOrQuit(frame.IsDstAddrPresent() == true, "Mac::Frame::IsDstAddrPresent failed\n");
     VerifyOrQuit(frame.IsSrcAddrPresent() == true, "Mac::Frame::IsSrcAddrPresent failed\n");
-    VerifyOrQuit((error = frame.GetSecurityControlField(scf)) == OT_ERROR_NONE,
+    VerifyOrQuit((error = frame.GetSecurityControlField(scf)) == kErrorNone,
                  "Mac::Frame::GetSecurityControlField failed\n");
     VerifyOrQuit(scf == 0x0d, "Mac::Frame::GetSecurityControlField value failed\n");
     frame.SetSecurityControlField(0xff);
-    VerifyOrQuit((error = frame.GetSecurityControlField(scf)) == OT_ERROR_NONE,
+    VerifyOrQuit((error = frame.GetSecurityControlField(scf)) == kErrorNone,
                  "Mac::Frame::GetSecurityControlField failed\n");
     VerifyOrQuit(scf == 0xff, "Mac::Frame::SetSecurityControlField value failed\n");
 #endif // OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
@@ -487,11 +487,11 @@
     VerifyOrQuit(frame.GetSequence() == 133, "Mac::Frame::GetSequence failed\n");
     VerifyOrQuit(frame.GetVersion() == Mac::Frame::kFcfFrameVersion2006, "Mac::Frame::GetVersion failed\n");
     VerifyOrQuit(frame.GetType() == Mac::Frame::kFcfFrameMacCmd, "Mac::Frame::GetType failed\n");
-    VerifyOrQuit(frame.GetCommandId(commandId) == OT_ERROR_NONE, "Mac::Frame::GetCommandId failed\n");
+    VerifyOrQuit(frame.GetCommandId(commandId) == kErrorNone, "Mac::Frame::GetCommandId failed\n");
     VerifyOrQuit(commandId == Mac::Frame::kMacCmdDataRequest, "Mac::Frame::GetCommandId value not correct\n");
-    VerifyOrQuit(frame.SetCommandId(Mac::Frame::kMacCmdBeaconRequest) == OT_ERROR_NONE,
+    VerifyOrQuit(frame.SetCommandId(Mac::Frame::kMacCmdBeaconRequest) == kErrorNone,
                  "Mac::Frame::SetCommandId failed\n");
-    VerifyOrQuit(frame.GetCommandId(commandId) == OT_ERROR_NONE, "Mac::Frame::GetCommandId failed\n");
+    VerifyOrQuit(frame.GetCommandId(commandId) == kErrorNone, "Mac::Frame::GetCommandId failed\n");
     VerifyOrQuit(commandId == Mac::Frame::kMacCmdBeaconRequest, "Mac::Frame::SetCommandId value not correct\n");
 
 #if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
@@ -506,12 +506,12 @@
     VerifyOrQuit(frame.GetSequence() == 141, "Mac::Frame::GetSequence failed\n");
     VerifyOrQuit(frame.IsVersion2015() == true, "Mac::Frame::IsVersion2015 failed\n");
     VerifyOrQuit(frame.GetType() == Mac::Frame::kFcfFrameMacCmd, "Mac::Frame::GetVersion failed\n");
-    VerifyOrQuit(frame.GetCommandId(commandId) == OT_ERROR_NONE, "Mac::Frame::GetCommandId failed\n");
+    VerifyOrQuit(frame.GetCommandId(commandId) == kErrorNone, "Mac::Frame::GetCommandId failed\n");
     VerifyOrQuit(commandId == Mac::Frame::kMacCmdDataRequest, "Mac::Frame::GetCommandId value not correct\n");
     printf("commandId:%d\n", commandId);
-    VerifyOrQuit(frame.SetCommandId(Mac::Frame::kMacCmdOrphanNotification) == OT_ERROR_NONE,
+    VerifyOrQuit(frame.SetCommandId(Mac::Frame::kMacCmdOrphanNotification) == kErrorNone,
                  "Mac::Frame::SetCommandId failed\n");
-    VerifyOrQuit(frame.GetCommandId(commandId) == OT_ERROR_NONE, "Mac::Frame::GetCommandId failed\n");
+    VerifyOrQuit(frame.GetCommandId(commandId) == kErrorNone, "Mac::Frame::GetCommandId failed\n");
     VerifyOrQuit(commandId == Mac::Frame::kMacCmdOrphanNotification, "Mac::Frame::SetCommandId value not correct\n");
 
 #endif
@@ -620,7 +620,7 @@
     Mac::CslIe *csl;
 
     IgnoreError(ackFrame.GenerateEnhAck(receivedFrame, false, ie_data, sizeof(ie_data)));
-    csl = reinterpret_cast<Mac::CslIe *>(ackFrame.GetHeaderIe(Mac::Frame::kHeaderIeCsl) + sizeof(Mac::HeaderIe));
+    csl = reinterpret_cast<Mac::CslIe *>(ackFrame.GetHeaderIe(Mac::CslIe::kHeaderIeId) + sizeof(Mac::HeaderIe));
     VerifyOrQuit(ackFrame.mLength == 23,
                  "Mac::Frame::GenerateEnhAck() failed, length incorrect\n"); // 23 is the length of the correct ack
     VerifyOrQuit(ackFrame.GetType() == Mac::Frame::kFcfFrameAck,
@@ -642,7 +642,7 @@
 
 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
     ackFrame.SetCslIe(123, 456);
-    csl = reinterpret_cast<Mac::CslIe *>(ackFrame.GetHeaderIe(Mac::Frame::kHeaderIeCsl) + sizeof(Mac::HeaderIe));
+    csl = reinterpret_cast<Mac::CslIe *>(ackFrame.GetHeaderIe(Mac::CslIe::kHeaderIeId) + sizeof(Mac::HeaderIe));
     VerifyOrQuit(csl->GetPeriod() == 123 && csl->GetPhase() == 456, "Mac::Frame::SetCslIe failed, CslIe incorrect\n");
 #endif
 #endif // (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
diff --git a/tests/unit/test_message.cpp b/tests/unit/test_message.cpp
index 9a2e50e..dace851 100644
--- a/tests/unit/test_message.cpp
+++ b/tests/unit/test_message.cpp
@@ -67,6 +67,8 @@
     message->WriteBytes(0, writeBuffer, kMaxSize);
     SuccessOrQuit(message->Read(0, readBuffer, kMaxSize), "Message::Read failed");
     VerifyOrQuit(memcmp(writeBuffer, readBuffer, kMaxSize) == 0, "Message compare failed");
+    VerifyOrQuit(message->CompareBytes(0, readBuffer, kMaxSize), "Message::CompareBytes failed");
+    VerifyOrQuit(message->Compare(0, readBuffer), "Message::Compare failed");
     VerifyOrQuit(message->GetLength() == kMaxSize, "Message::GetLength failed");
 
     for (uint16_t offset = 0; offset < kMaxSize; offset++)
@@ -82,11 +84,30 @@
 
             SuccessOrQuit(message->Read(0, readBuffer, kMaxSize), "Message::Read failed");
             VerifyOrQuit(memcmp(writeBuffer, readBuffer, kMaxSize) == 0, "Message compare failed");
+            VerifyOrQuit(message->Compare(0, writeBuffer), "Message::Compare() failed");
 
             memset(readBuffer, 0, sizeof(readBuffer));
             SuccessOrQuit(message->Read(offset, readBuffer, length), "Message::Read failed");
             VerifyOrQuit(memcmp(readBuffer, &writeBuffer[offset], length) == 0, "Message compare failed");
             VerifyOrQuit(memcmp(&readBuffer[length], zeroBuffer, kMaxSize - length) == 0, "Message read after length");
+
+            VerifyOrQuit(message->CompareBytes(offset, &writeBuffer[offset], length), "Message::CompareBytes() failed");
+
+            if (length == 0)
+            {
+                continue;
+            }
+
+            // Change the first byte, and then last byte, and verify that
+            // `CompareBytes()` correctly fails.
+
+            writeBuffer[offset]++;
+            VerifyOrQuit(!message->CompareBytes(offset, &writeBuffer[offset], length), "CompareBytes() failed");
+            writeBuffer[offset]--;
+
+            writeBuffer[offset + length - 1]++;
+            VerifyOrQuit(!message->CompareBytes(offset, &writeBuffer[offset], length), "CompareBytes() failed");
+            writeBuffer[offset + length - 1]--;
         }
 
         // Verify `ReadBytes()` behavior when requested read length goes beyond available bytes in the message.
@@ -97,10 +118,14 @@
 
             memset(readBuffer, 0, sizeof(readBuffer));
             readLength = message->ReadBytes(offset, readBuffer, length);
-            VerifyOrQuit(readLength <= length, "Message::ReadBytes() returned longer length");
+
+            VerifyOrQuit(readLength < length, "Message::ReadBytes() returned longer length");
             VerifyOrQuit(readLength == kMaxSize - offset, "Message::Read failed");
             VerifyOrQuit(memcmp(readBuffer, &writeBuffer[offset], readLength) == 0, "Message compare failed");
             VerifyOrQuit(memcmp(&readBuffer[readLength], zeroBuffer, kMaxSize - readLength) == 0, "read after length");
+
+            VerifyOrQuit(!message->CompareBytes(offset, readBuffer, length), "Message::CompareBytes failed");
+            VerifyOrQuit(message->CompareBytes(offset, readBuffer, readLength), "Message::CompareBytes failed");
         }
     }
 
@@ -140,6 +165,11 @@
                 VerifyOrQuit(
                     memcmp(&readBuffer[dstOffset + bytesCopied], zeroBuffer, kMaxSize - bytesCopied - dstOffset) == 0,
                     "read after length");
+
+                VerifyOrQuit(message->CompareBytes(srcOffset, *message2, dstOffset, bytesCopied),
+                             "Message::CompareBytes with two messages failed");
+                VerifyOrQuit(message2->CompareBytes(dstOffset, *message, srcOffset, bytesCopied),
+                             "Message::CompareBytes with two messages failed");
             }
         }
     }
diff --git a/tests/unit/test_multicast_listeners_table.cpp b/tests/unit/test_multicast_listeners_table.cpp
index 70efcde..8c1922c 100644
--- a/tests/unit/test_multicast_listeners_table.cpp
+++ b/tests/unit/test_multicast_listeners_table.cpp
@@ -92,10 +92,10 @@
     SuccessOrQuit(table.Add(static_cast<const Ip6::Address &>(MA501), TimerMilli::GetNow()), "Add failed");
     VerifyOrQuit(table.Count() == 2, "Table count is wrong");
 
-    // Add invalid MAs should fail with OT_ERROR_INVALID_ARGS
-    VerifyOrQuit(table.Add(static_cast<const Ip6::Address &>(MA201), TimerMilli::GetNow()) == OT_ERROR_INVALID_ARGS,
+    // Add invalid MAs should fail with kErrorInvalidArgs
+    VerifyOrQuit(table.Add(static_cast<const Ip6::Address &>(MA201), TimerMilli::GetNow()) == kErrorInvalidArgs,
                  "Add should fail");
-    VerifyOrQuit(table.Add(static_cast<const Ip6::Address &>(MA301), TimerMilli::GetNow()) == OT_ERROR_INVALID_ARGS,
+    VerifyOrQuit(table.Add(static_cast<const Ip6::Address &>(MA301), TimerMilli::GetNow()) == kErrorInvalidArgs,
                  "Add should fail");
 
     // Expire should expire outdated Listeners
@@ -117,7 +117,7 @@
     }
 
     // Now the table is full, we can't add more addresses
-    VerifyOrQuit(table.Add(static_cast<const Ip6::Address &>(MA501), TimerMilli::GetNow()) == OT_ERROR_NO_BUFS,
+    VerifyOrQuit(table.Add(static_cast<const Ip6::Address &>(MA501), TimerMilli::GetNow()) == kErrorNoBufs,
                  "Add should fail");
 
     // Expire one Listener at a time
@@ -169,7 +169,7 @@
     otBackboneRouterMulticastListenerInfo     info;
     size_t                                    table_size = 0;
 
-    while (otBackboneRouterMulticastListenerGetNext(aInstance, &iter, &info) == OT_ERROR_NONE)
+    while (otBackboneRouterMulticastListenerGetNext(aInstance, &iter, &info) == kErrorNone)
     {
         VerifyOrQuit(false, "Table should be empty");
     }
@@ -177,7 +177,7 @@
     SuccessOrQuit(otBackboneRouterMulticastListenerAdd(aInstance, &MA401, 30), "Add failed");
 
     table_size = 0, iter = OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ITERATOR_INIT;
-    while (otBackboneRouterMulticastListenerGetNext(aInstance, &iter, &info) == OT_ERROR_NONE)
+    while (otBackboneRouterMulticastListenerGetNext(aInstance, &iter, &info) == kErrorNone)
     {
         table_size++;
 
@@ -189,7 +189,7 @@
     otBackboneRouterMulticastListenerClear(aInstance);
 
     iter = OT_BACKBONE_ROUTER_MULTICAST_LISTENER_ITERATOR_INIT;
-    while (otBackboneRouterMulticastListenerGetNext(aInstance, &iter, &info) == OT_ERROR_NONE)
+    while (otBackboneRouterMulticastListenerGetNext(aInstance, &iter, &info) == kErrorNone)
     {
         VerifyOrQuit(false, "Table should be empty");
     }
diff --git a/tests/unit/test_ndproxy_table.cpp b/tests/unit/test_ndproxy_table.cpp
index 73db965..8bbdf89 100644
--- a/tests/unit/test_ndproxy_table.cpp
+++ b/tests/unit/test_ndproxy_table.cpp
@@ -58,7 +58,7 @@
 
 void TestNdProxyTable(void)
 {
-    otError error;
+    Error error;
 
     sInstance = testInitInstance();
     VerifyOrQuit(sInstance != nullptr, "Null OpenThread instance");
@@ -72,7 +72,7 @@
 
     // Reregister address IID when there are enough room should succeed.
     error = table.Register(existedAddressIid, existedMeshLocalIid, 0, nullptr);
-    VerifyOrQuit(error == OT_ERROR_NONE, "Register failed");
+    VerifyOrQuit(error == kErrorNone, "Register failed");
     VerifyOrQuit(table.IsRegistered(existedAddressIid), "should be registered");
     VerifyOrQuit(!table.IsRegistered(notExistAddressIid), "should not be registered");
 
@@ -83,17 +83,17 @@
 
         // Reregister address IID when there are enough room should succeed.
         error = table.Register(addressIid, meshLocalIid, i, nullptr);
-        VerifyOrQuit(error == OT_ERROR_NONE, "Register failed");
+        VerifyOrQuit(error == kErrorNone, "Register failed");
 
         VerifyOrQuit(table.IsRegistered(addressIid), "should be registered");
 
         // Reregister the same address IID should always succeed.
         error = table.Register(addressIid, meshLocalIid, i, nullptr);
-        VerifyOrQuit(error == OT_ERROR_NONE, "Register again failed");
+        VerifyOrQuit(error == kErrorNone, "Register again failed");
 
         // Register the same address IID with a different ML-IID should fail.
         error = table.Register(addressIid, notExistMeshLocalIid, i, nullptr);
-        VerifyOrQuit(error == OT_ERROR_DUPLICATED, "Register duplicate should fail");
+        VerifyOrQuit(error == kErrorDuplicated, "Register duplicate should fail");
 
         VerifyOrQuit(table.IsRegistered(addressIid), "should be registered");
     }
@@ -101,7 +101,7 @@
     // Now the table is full, registering another IID should fail.
     error =
         table.Register(notExistAddressIid, notExistMeshLocalIid, OPENTHREAD_CONFIG_NDPROXY_TABLE_ENTRY_NUM, nullptr);
-    VerifyOrQuit(error == OT_ERROR_NO_BUFS, "should fail with no bufs");
+    VerifyOrQuit(error == kErrorNoBufs, "should fail with no bufs");
     VerifyOrQuit(!table.IsRegistered(notExistAddressIid), "should not be registered");
 }
 
diff --git a/tests/unit/test_netif.cpp b/tests/unit/test_netif.cpp
index ca1ace0..be33e80 100644
--- a/tests/unit/test_netif.cpp
+++ b/tests/unit/test_netif.cpp
@@ -168,11 +168,11 @@
     VerifyMulticastAddressList(netif, &addresses[0], 8);
 
     IgnoreError(address.FromString(kTestAddress1)); // same as netifAddress (internal)
-    VerifyOrQuit(netif.UnsubscribeExternalMulticast(address) == OT_ERROR_INVALID_ARGS,
+    VerifyOrQuit(netif.UnsubscribeExternalMulticast(address) == kErrorInvalidArgs,
                  "UnsubscribeExternalMulticast() did not fail when address was not external");
 
     IgnoreError(address.FromString(kRealmLocalAllMpl));
-    VerifyOrQuit(netif.UnsubscribeExternalMulticast(address) == OT_ERROR_INVALID_ARGS,
+    VerifyOrQuit(netif.UnsubscribeExternalMulticast(address) == kErrorInvalidArgs,
                  "UnsubscribeExternalMulticast() did not fail when address was fixed address");
 
     netif.UnsubscribeAllRoutersMulticast();
@@ -200,7 +200,7 @@
 
     for (uint8_t i = 0; i < 5; i++)
     {
-        VerifyOrQuit(netif.SubscribeExternalMulticast(addresses[i]) == OT_ERROR_INVALID_ARGS,
+        VerifyOrQuit(netif.SubscribeExternalMulticast(addresses[i]) == kErrorInvalidArgs,
                      "SubscribeExternalMulticast() did not fail when address was a default/fixed address");
     }
 }
diff --git a/tests/unit/test_platform.cpp b/tests/unit/test_platform.cpp
index 99f156b..bb2e7cc 100644
--- a/tests/unit/test_platform.cpp
+++ b/tests/unit/test_platform.cpp
@@ -94,7 +94,7 @@
 
 extern "C" {
 
-#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
+#if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
 void *otPlatCAlloc(size_t aNum, size_t aSize)
 {
     return calloc(aNum, aSize);
@@ -691,6 +691,27 @@
 {
     OT_UNUSED_VARIABLE(aInstance);
     OT_UNUSED_VARIABLE(aShortAddress);
+
+    otLinkMetrics metrics;
+
+    memset(&metrics, 0, sizeof(metrics));
+
+    return metrics;
+}
+#endif
+
+#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
+otError otPlatInfraIfSendIcmp6Nd(uint32_t            aInfraIfIndex,
+                                 const otIp6Address *aDestAddress,
+                                 const uint8_t *     aBuffer,
+                                 uint16_t            aBufferLength)
+{
+    OT_UNUSED_VARIABLE(aInfraIfIndex);
+    OT_UNUSED_VARIABLE(aDestAddress);
+    OT_UNUSED_VARIABLE(aBuffer);
+    OT_UNUSED_VARIABLE(aBufferLength);
+
+    return OT_ERROR_FAILED;
 }
 #endif
 
diff --git a/tests/unit/test_timer.cpp b/tests/unit/test_timer.cpp
index 9ff02ab..d3b8e69 100644
--- a/tests/unit/test_timer.cpp
+++ b/tests/unit/test_timer.cpp
@@ -87,7 +87,7 @@
 {
 public:
     explicit TestTimer(ot::Instance &aInstance)
-        : TimerType(aInstance, TestTimer::HandleTimerFired, nullptr)
+        : TimerType(aInstance, TestTimer::HandleTimerFired)
         , mFiredCounter(0)
     {
     }
diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt
index f777749..96ab34a 100644
--- a/third_party/CMakeLists.txt
+++ b/third_party/CMakeLists.txt
@@ -34,8 +34,16 @@
     add_subdirectory(nxp)
 elseif(OT_PLATFORM MATCHES "cc*")
     add_subdirectory(ti)
-elseif(OT_PLATFORM STREQUAL "qpg6095")
+elseif(OT_PLATFORM MATCHES "^qpg*")
+    add_subdirectory(Qorvo)
+elseif(OT_PLATFORM STREQUAL "gp712")
     add_subdirectory(Qorvo)
 elseif(OT_PLATFORM STREQUAL "samr21")
     add_subdirectory(microchip)
+elseif(OT_PLATFORM MATCHES "^nrf*")
+    add_subdirectory(jlink)
+    add_subdirectory(NordicSemiconductor)
+elseif(OT_PLATFORM MATCHES "^efr*")
+    add_subdirectory(jlink)
+    add_subdirectory(silabs)
 endif()
diff --git a/third_party/mbedtls/mbedtls-config.h b/third_party/mbedtls/mbedtls-config.h
index e318e8f..579328b 100644
--- a/third_party/mbedtls/mbedtls-config.h
+++ b/third_party/mbedtls/mbedtls-config.h
@@ -117,7 +117,7 @@
 #define MBEDTLS_ECP_FIXED_POINT_OPTIM      0 /**< Enable fixed-point speed-up */
 #define MBEDTLS_ENTROPY_MAX_SOURCES        1 /**< Maximum number of sources supported */
 
-#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
+#if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE
 #define MBEDTLS_PLATFORM_STD_CALLOC      otPlatCAlloc /**< Default allocator to use, can be undefined */
 #define MBEDTLS_PLATFORM_STD_FREE        otPlatFree /**< Default free to use, can be undefined */
 #else
diff --git a/tools/harness-thci/OpenThread.py b/tools/harness-thci/OpenThread.py
index c5c4ed5..db354e0 100644
--- a/tools/harness-thci/OpenThread.py
+++ b/tools/harness-thci/OpenThread.py
@@ -148,14 +148,12 @@
         """
         Connect to the device.
         """
-        pass
 
     @abstractmethod
     def _disconnect(self):
         """
         Disconnect from the device
         """
-        pass
 
     @abstractmethod
     def _cliReadLine(self):
@@ -164,7 +162,6 @@
         Returns:
             None if no data
         """
-        pass
 
     @abstractmethod
     def _cliWriteLine(self, line):
@@ -173,17 +170,14 @@
         Args:
             line str: data send to device
         """
-        pass
 
     @abstractmethod
     def _onCommissionStart(self):
         """Called when commissioning starts."""
-        pass
 
     @abstractmethod
     def _onCommissionStop(self):
         """Called when commissioning stops."""
-        pass
 
     def __sendCommand(self, cmd, expectEcho=True):
         self.log("command: %s", cmd)
@@ -436,9 +430,7 @@
         print('call setAddressFilterMode() ' + mode)
         try:
             cmd = 'macfilter addr ' + mode
-            if self.__executeCommand(cmd)[-1] == 'Done':
-                return True
-            return False
+            return self.__executeCommand(cmd)[-1] == 'Done'
         except Exception as e:
             ModuleHelper.WriteIntoDebugLogger('__setAddressFilterMode() Error: ' + str(e))
 
@@ -575,6 +567,7 @@
         strIp6Prefix = prefix[:19]
         return strIp6Prefix + '::'
 
+    # pylint: disable=no-self-use
     def __convertLongToHex(self, iValue, fillZeros=None):
         """convert a long hex integer to string
            remove '0x' and 'L' return string
@@ -640,6 +633,7 @@
         self.logThreadStatus = self.logStatus['stop']
         return logs
 
+    # pylint: disable=no-self-use
     def __convertChannelMask(self, channelsArray):
         """convert channelsArray to bitmask format
 
@@ -705,6 +699,7 @@
         print('%s call getCommissionerSessionId' % self)
         return self.__executeCommand('commissioner sessionid')[0]
 
+    # pylint: disable=no-self-use
     def __escapeEscapable(self, string):
         """Escape CLI escapable characters in the given string.
 
@@ -737,7 +732,7 @@
             cmd = 'networkname %s' % networkName
             datasetCmd = 'dataset networkname %s' % networkName
             self.hasActiveDatasetToCommit = True
-            return (self.__executeCommand(cmd)[-1] == 'Done' and self.__executeCommand(datasetCmd)[-1] == 'Done')
+            return self.__executeCommand(cmd)[-1] == 'Done' and self.__executeCommand(datasetCmd)[-1] == 'Done'
         except Exception as e:
             ModuleHelper.WriteIntoDebugLogger('setNetworkName() Error: ' + str(e))
 
@@ -761,7 +756,7 @@
             cmd = 'channel %s' % channel
             datasetCmd = 'dataset channel %s' % channel
             self.hasActiveDatasetToCommit = True
-            return (self.__executeCommand(cmd)[-1] == 'Done' and self.__executeCommand(datasetCmd)[-1] == 'Done')
+            return self.__executeCommand(cmd)[-1] == 'Done' and self.__executeCommand(datasetCmd)[-1] == 'Done'
         except Exception as e:
             ModuleHelper.WriteIntoDebugLogger('setChannel() Error: ' + str(e))
 
@@ -907,7 +902,7 @@
 
             self.networkKey = masterKey
             self.hasActiveDatasetToCommit = True
-            return (self.__executeCommand(cmd)[-1] == 'Done' and self.__executeCommand(datasetCmd)[-1] == 'Done')
+            return self.__executeCommand(cmd)[-1] == 'Done' and self.__executeCommand(datasetCmd)[-1] == 'Done'
         except Exception as e:
             ModuleHelper.WriteIntoDebugLogger('setNetworkkey() Error: ' + str(e))
 
@@ -1266,7 +1261,7 @@
             cmd = 'panid %s' % panid
             datasetCmd = 'dataset panid %s' % panid
             self.hasActiveDatasetToCommit = True
-            return (self.__executeCommand(cmd)[-1] == 'Done' and self.__executeCommand(datasetCmd)[-1] == 'Done')
+            return self.__executeCommand(cmd)[-1] == 'Done' and self.__executeCommand(datasetCmd)[-1] == 'Done'
         except Exception as e:
             ModuleHelper.WriteIntoDebugLogger('setPANID() Error: ' + str(e))
 
@@ -1880,7 +1875,7 @@
 
             self.xpanId = xpanid
             self.hasActiveDatasetToCommit = True
-            return (self.__executeCommand(cmd)[-1] == 'Done' and self.__executeCommand(datasetCmd)[-1] == 'Done')
+            return self.__executeCommand(cmd)[-1] == 'Done' and self.__executeCommand(datasetCmd)[-1] == 'Done'
         except Exception as e:
             ModuleHelper.WriteIntoDebugLogger('setXpanId() Error: ' + str(e))
 
@@ -2045,7 +2040,7 @@
         if not listTLV_ids:
             return
 
-        if not len(listTLV_ids):
+        if len(listTLV_ids) == 0:
             return
 
         cmd = 'networkdiagnostic get %s %s' % (
@@ -2061,7 +2056,7 @@
         if not listTLV_ids:
             return
 
-        if not len(listTLV_ids):
+        if len(listTLV_ids) == 0:
             return
 
         cmd = 'networkdiagnostic reset %s %s' % (
@@ -2085,10 +2080,7 @@
         self.__executeCommand('ifconfig up')
         cmd = 'joiner start %s' % (strPSKc)
         print(cmd)
-        if self.__executeCommand(cmd)[-1] == 'Done':
-            return True
-        else:
-            return False
+        return self.__executeCommand(cmd)[-1] == 'Done'
 
     @API
     def startCollapsedCommissioner(self):
@@ -2835,10 +2827,7 @@
     @API
     def ValidateDeviceFirmware(self):
         print('%s call ValidateDeviceFirmware' % self)
-        if 'OPENTHREAD' in self.UIStatusMsg:
-            return True
-        else:
-            return False
+        return 'OPENTHREAD' in self.UIStatusMsg
 
     # Low power THCI
     @API
@@ -2870,7 +2859,8 @@
         print(cmd)
         return self.__executeCommand(cmd)[-1] == 'Done'
 
-    def getForwardSeriesFlagsFromHexStr(self, flags):
+    @staticmethod
+    def getForwardSeriesFlagsFromHexStr(flags):
         hexFlags = int(flags, 16)
         strFlags = ''
         if hexFlags == 0:
@@ -2887,7 +2877,8 @@
 
         return strFlags
 
-    def mapMetricsHexToChar(self, metrics):
+    @staticmethod
+    def mapMetricsHexToChar(metrics):
         metricsFlagMap = {
             0x40: 'p',
             0x09: 'q',
@@ -2896,21 +2887,22 @@
         }
         return metricsFlagMap.get(metrics, '?')
 
-    def getMetricsFlagsFromHexStr(self, metrics):
+    @staticmethod
+    def getMetricsFlagsFromHexStr(metrics):
         if metrics.startswith('0x'):
             metrics = metrics[2:]
         hexMetricsArray = bytearray.fromhex(metrics)
 
         strMetrics = ''
         for metric in hexMetricsArray:
-            strMetrics += mapMetricsHexToChar(metric)
+            strMetrics += OpenThreadTHCI.mapMetricsHexToChar(metric)
 
         return strMetrics
 
     @API
     def LinkMetricsSingleReq(self, dst_addr, metrics):
         self.log('call LinkMetricsSingleReq')
-        cmd = 'linkmetrics query %s single %s' % (dst_addr, getMetricsFlagsFromHexStr(metrics))
+        cmd = 'linkmetrics query %s single %s' % (dst_addr, self.getMetricsFlagsFromHexStr(metrics))
         print(cmd)
         return self.__executeCommand(cmd)[-1] == 'Done'
 
@@ -2919,14 +2911,14 @@
         self.log('call LinkMetricsMgmtReq')
         cmd = 'linkmetrics mgmt %s ' % dst_addr
         if type_ == 'FWD':
-            cmd += 'forward %d %s' % (series_id, getForwardSeriesFlagsFromHexStr(flags))
+            cmd += 'forward %d %s' % (series_id, self.getForwardSeriesFlagsFromHexStr(flags))
             if flags != 0:
-                cmd += ' %s' % (getMetricsFlagsFromHexStr(metrics))
+                cmd += ' %s' % (self.getMetricsFlagsFromHexStr(metrics))
         elif type_ == 'ENH':
             cmd += 'enhanced-ack'
             if flags != 0:
                 cmd += ' register'
-                metricsFlags = getMetricsFlagsFromHexStr(metrics)
+                metricsFlags = self.getMetricsFlagsFromHexStr(metrics)
                 if '?' in metricsFlags:
                     cmd += ' %s r' % metricsFlags.replace('?', '')
                 else:
@@ -2974,7 +2966,7 @@
         print(cmd1)
         cmd2 = 'udp send %s %d %s' % (destination, port, payload)
         print(cmd2)
-        return (self.__executeCommand(cmd1)[-1] == 'Done' and self.__executeCommand(cmd2)[-1] == 'Done')
+        return self.__executeCommand(cmd1)[-1] == 'Done' and self.__executeCommand(cmd2)[-1] == 'Done'
 
     @API
     def send_udp(self, interface, destination, port, payload='12ABcd'):
@@ -2986,7 +2978,7 @@
         print(cmd1)
         cmd2 = 'udp send %s %s -x %s' % (destination, port, payload)
         print(cmd2)
-        return (self.__executeCommand(cmd1)[-1] == 'Done' and self.__executeCommand(cmd2)[-1] == 'Done')
+        return self.__executeCommand(cmd1)[-1] == 'Done' and self.__executeCommand(cmd2)[-1] == 'Done'
 
     @API
     def sendMACcmd(self, enh=False):
@@ -3071,7 +3063,7 @@
             return self.__lines.pop(0)
 
         tail = ''
-        if len(self.__lines):
+        if len(self.__lines) != 0:
             tail = self.__lines.pop()
 
         try:
diff --git a/tools/otci/LICENSE b/tools/otci/LICENSE
new file mode 100644
index 0000000..d63767e
--- /dev/null
+++ b/tools/otci/LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) 2020, The OpenThread Authors.
+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.
diff --git a/tools/otci/README.md b/tools/otci/README.md
new file mode 100644
index 0000000..788f937
--- /dev/null
+++ b/tools/otci/README.md
@@ -0,0 +1,53 @@
+# OpenThread Control Interface
+
+The OpenThread Control Interface (OTCI) is a library which provides uniform python interfaces to connect and control various kinds of devices running OpenThread.
+
+## Supported device types
+
+- OpenThread CLI
+  - SOC device via Serial
+- OpenThread NCP (limited support via [pyspinel](https://pypi.org/project/pyspinel/))
+  - SOC device via Serial
+- [OpenThread Border Router](https://github.com/openthread/ot-br-posix)
+  - OTBR device via SSH
+
+## Example
+
+```python
+import otci
+
+# Connect to an OTBR device via SSH
+node1 = otci.connect_otbr_ssh("192.168.1.101")
+
+# Connect to an OpenThread CLI device via Serial
+node2 = otci.connect_cli_serial("/dev/ttyACM0"))
+
+# Start node1 to become Leader
+node1.dataset_init_buffer()
+node1.dataset_set_buffer(network_name='test', master_key='00112233445566778899aabbccddeeff', panid=0xface, channel=11)
+node1.dataset_commit_buffer('active')
+
+node1.ifconfig_up()
+node1.thread_start()
+node1.wait(5)
+assert node1.get_state() == "leader"
+
+# Start Commissioner on node1
+node1.commissioner_start()
+node1.wait(3)
+
+node1.commissioner_add_joiner("TEST123",eui64='*')
+
+# Start node2
+node2.ifconfig_up()
+node2.set_router_selection_jitter(1)
+
+# Start Joiner on node2 to join the network
+node2.joiner_start("TEST123")
+node2.wait(10, expect_line="Join success")
+
+# Wait for node 2 to become Router
+node2.thread_start()
+node2.wait(5)
+assert node2.get_state() == "router"
+```
diff --git a/tests/scripts/expect/cli-anycast.exp b/tools/otci/otci/__init__.py
similarity index 73%
copy from tests/scripts/expect/cli-anycast.exp
copy to tools/otci/otci/__init__.py
index 3081a6b..8c3a1e5 100644
--- a/tests/scripts/expect/cli-anycast.exp
+++ b/tools/otci/otci/__init__.py
@@ -1,4 +1,4 @@
-#!/usr/bin/expect -f
+#!/usr/bin/env python3
 #
 #  Copyright (c) 2020, The OpenThread Authors.
 #  All rights reserved.
@@ -27,30 +27,23 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
-source "tests/scripts/expect/_common.exp"
+from . import errors
+from .constants import THREAD_VERSION_1_1, THREAD_VERSION_1_2
+from .otci import OTCI
+from .otci import \
+    connect_cli_sim, \
+    connect_cli_serial, \
+    connect_ncp_sim, \
+    connect_cmd_handler, \
+    connect_otbr_ssh
+from .types import Rloc16, ChildId
 
+_connectors = [
+    'connect_cli_sim',
+    'connect_cli_serial',
+    'connect_ncp_sim',
+    'connect_otbr_ssh',
+    'connect_cmd_handler',
+]
 
-set spawn_id [spawn_node 1]
-
-send "panid 0xface\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-
-wait_for "state" "leader"
-expect "Done"
-
-send "ping fdde:ad00:beef:0:0:ff:fe00:fc00\n"
-expect "Done"
-expect "16 bytes from "
-expect {
-    "fdde:ad00:beef:0:0:ff:fe00:fc00" abort
-
-    -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})} {}
-
-    timeout abort
-}
-
-dispose
+__all__ = ['OTCI', 'errors', 'Rloc16', 'ChildId', 'THREAD_VERSION_1_1', 'THREAD_VERSION_1_2'] + _connectors
diff --git a/tools/otci/otci/command_handlers.py b/tools/otci/otci/command_handlers.py
new file mode 100644
index 0000000..c3b6484
--- /dev/null
+++ b/tools/otci/otci/command_handlers.py
@@ -0,0 +1,211 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2020, The OpenThread Authors.
+#  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.
+#
+import logging
+import queue
+import re
+import threading
+import time
+from abc import abstractmethod
+from typing import Union, List, Pattern
+
+from .connectors import OtCliHandler
+from .errors import ExpectLineTimeoutError, CommandError
+from .utils import match_line
+
+
+class OTCommandHandler:
+    """This abstract class defines interfaces of a OT Command Handler."""
+
+    @abstractmethod
+    def execute_command(self, cmd: str, timeout: float) -> List[str]:
+        """Method execute_command should execute the OT CLI command within a timeout (in seconds) and return the
+        command output as a list of lines.
+
+        Note: each line SHOULD NOT contain '\r\n' at the end. The last line of output should be 'Done' or
+        'Error <code>: <msg>' following OT CLI conventions.
+        """
+        pass
+
+    @abstractmethod
+    def close(self):
+        """Method close should close the OT Command Handler."""
+        pass
+
+    @abstractmethod
+    def wait(self, duration: float) -> List[str]:
+        """Method wait should wait for a given duration and return the OT CLI output during this period.
+
+        Normally, OT CLI does not output when it's not executing any command. But OT CLI can also output
+        asynchronously in some cases (e.g. `Join Success` when Joiner joins successfully).
+        """
+        pass
+
+
+class OtCliCommandRunner(OTCommandHandler):
+    __PATTERN_COMMAND_DONE_OR_ERROR = re.compile(
+        r'(Done|Error|Error \d+:.*|.*: command not found)$')  # "Error" for spinel-cli.py
+
+    __PATTERN_LOG_LINE = re.compile(r'((\[(NONE|CRIT|WARN|NOTE|INFO|DEBG)\])'
+                                    r'|(-.*-+: )'  # e.g. -CLI-----: 
+                                    r')')
+    """regex used to filter logs"""
+
+    __ASYNC_COMMANDS = {
+        'scan',
+    }
+
+    def __init__(self, otcli: OtCliHandler, is_spinel_cli=False):
+        self.__otcli: OtCliHandler = otcli
+        self.__is_spinel_cli = is_spinel_cli
+        self.__expect_command_echoback = not self.__is_spinel_cli
+
+        self.__pending_lines = queue.Queue()
+        self.__should_close = threading.Event()
+        self.__otcli_reader = threading.Thread(target=self.__otcli_read_routine)
+        self.__otcli_reader.setDaemon(True)
+        self.__otcli_reader.start()
+
+    def __repr__(self):
+        return repr(self.__otcli)
+
+    def execute_command(self, cmd, timeout=10) -> None:
+        self.__otcli.writeline(cmd)
+
+        if cmd in {'reset', 'factoryreset'}:
+            return []
+
+        if self.__expect_command_echoback:
+            self.__expect_line(timeout, cmd)
+
+        output = self.__expect_line(timeout,
+                                    OtCliCommandRunner.__PATTERN_COMMAND_DONE_OR_ERROR,
+                                    asynchronous=cmd.split()[0] in OtCliCommandRunner.__ASYNC_COMMANDS)
+        return output
+
+    def wait(self, duration: float) -> List[str]:
+        self.__otcli.wait(duration)
+
+        output = []
+        try:
+            while True:
+                line = self.__pending_lines.get_nowait()
+                output.append(line)
+
+        except queue.Empty:
+            pass
+
+        return output
+
+    def close(self):
+        self.__should_close.set()
+        self.__otcli.close()
+
+    #
+    # Private methods
+    #
+
+    def __expect_line(self, timeout: float, expect_line: Union[str, Pattern], asynchronous=False) -> List[str]:
+        output = []
+
+        if not asynchronous:
+            while True:
+                try:
+                    line = self.__pending_lines.get(timeout=timeout)
+                except queue.Empty:
+                    raise ExpectLineTimeoutError(expect_line)
+
+                output.append(line)
+
+                if match_line(line, expect_line):
+                    break
+        else:
+            done = False
+            while not done and timeout > 0:
+                lines = self.wait(1)
+                timeout -= 1
+
+                for line in lines:
+                    output.append(line)
+
+                    if match_line(line, expect_line):
+                        done = True
+                        break
+
+            if not done:
+                raise ExpectLineTimeoutError(expect_line)
+
+        return output
+
+    def __otcli_read_routine(self):
+        while not self.__should_close.isSet():
+            line = self.__otcli.readline()
+            if line.startswith('> '):
+                line = line[2:]
+
+            logging.debug('%s: %s', self.__otcli, line)
+
+            if not OtCliCommandRunner.__PATTERN_LOG_LINE.match(line):
+                self.__pending_lines.put(line)
+
+
+class OtbrSshCommandRunner(OTCommandHandler):
+
+    def __init__(self, host, port, username, password):
+        import paramiko
+
+        self.__host = host
+        self.__port = port
+        self.__ssh = paramiko.SSHClient()
+        self.__ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+        self.__ssh.connect(host,
+                           port=port,
+                           username=username,
+                           password=password,
+                           allow_agent=False,
+                           look_for_keys=False)
+
+    def __repr__(self):
+        return f'{self.__host}:{self.__port}'
+
+    def execute_command(self, cmd: str, timeout: float) -> List[str]:
+        sh_cmd = f'sudo ot-ctl "{cmd}"'
+        cmd_in, cmd_out, cmd_err = self.__ssh.exec_command(sh_cmd, timeout=int(timeout), bufsize=1024)
+        err = cmd_err.read().decode('utf-8')
+        if err:
+            raise CommandError(cmd, [err])
+
+        output = [l.rstrip('\r\n') for l in cmd_out.readlines()]
+        return output
+
+    def close(self):
+        self.__ssh.close()
+
+    def wait(self, duration: float) -> List[str]:
+        time.sleep(duration)
+        return []
diff --git a/tools/otci/otci/connectors.py b/tools/otci/otci/connectors.py
new file mode 100644
index 0000000..1785f11
--- /dev/null
+++ b/tools/otci/otci/connectors.py
@@ -0,0 +1,161 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2020, The OpenThread Authors.
+#  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.
+#
+import logging
+import subprocess
+import time
+from abc import abstractmethod
+
+
+class OtCliHandler:
+    """This abstract class defines interfaces for a OT CLI Handler."""
+
+    @abstractmethod
+    def readline(self) -> str:
+        """Method readline should return the next line read from OT CLI."""
+        pass
+
+    @abstractmethod
+    def writeline(self, s: str) -> None:
+        """Method writeline should write a line to the OT CLI.
+
+        It should block until all characters are written to OT CLI.
+        """
+        pass
+
+    @abstractmethod
+    def wait(self, duration: float) -> None:
+        """Method wait should wait for a given duration.
+
+        A normal implementation should just call `time.sleep(duration)`. This is intended for proceeding Virtual Time
+        Simulation instances.
+        """
+        pass
+
+    @abstractmethod
+    def close(self) -> None:
+        """Method close should close the OT CLI Handler."""
+        pass
+
+
+class Simulator:
+    """This abstract class defines interfaces for a Virtual Time Simulator."""
+
+    @abstractmethod
+    def go(self, duration: float):
+        """Proceed the simulator for a given duration (in seconds)."""
+        pass
+
+
+class OtCliPopen(OtCliHandler):
+    """Connector for OT CLI process (a Popen instance)."""
+
+    def __init__(self, proc: subprocess.Popen, nodeid: int, simulator: Simulator):
+        self.__otcli_proc = proc
+        self.__nodeid = nodeid
+        self.__simulator = simulator
+
+    def __repr__(self):
+        return 'OTCli<%d>' % self.__nodeid
+
+    def readline(self) -> str:
+        return self.__otcli_proc.stdout.readline().rstrip('\r\n')
+
+    def writeline(self, s: str):
+        self.__otcli_proc.stdin.write(s + '\n')
+        self.__otcli_proc.stdin.flush()
+
+    def wait(self, duration: float):
+        if self.__simulator is not None:
+            # Virtual time simulation
+            self.__simulator.go(duration)
+        else:
+            # Real time simulation
+            time.sleep(duration)
+
+    def close(self):
+        self.__otcli_proc.stdin.close()
+        self.__otcli_proc.stdout.close()
+        self.__otcli_proc.wait()
+
+
+class OtCliSim(OtCliPopen):
+    """Connector for OT CLI Simulation instances."""
+
+    def __init__(self, executable: str, nodeid: int, simulator: Simulator):
+        logging.info('%s: executable=%s', self.__class__.__name__, executable)
+
+        proc = subprocess.Popen(args=[executable, str(nodeid)],
+                                executable=executable,
+                                stdin=subprocess.PIPE,
+                                stdout=subprocess.PIPE,
+                                encoding='utf-8',
+                                bufsize=1024)
+        super().__init__(proc, nodeid, simulator)
+
+
+class OtNcpSim(OtCliHandler):
+    """Connector for OT NCP Simulation instances."""
+
+    def __init__(self, executable: str, nodeid: int, simulator: Simulator):
+        logging.info('%s: executable=%s', self.__class__.__name__, executable)
+
+        proc = subprocess.Popen(args=f'spinel-cli.py -p "{executable}" -n {nodeid} 2>&1',
+                                stdin=subprocess.PIPE,
+                                stdout=subprocess.PIPE,
+                                encoding='utf-8',
+                                bufsize=1024,
+                                shell=True)
+        super().__init__(proc, nodeid, simulator)
+
+
+class OtCliSerial(OtCliHandler):
+    """Connector for OT CLI SOC devices via Serial."""
+
+    def __init__(self, dev: str, baudrate: int):
+        self.__dev = dev
+        self.__baudrate = baudrate
+
+        import serial
+        self.__serial = serial.Serial(self.__dev, self.__baudrate, timeout=None, exclusive=True)
+
+    def __repr__(self):
+        return self.__dev
+
+    def readline(self) -> str:
+        line = self.__serial.readline().decode('utf-8').rstrip('\r\n')
+        return line
+
+    def writeline(self, s: str):
+        self.__serial.write((s + '\n').encode('utf-8'))
+
+    def wait(self, duration: float):
+        time.sleep(duration)
+
+    def close(self):
+        self.__serial.close()
diff --git a/tests/scripts/expect/cli-anycast.exp b/tools/otci/otci/constants.py
similarity index 76%
copy from tests/scripts/expect/cli-anycast.exp
copy to tools/otci/otci/constants.py
index 3081a6b..4bf13c4 100644
--- a/tests/scripts/expect/cli-anycast.exp
+++ b/tools/otci/otci/constants.py
@@ -1,4 +1,4 @@
-#!/usr/bin/expect -f
+#!/usr/bin/env python3
 #
 #  Copyright (c) 2020, The OpenThread Authors.
 #  All rights reserved.
@@ -27,30 +27,6 @@
 #  POSSIBILITY OF SUCH DAMAGE.
 #
 
-source "tests/scripts/expect/_common.exp"
-
-
-set spawn_id [spawn_node 1]
-
-send "panid 0xface\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-
-wait_for "state" "leader"
-expect "Done"
-
-send "ping fdde:ad00:beef:0:0:ff:fe00:fc00\n"
-expect "Done"
-expect "16 bytes from "
-expect {
-    "fdde:ad00:beef:0:0:ff:fe00:fc00" abort
-
-    -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})} {}
-
-    timeout abort
-}
-
-dispose
+# Thread versions
+THREAD_VERSION_1_1 = 2
+THREAD_VERSION_1_2 = 3
diff --git a/tests/scripts/expect/v1_2-rcp.exp b/tools/otci/otci/errors.py
old mode 100755
new mode 100644
similarity index 60%
copy from tests/scripts/expect/v1_2-rcp.exp
copy to tools/otci/otci/errors.py
index 776d880..dc8fd59
--- a/tests/scripts/expect/v1_2-rcp.exp
+++ b/tools/otci/otci/errors.py
@@ -1,4 +1,4 @@
-#!/usr/bin/expect -f
+#!/usr/bin/env python3
 #
 #  Copyright (c) 2020, The OpenThread Authors.
 #  All rights reserved.
@@ -26,40 +26,39 @@
 #  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 #  POSSIBILITY OF SUCH DAMAGE.
 #
+from typing import List
 
-source "tests/scripts/expect/_common.exp"
 
-spawn_node 1
+class OTCIError(Exception):
+    """Base class for OTCI Errors."""
+    pass
 
-send "panid 0xface\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-wait_for "state" "leader"
-expect "Done"
 
-send "ipaddr mleid\n"
-expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-set addr $expect_out(1,string)
-expect "Done"
+class ExpectLineTimeoutError(OTCIError):
+    """OTCI failed to find an expected line before timeout."""
 
-spawn_node 2 mtd
+    def __init__(self, line):
+        super(ExpectLineTimeoutError, self).__init__("Expected line %r, but timed out" % line)
 
-send "panid 0xface\n"
-expect "Done"
-send "mode -\n"
-expect "Done"
-send "csl period 5000\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-wait_for "state" "child"
-expect "Done"
-send "ping $addr\n"
-expect "16 bytes from $addr: icmp_seq=1"
 
-dispose_all
+class CommandError(OTCIError):
+    """OTCI failed to execute a command."""
+
+    def __init__(self, cmd: str, output: List[str]):
+        self.__output = output
+        super(CommandError, self).__init__("Command error while executing %r:\n%s\n" % (cmd, '\n'.join(output)))
+
+    def error(self) -> str:
+        return self.__output[-1]
+
+
+class UnexpectedCommandOutput(OTCIError):
+    """OTCI got unexpected command output."""
+
+    def __init__(self, output: List[str]):
+        super(UnexpectedCommandOutput, self).__init__("Unexpected command output:\n%s\n" % '\n'.join(output))
+
+
+class InvalidArgumentsError(OTCIError):
+    """Invalid arguments."""
+    pass
diff --git a/tools/otci/otci/otci.py b/tools/otci/otci/otci.py
new file mode 100644
index 0000000..361162b
--- /dev/null
+++ b/tools/otci/otci/otci.py
@@ -0,0 +1,1938 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2020, The OpenThread Authors.
+#  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.
+#
+import ipaddress
+import logging
+import re
+import time
+from collections import Counter
+from typing import List, Collection, Union, Tuple, Optional, Dict, Pattern, Any
+
+from . import connectors
+from .command_handlers import OTCommandHandler, OtCliCommandRunner, OtbrSshCommandRunner
+from .connectors import Simulator
+from .errors import UnexpectedCommandOutput, ExpectLineTimeoutError, CommandError, InvalidArgumentsError
+from .types import ChildId, Rloc16, Ip6Addr, ThreadState, PartitionId, DeviceMode, RouterId, SecurityPolicy, Ip6Prefix, \
+    RouterTableEntry
+from .utils import match_line, constant_property
+
+
+class OTCI(object):
+    """
+    This class represents an OpenThread Controller Interface instance that provides versatile interfaces to
+    manipulate an OpenThread device.
+    """
+
+    def __init__(self, otcmd: OTCommandHandler):
+        """
+        This method initializes an OTCI instance.
+
+        :param otcmd: An OpenThread Command Handler instance to execute OpenThread CLI commands.
+        """
+        self.__otcmd: OTCommandHandler = otcmd
+        self.__logger = logging.getLogger(name=str(self))
+
+    def __repr__(self):
+        """Gets the string representation of the OTCI instance."""
+        return repr(self.__otcmd)
+
+    def wait(self, duration: float, expect_line: Union[str, Pattern, Collection[Any]] = None):
+        """Wait for a given duration.
+
+        :param duration: The duration (in seconds) wait for.
+        :param expect_line: The line expected to output if given.
+                            Raise ExpectLineTimeoutError if expect_line is not found within the given duration.
+        """
+        self.__logger and self.__logger.info("wait for %.3f seconds", duration)
+        if expect_line is None:
+            self.__otcmd.wait(duration)
+        else:
+            success = False
+
+            while duration > 0:
+                output = self.__otcmd.wait(1)
+                if match_line(expect_line, output):
+                    success = True
+                    break
+
+            if not success:
+                raise ExpectLineTimeoutError(expect_line)
+
+    def close(self):
+        """Close the OTCI instance."""
+        self.__otcmd.close()
+
+    def execute_command(self, cmd: str, timeout: float = 10, silent: bool = False) -> List[str]:
+        """Execute the OpenThread CLI command.
+
+        :param cmd: The command to execute.
+        :param timeout: The command timeout.
+        :param silent: Whether to run the command silent without logging.
+        :returns: The command output as a list of lines.
+        """
+        if not silent:
+            self.__logger and self.__logger.info('> %s', cmd)
+
+        output = self.__otcmd.execute_command(cmd, timeout)
+
+        if not silent:
+            for line in output:
+                self.__logger and self.__logger.info('%s', line)
+
+        if cmd in ('reset', 'factoryreset'):
+            self.__wait_reset()
+            return output
+
+        if output[-1] == 'Done':
+            output = output[:-1]
+            return output
+        else:
+            raise CommandError(cmd, output)
+
+    def set_logger(self, logger: logging.Logger):
+        """Set the logger for the OTCI instance, or None to disable logging."""
+        self.__logger = logger
+
+    #
+    # Constant properties
+    #
+    @constant_property
+    def version(self):
+        """Returns the firmware version. (e.g. "OPENTHREAD/20191113-01411-gb2d66e424-dirty; SIMULATION; Nov 14 2020 14:24:38")"""
+        return self.__parse_str(self.execute_command('version'))
+
+    @constant_property
+    def thread_version(self):
+        """Get the Thread Version number."""
+        return self.__parse_int(self.execute_command('thread version'))
+
+    @constant_property
+    def api_version(self):
+        """Get API version number."""
+        try:
+            return self.__parse_int(self.execute_command('version api'))
+        except ValueError:
+            # If the device does not have `version api` command, it will print the firmware version, which would lead to ValueError.
+            return 0
+
+    #
+    # Basic device operations
+    #
+    def ifconfig_up(self):
+        """Bring up the IPv6 interface."""
+        self.execute_command('ifconfig up')
+
+    def ifconfig_down(self):
+        """Bring down the IPv6 interface."""
+        self.execute_command('ifconfig down')
+
+    def get_ifconfig_state(self) -> bool:
+        """Get the status of the IPv6 interface."""
+        return self.__parse_values(self.execute_command('ifconfig'), up=True, down=False)
+
+    def thread_start(self):
+        """Enable Thread protocol operation and attach to a Thread network."""
+        self.execute_command('thread start')
+
+    def thread_stop(self):
+        """Disable Thread protocol operation and detach from a Thread network."""
+        self.execute_command('thread stop')
+
+    def reset(self):
+        """Signal a platform reset."""
+        self.execute_command('reset')
+
+    def factory_reset(self):
+        """Delete all stored settings, and signal a platform reset."""
+        self.execute_command('factoryreset')
+
+    #
+    # Network Operations
+    #
+
+    def ping(self, ip: str, size: int = None, count: int = None, interval: int = None, hoplimit: int = None):
+        """Send an ICMPv6 Echo Request.
+
+        :param ip: The target IPv6 address to ping.
+        :param size: The number of data bytes in the payload.
+        :param count: The number of ICMPv6 Echo Requests to be sent.
+        :param interval: The interval between two consecutive ICMPv6 Echo Requests in seconds. The value may have fractional form, for example 0.5.
+        :param hoplimit: The hoplimit of ICMPv6 Echo Request to be sent.
+        """
+        cmd = f'ping {ip}'
+
+        if size is not None:
+            cmd += f' {size}'
+
+        if count is not None:
+            cmd += f' {count}'
+
+        if interval is not None:
+            cmd += f' {interval}'
+
+        if hoplimit is not None:
+            cmd += f' {hoplimit}'
+
+        self.execute_command(cmd)
+
+    def ping_stop(self):
+        """Stop sending ICMPv6 Echo Requests."""
+        self.execute_command('ping stop')
+
+    def scan(self, channel: int = None) -> List[Dict[str, Any]]:
+        """Perform an IEEE 802.15.4 Active Scan."""
+        cmd = 'scan'
+        if channel is not None:
+            cmd += f' {channel}'
+
+        output = self.execute_command(cmd, timeout=10)
+        if len(output) < 2:
+            raise UnexpectedCommandOutput(output)
+
+        networks = []
+        for line in output[2:]:
+            fields = line.strip().split('|')
+
+            _, J, netname, extpanid, panid, extaddr, ch, dbm, lqi, _ = fields
+            networks.append({
+                'joinable': bool(int(J)),
+                'network_name': netname.strip(),
+                'extpanid': extpanid,
+                'panid': int(panid, 16),
+                'extaddr': extaddr,
+                'channel': int(ch),
+                'dbm': int(dbm),
+                'lqi': int(lqi),
+            })
+
+        return networks
+
+    def scan_energy(self, duration: float = None, channel: int = None) -> Dict[int, int]:
+        """Perform an IEEE 802.15.4 Energy Scan."""
+        cmd = 'scan energy'
+        if duration is not None:
+            cmd += f' {duration * 1000:d}'
+
+        if channel is not None:
+            cmd += f' {channel}'
+
+        output = self.execute_command(cmd, timeout=10)
+        if len(output) < 2:
+            raise UnexpectedCommandOutput(output)
+
+        channels = {}
+        for line in output[2:]:
+            fields = line.strip().split('|')
+
+            _, Ch, RSSI, _ = fields
+            channels[int(Ch)] = int(RSSI)
+
+        return channels
+
+    def mac_send_data_request(self):
+        """Instruct an Rx-Off-When-Idle device to send a Data Request mac frame to its parent."""
+        self.execute_command('mac send datarequest')
+
+    def mac_send_empty_data(self):
+        """Instruct an Rx-Off-When-Idle device to send a Empty Data mac frame to its parent."""
+        self.execute_command('mac send emptydata')
+
+    # TODO: discover
+    # TODO: dns resolve <hostname> [DNS server IP] [DNS server port]
+    # TODO: fake /a/an <dst-ipaddr> <target> <meshLocalIid>
+    # TODO: sntp query
+
+    #
+    # Set or get device/network parameters
+    #
+
+    def get_mode(self) -> str:
+        """Get the Thread Device Mode value.
+
+            -: no flags set (rx-off-when-idle, minimal Thread device, stable network data)
+            r: rx-on-when-idle
+            d: Full Thread Device
+            n: Full Network Data
+        """
+        return self.__parse_str(self.execute_command('mode'))
+
+    def set_mode(self, mode: str):
+        """Set the Thread Device Mode value.
+
+            -: no flags set (rx-off-when-idle, minimal Thread device, stable network data)
+            r: rx-on-when-idle
+            d: Full Thread Device
+            n: Full Network Data
+        """
+        self.execute_command(f'mode {DeviceMode(mode)}')
+
+    def get_extaddr(self) -> str:
+        """Get the IEEE 802.15.4 Extended Address."""
+        return self.__parse_extaddr(self.execute_command('extaddr'))
+
+    def set_extaddr(self, extaddr: str):
+        """Set the IEEE 802.15.4 Extended Address."""
+        self.__validate_hex64b(extaddr)
+        self.execute_command(f'extaddr {extaddr}')
+
+    def get_eui64(self) -> str:
+        """Get the factory-assigned IEEE EUI-64."""
+        return self.__parse_eui64(self.execute_command('eui64'))
+
+    def set_extpanid(self, extpanid: str):
+        """Set the Thread Extended PAN ID value."""
+        self.__validate_extpanid(extpanid)
+        self.execute_command(f'extpanid {extpanid}')
+
+    def get_extpanid(self) -> str:
+        """Get the Thread Extended PAN ID value."""
+        return self.__parse_extpanid(self.execute_command('extpanid'))
+
+    def set_channel(self, ch):
+        """Set the IEEE 802.15.4 Channel value."""
+        self.execute_command('channel %d' % ch)
+
+    def get_channel(self):
+        """Get the IEEE 802.15.4 Channel value."""
+        return self.__parse_int(self.execute_command('channel'))
+
+    def get_preferred_channel_mask(self) -> int:
+        """Get preferred channel mask."""
+        return self.__parse_int(self.execute_command('channel preferred'))
+
+    def get_supported_channel_mask(self):
+        """Get supported channel mask."""
+        return self.__parse_int(self.execute_command('channel supported'))
+
+    def get_panid(self):
+        """Get the IEEE 802.15.4 PAN ID value."""
+        return self.__parse_int(self.execute_command('panid'), 16)
+
+    def set_panid(self, panid):
+        """Get the IEEE 802.15.4 PAN ID value."""
+        self.execute_command('panid %d' % panid)
+
+    def set_network_name(self, name):
+        """Set network name."""
+        self.execute_command('networkname %s' % self.__escape_escapable(name))
+
+    def get_network_name(self):
+        """Get network name."""
+        return self.__parse_str(self.execute_command('networkname'))
+
+    def get_master_key(self) -> str:
+        """Get the network master key."""
+        return self.__parse_master_key(self.execute_command('masterkey'))
+
+    def set_master_key(self, masterkey: str):
+        """Set the network master key."""
+        self.__validate_master_key(masterkey)
+        self.execute_command(f'masterkey {masterkey}')
+
+    def get_key_sequence_counter(self) -> int:
+        """Get the Thread Key Sequence Counter."""
+        return self.__parse_int(self.execute_command('keysequence counter'))
+
+    def set_key_sequence_counter(self, counter: int):
+        """Set the Thread Key Sequence Counter."""
+        self.execute_command(f'keysequence counter {counter}')
+
+    def get_key_sequence_guard_time(self) -> int:
+        """Get Thread Key Switch Guard Time (in hours)."""
+        return self.__parse_int(self.execute_command('keysequence guardtime'))
+
+    def set_key_sequence_guard_time(self, hours: int):
+        """Set Thread Key Switch Guard Time (in hours) 0 means Thread Key Switch immediately if key index match."""
+        self.execute_command(f'keysequence guardtime {hours}')
+
+    def get_cca_threshold(self) -> int:
+        """Get the CCA threshold in dBm measured at antenna connector per IEEE 802.15.4 - 2015 section 10.1.4."""
+        output = self.execute_command(f'ccathreshold')
+        val = self.__parse_str(output)
+        if not val.endswith(' dBm'):
+            raise UnexpectedCommandOutput(output)
+
+        return int(val[:-4])
+
+    def set_cca_threshold(self, val: int):
+        """Set the CCA threshold measured at antenna connector per IEEE 802.15.4 - 2015 section 10.1.4."""
+        self.execute_command(f'ccathreshold {val}')
+
+    def get_promiscuous(self) -> bool:
+        """Get radio promiscuous property."""
+        return self.__parse_Enabled_or_Disabled(self.execute_command('promiscuous'))
+
+    def enable_promiscuous(self):
+        """Enable radio promiscuous operation and print raw packet content."""
+        self.execute_command('promiscuous enable')
+
+    def disable_promiscuous(self):
+        """Disable radio promiscuous operation."""
+        self.execute_command('promiscuous disable')
+
+    def get_txpower(self) -> int:
+        """Get the transmit power in dBm."""
+        line = self.__parse_str(self.execute_command('txpower'))
+        if not line.endswith(' dBm'):
+            raise UnexpectedCommandOutput([line])
+
+        return int(line.split()[0])
+
+    def set_txpower(self, val: int):
+        """Set the transmit power in dBm."""
+        self.execute_command(f'txpower {val}')
+
+    # TODO: fem
+    # TODO: fem lnagain
+    # TODO: fem lnagain <LNA gain>
+    # TODO: mac retries direct
+    # TODO: mac retries direct
+    # TODO: mac retries indirect
+    # TODO: mac retries indirect <number>
+
+    #
+    # Basic Node states and properties
+    #
+
+    def get_state(self) -> ThreadState:
+        """Get the current Thread state."""
+        return ThreadState(self.__parse_str(self.execute_command('state')))
+
+    def set_state(self, state: str):
+        """Try to switch to state detached, child, router or leader."""
+        self.execute_command(f'state {state}')
+
+    def get_rloc16(self) -> int:
+        """Get the Thread RLOC16 value."""
+        return self.__parse_int(self.execute_command('rloc16'), 16)
+
+    def get_router_id(self) -> int:
+        """Get the Thread Router ID value."""
+        return self.get_rloc16() >> 10
+
+    def prefer_router_id(self, routerid: int):
+        """Prefer a Router ID when solicit router id from Leader."""
+        self.execute_command(f'preferrouterid {routerid}')
+
+    def is_singleton(self) -> bool:
+        return self.__parse_values(self.execute_command('singleton'), true=True, false=False)
+
+    #
+    # RCP related utilities
+    #
+
+    def get_rcp_version(self):
+        return self.__parse_str(self.execute_command('rcp version'))
+
+    #
+    # Unsecure port utilities
+    #
+
+    def get_unsecure_ports(self) -> List[int]:
+        """all ports from the allowed unsecured port list."""
+        return self.__parse_int_list(self.execute_command('unsecureport get'))
+
+    def add_unsecure_port(self, port: int):
+        """Add a port to the allowed unsecured port list."""
+        self.execute_command(f'unsecureport add {port}')
+
+    def remove_unsecure_port(self, port: int):
+        """Remove a port from the allowed unsecured port list."""
+        self.execute_command(f'unsecureport remove {port}')
+
+    def clear_unsecure_ports(self):
+        """Remove all ports from the allowed unsecured port list."""
+        self.execute_command('unsecureport remove all')
+
+    #
+    # Leader configurations
+    #
+
+    def get_preferred_partition_id(self) -> PartitionId:
+        """Get the preferred Thread Leader Partition ID."""
+        return PartitionId(self.__parse_int(self.execute_command(self.__get_partition_preferred_cmd())))
+
+    def set_preferred_partition_id(self, parid: int):
+        """Set the preferred Thread Leader Partition ID."""
+        self.execute_command(f'{self.__get_partition_preferred_cmd()} {parid}')
+
+    def __get_partition_preferred_cmd(self) -> str:
+        """"""
+        return 'partitionid preferred' if self.api_version >= 51 else 'leaderpartitionid'
+
+    def get_leader_weight(self) -> int:
+        """Get the Thread Leader Weight."""
+        return self.__parse_int(self.execute_command('leaderweight'))
+
+    def set_leader_weight(self, weight: int):
+        """Set the Thread Leader Weight."""
+        self.execute_command(f'leaderweight {weight}')
+
+    __LEADER_DATA_KEY_MAP = {
+        'Partition ID': 'partition_id',
+        'Weighting': 'weight',
+        'Data Version': 'data_ver',
+        'Stable Data Version': 'stable_data_ver',
+        'Leader Router ID': 'leader_id',
+    }
+
+    def get_leader_data(self) -> Dict[str, int]:
+        """Get the Thread Leader Data."""
+        data = {}
+        output = self.execute_command('leaderdata')
+
+        try:
+            for line in output:
+                k, v = line.split(': ')
+                data[OTCI.__LEADER_DATA_KEY_MAP[k]] = int(v)
+        except KeyError:
+            raise UnexpectedCommandOutput(output)
+
+        return data
+
+    #
+    # Router configurations
+    #
+
+    def get_router_selection_jitter(self):
+        """Get the ROUTER_SELECTION_JITTER value."""
+        return self.__parse_int(self.execute_command('routerselectionjitter'))
+
+    def set_router_selection_jitter(self, jitter):
+        """Set the ROUTER_SELECTION_JITTER value."""
+        self.execute_command(f'routerselectionjitter {jitter}')
+
+    def get_network_id_timeout(self) -> int:
+        """Get the NETWORK_ID_TIMEOUT parameter used in the Router role."""
+        return self.__parse_int(self.execute_command('networkidtimeout'))
+
+    def set_network_id_timeout(self, timeout: int):
+        """Set the NETWORK_ID_TIMEOUT parameter used in the Router role."""
+        self.execute_command(f'networkidtimeout {timeout}')
+
+    def get_parent_priority(self) -> int:
+        """Get the assigned parent priority value, -2 means not assigned."""
+        return self.__parse_int(self.execute_command('parentpriority'))
+
+    def set_parent_priority(self, priority: int):
+        """Set the assigned parent priority value: 1, 0, -1 or -2."""
+        self.execute_command(f'parentpriority {priority}')
+
+    def get_router_upgrade_threshold(self) -> int:
+        """Get the ROUTER_UPGRADE_THRESHOLD value."""
+        return self.__parse_int(self.execute_command('routerupgradethreshold'))
+
+    def set_router_upgrade_threshold(self, threshold: int):
+        """Set the ROUTER_UPGRADE_THRESHOLD value."""
+        self.execute_command(f'routerupgradethreshold {threshold}')
+
+    def get_router_downgrade_threshold(self):
+        """Set the ROUTER_DOWNGRADE_THRESHOLD value."""
+        return self.__parse_int(self.execute_command('routerdowngradethreshold'))
+
+    def set_router_downgrade_threshold(self, threshold: int):
+        """Get the ROUTER_DOWNGRADE_THRESHOLD value."""
+        self.execute_command(f'routerdowngradethreshold {threshold}')
+
+    def get_router_eligible(self) -> bool:
+        """Indicates whether the router role is enabled or disabled."""
+        return self.__parse_Enabled_or_Disabled(self.execute_command('routereligible'))
+
+    def enable_router_eligible(self):
+        """Disable the router role."""
+        self.execute_command('routereligible enable')
+
+    def disable_router_eligible(self):
+        """Disable the router role."""
+        self.execute_command('routereligible disable')
+
+    def get_router_list(self) -> List[RouterId]:
+        """Get allocated Router IDs."""
+        line = self.__parse_str(self.execute_command('router list'))
+        return list(map(RouterId, line.strip().split()))
+
+    def get_router_table(self) -> Dict[RouterId, RouterTableEntry]:
+        """table of routers."""
+        output = self.execute_command('router table')
+        if len(output) < 2:
+            raise UnexpectedCommandOutput(output)
+
+        #
+        # Example output:
+        #
+        # | ID | RLOC16 | Next Hop | Path Cost | LQ In | LQ Out | Age | Extended MAC     |
+        # +----+--------+----------+-----------+-------+--------+-----+------------------+
+        # | 21 | 0x5400 |       21 |         0 |     3 |      3 |   5 | d28d7f875888fccb |
+        # | 56 | 0xe000 |       56 |         0 |     0 |      0 | 182 | f2d92a82c8d8fe43 |
+        # Done
+        #
+
+        headers = self.__split_table_row(output[0])
+
+        table = {}
+        for line in output[2:]:
+            line = line.strip()
+            if not line:
+                continue
+
+            fields = self.__split_table_row(line)
+            if len(fields) != len(headers):
+                raise UnexpectedCommandOutput(output)
+
+            col = lambda colname: self.__get_table_col(colname, headers, fields)
+            id = col('ID')
+
+            table[RouterId(id)] = router = RouterTableEntry({
+                'id': RouterId(id),
+                'rloc16': Rloc16(col('RLOC16'), 16),
+                'next_hop': int(col('Next Hop')),
+                'path_cost': int(col('Path Cost')),
+                'lq_in': int(col('LQ In')),
+                'lq_out': int(col('LQ Out')),
+                'age': int(col('Age')),
+                'extaddr': col('Extended MAC'),
+            })
+
+            if 'Link' in headers:
+                router['link'] = int(col('Link'))
+            else:
+                # support older version of OT which does not output `Link` field
+                router['link'] = self.get_router_info(router['id'], silent=True)['link']
+
+        return table
+
+    def get_router_info(self, id: int, silent: bool = False) -> RouterTableEntry:
+        cmd = f'router {id}'
+        info = {}
+        output = self.execute_command(cmd, silent=silent)
+        items = [line.strip().split(': ') for line in output]
+
+        headers = [h for h, _ in items]
+        fields = [f for _, f in items]
+        col = lambda colname: self.__get_table_col(colname, headers, fields)
+
+        return RouterTableEntry({
+            'id': RouterId(id),
+            'rloc16': Rloc16(col('Rloc'), 16),
+            'alloc': int(col('Alloc')),
+            'next_hop': int(col('Next Hop'), 16) >> 10,  # convert RLOC16 to Router ID
+            'link': int(col('Link')),
+        })
+
+    #
+    # Router utilities: Child management
+    #
+
+    def get_child_table(self) -> Dict[ChildId, Dict[str, Any]]:
+        """Get the table of attached children."""
+        output = self.execute_command('child table')
+        if len(output) < 2:
+            raise UnexpectedCommandOutput(output)
+
+        #
+        # Example output:
+        # | ID  | RLOC16 | Timeout    | Age        | LQ In | C_VN |R|D|N|Ver|CSL|QMsgCnt| Extended MAC     |
+        # +-----+--------+------------+------------+-------+------+-+-+-+---+---+-------+------------------+
+        # |   1 | 0xc801 |        240 |         24 |     3 |  131 |1|0|0|  3| 0 |     0 | 4ecede68435358ac |
+        # |   2 | 0xc802 |        240 |          2 |     3 |  131 |0|0|0|  3| 1 |     0 | a672a601d2ce37d8 |
+        # Done
+        #
+
+        headers = self.__split_table_row(output[0])
+
+        table = {}
+        for line in output[2:]:
+            line = line.strip()
+            if not line:
+                continue
+
+            fields = self.__split_table_row(line)
+            col = lambda colname: self.__get_table_col(colname, headers, fields)
+
+            id = int(col("ID"))
+            r, d, n = int(col("R")), int(col("D")), int(col("N"))
+            mode = DeviceMode(f'{"r" if r else ""}{"d" if d else ""}{"n" if n else ""}')
+
+            child = {
+                'id': ChildId(id),
+                'rloc16': Rloc16(col('RLOC16'), 16),
+                'timeout': int(col('Timeout')),
+                'age': int(col('Age')),
+                'lq_in': int(col('LQ In')),
+                'c_vn': int(col('C_VN')),
+                'mode': mode,
+                'extaddr': col('Extended MAC')
+            }
+
+            if 'Ver' in headers:
+                child['ver'] = int(col('Ver'))
+
+            if 'CSL' in headers:
+                child['csl'] = bool(int(col('CSL')))
+
+            if 'QMsgCnt' in headers:
+                child['qmsgcnt'] = int(col('QMsgCnt'))
+
+            table[ChildId(id)] = child
+
+        return table
+
+    def __split_table_row(self, row: str) -> List[str]:
+        if not (row.startswith('|') and row.endswith('|')):
+            raise ValueError(row)
+
+        fields = row.split('|')
+        fields = [x.strip() for x in fields[1:-1]]
+        return fields
+
+    def __get_table_col(self, colname: str, headers: List[str], fields: List[str]) -> str:
+        return fields[headers.index(colname)]
+
+    def get_child_list(self) -> List[ChildId]:
+        """Get attached Child IDs."""
+        line = self.__parse_str(self.execute_command(f'child list'))
+        return [ChildId(id) for id in line.strip().split()]
+
+    def get_child_info(self, child: Union[ChildId, Rloc16]) -> Dict[str, Any]:
+        output = self.execute_command(f'child {child}')
+
+        info = {}
+
+        for line in output:
+            k, v = line.split(': ')
+            if k == 'Child ID':
+                info['id'] = int(v)
+            elif k == 'Rloc':
+                info['rloc16'] = int(v, 16)
+            elif k == 'Ext Addr':
+                info['extaddr'] = v
+            elif k == 'Mode':
+                info['mode'] = DeviceMode(v)
+            elif k == 'Net Data':
+                info['c_vn'] = int(v)
+            elif k == 'Timeout':
+                info['timeout'] = int(v)
+            elif k == 'Age':
+                info['age'] = int(v)
+            elif k == 'Link Quality In':
+                info['lq_in'] = int(v)
+            elif k == 'RSSI':
+                info['rssi'] = int(v)
+            else:
+                self.__logger and self.__logger.warning("Child info %s: %s ignored", k, v)
+
+        return info
+
+    def get_child_ipaddrs(self) -> Dict[Rloc16, List[Ip6Addr]]:
+        """Get the list of IP addresses stored for MTD children.
+
+        Note: Each MTD child might has multiple IP addresses.
+        """
+        output = self.execute_command('childip')
+
+        ipaddrs = {}
+
+        for line in output:
+            rloc16, ip = line.split(': ')
+            rloc16 = Rloc16(rloc16, 16)
+            ipaddrs.setdefault(rloc16, []).append(Ip6Addr(ip.strip()))
+
+        return ipaddrs
+
+    #
+    # Child configurations
+    #
+
+    def get_max_children(self) -> int:
+        """Get the Thread maximum number of allowed children."""
+        return self.__parse_int(self.execute_command('childmax'))
+
+    def set_max_children(self, val: int):
+        """Set the Thread maximum number of allowed children."""
+        self.execute_command(f'childmax {val}')
+
+    def get_child_ip_max(self) -> int:
+        """Get the maximum number of IP addresses that each MTD child may register with this device as parent."""
+        return self.__parse_int(self.execute_command('childip max'))
+
+    def set_child_ip_max(self, val: int):
+        """Get the maximum number of IP addresses that each MTD child may register with this device as parent."""
+        self.execute_command(f'childip max {val}')
+
+    def get_child_timeout(self):
+        """Get the Thread Child Timeout value."""
+        return self.__parse_int(self.execute_command('childtimeout'))
+
+    def set_child_timeout(self, timeout):
+        """Set the Thread Child Timeout value."""
+        self.execute_command('childtimeout %d' % timeout)
+
+    def get_child_supervision_interval(self) -> int:
+        """Get the Child Supervision Check Timeout value."""
+        return self.__parse_int(self.execute_command('childsupervision interval'))
+
+    def set_child_supervision_interval(self, val: int):
+        """Set the Child Supervision Interval value.
+        This command can only be used with FTD devices.
+        """
+        self.execute_command(f'childsupervision interval {val}')
+
+    def get_child_supervision_check_timeout(self) -> int:
+        """Get the Child Supervision Check Timeout value."""
+        return self.__parse_int(self.execute_command('childsupervision checktimeout'))
+
+    def set_child_supervision_check_timeout(self, val: int):
+        """Set the Child Supervision Check Timeout value."""
+        self.execute_command(f'childsupervision checktimeout {val}')
+
+    #
+    # Neighbor management
+    #
+
+    def get_neighbor_list(self) -> List[Rloc16]:
+        """Get a list of RLOC16 of neighbors"""
+        line = self.__parse_str(self.execute_command('neighbor list')).strip()
+        return [Rloc16(id, 16) for id in line.split()]
+
+    def get_neighbor_table(self) -> Dict[Rloc16, Dict[str, Any]]:
+        output = self.execute_command('neighbor table')
+        if len(output) < 2:
+            raise UnexpectedCommandOutput(output)
+
+        #
+        # Example output:
+        #
+        # | Role | RLOC16 | Age | Avg RSSI | Last RSSI |R|D|N| Extended MAC     |
+        # +------+--------+-----+----------+-----------+-+-+-+------------------+
+        # |   C  | 0xcc01 |  96 |      -46 |       -46 |1|1|1| 1eb9ba8a6522636b |
+        # |   R  | 0xc800 |   2 |      -29 |       -29 |1|1|1| 9a91556102c39ddb |
+        # |   R  | 0xf000 |   3 |      -28 |       -28 |1|1|1| 0ad7ed6beaa6016d |
+        # Done
+        #
+
+        headers = self.__split_table_row(output[0])
+
+        table = {}
+        for line in output[2:]:
+            line = line.strip()
+            if not line:
+                continue
+
+            fields = self.__split_table_row(line)
+            col = lambda colname: self.__get_table_col(colname, headers, fields)
+
+            role = col('Role')
+            is_router = role == 'R'
+            r, d, n = int(col('R')), int(col('D')), int(col('N'))
+            mode = DeviceMode(f'{"r" if r else ""}{"d" if d else ""}{"n" if n else ""}')
+
+            rloc16 = Rloc16(col('RLOC16'), 16)
+
+            table[rloc16] = {
+                'is_router': is_router,
+                'rloc16': rloc16,
+                'age': int(col('Age')),
+                'avg_rssi': int(col('Avg RSSI')),
+                'last_rssi': int(col('Last RSSI')),
+                'mode': mode,
+                'extaddr': col('Extended MAC'),
+            }
+
+        return table
+
+    #
+    # SED/SSED configuration
+    #
+
+    def get_poll_period(self) -> int:
+        """Get the customized data poll period of sleepy end device (milliseconds).
+        Only for Reference Device."""
+        return self.__parse_int(self.execute_command('pollperiod'))
+
+    def set_poll_period(self, poll_period: int):
+        """Set the customized data poll period (in milliseconds) for sleepy end device.
+
+        Only for Reference Device."""
+        self.execute_command(f'pollperiod {poll_period}')
+
+    # TODO: csl
+    # TODO: csl channel <channel>
+    # TODO: csl period <period>
+    # TODO: csl timeout <timeout>
+
+    _CSL_PERIOD_PATTERN = re.compile(r'(\d+)\(in units of 10 symbols\), \d+ms')
+    _CSL_TIMEOUT_PATTERN = re.compile(r'(\d+)s')
+
+    def get_csl_config(self) -> Dict[str, int]:
+        """Get the CSL configuration."""
+        output = self.execute_command('csl')
+
+        cfg = {}
+        for line in output:
+            k, v = line.split(': ')
+            if k == 'Channel':
+                cfg['channel'] = int(v)
+            elif k == 'Timeout':
+                cfg['timeout'] = int(OTCI._CSL_TIMEOUT_PATTERN.match(v).group(1))
+            elif k == 'Period':
+                cfg['period'] = int(OTCI._CSL_PERIOD_PATTERN.match(v).group(1))
+            else:
+                logging.warning("Ignore unknown CSL parameter: %s: %s", k, v)
+
+        return cfg
+
+    def config_csl(self, channel: int = None, period: int = None, timeout: int = None):
+        """Configure CSL parameters.
+
+        :param channel: Set CSL channel.
+        :param period: Set CSL period in units of 10 symbols. Disable CSL by setting this parameter to 0.
+        :param timeout: Set the CSL timeout in seconds.
+        """
+
+        if channel is None and period is None and timeout is None:
+            raise InvalidArgumentsError("Please specify at least 1 parameter to configure.")
+
+        if channel is not None:
+            self.execute_command(f'csl channel {channel}')
+
+        if period is not None:
+            self.execute_command(f'csl period {period}')
+
+        if timeout is not None:
+            self.execute_command(f'csl timeout {timeout}')
+
+    #
+    # Leader utilities
+    #
+
+    def get_context_id_reuse_delay(self) -> int:
+        """Get the CONTEXT_ID_REUSE_DELAY value."""
+        return self.__parse_int(self.execute_command('contextreusedelay'))
+
+    def set_context_id_reuse_delay(self, val: int):
+        """Set the CONTEXT_ID_REUSE_DELAY value."""
+        self.execute_command(f'contextreusedelay {val}')
+
+    def release_router_id(self, routerid: int):
+        """Release a Router ID that has been allocated by the device in the Leader role."""
+        self.execute_command(f'releaserouterid {routerid}')
+
+    # Time Sync utilities
+    # TODO: networktime
+    # TODO: networktime <timesyncperiod> <xtalthreshold>
+    # TODO: delaytimermin
+    # TODO: delaytimermin <delaytimermin>
+
+    #
+    # Commissioniner operations
+    #
+
+    def commissioner_start(self):
+        """Start the Commissioner role."""
+        self.execute_command('commissioner start')
+
+    def commissioner_stop(self):
+        """Stop the Commissioner role."""
+        self.execute_command('commissioner stop')
+
+    def get_commissioiner_state(self) -> str:
+        """Get current Commissioner state (active or petitioning or disabled)."""
+        return self.__parse_str(self.execute_command('commissioner state'))
+
+    def get_commissioner_session_id(self) -> int:
+        """Get current commissioner session id."""
+        return self.__parse_int(self.execute_command('commissioner sessionid'))
+
+    def commissioner_add_joiner(self, pskd, eui64=None, discerner=None, timeout=None):
+        """Add a Joiner entry.
+
+        :param pskd: Pre-Shared Key for the Joiner.
+        :param eui64: The IEEE EUI-64 of the Joiner or '*' to match any Joiner
+        :param discerner: The Joiner discerner in format number/length.
+        :param timeout: Joiner timeout in seconds.
+        """
+        if (eui64 is not None) == (discerner is not None):
+            raise InvalidArgumentsError("Please specify eui64 or discerner, but not both.")
+
+        if eui64 is not None and eui64 != '*':
+            self.__validate_extaddr(eui64)
+
+        cmd = f'commissioner joiner add {eui64 or discerner} {pskd}'
+
+        if timeout is not None:
+            cmd += f' {timeout}'
+
+        self.execute_command(cmd)
+
+    def commissioner_remove_jointer(self, eui64=None, discerner=None):
+        if (eui64 is not None) == (discerner is not None):
+            raise InvalidArgumentsError("Please specify eui64 or discerner, but not both.")
+
+        if eui64 is not None and eui64 != '*':
+            self.__validate_extaddr(eui64)
+
+        self.execute_command(f'commissioner joiner remove {eui64 or discerner}')
+
+    def set_commissioner_provisioning_url(self, url: str):
+        self.execute_command(f'commissioner provisioningurl {url}')
+
+    # TODO: commissioner announce
+    # TODO: commissioner energy
+    # TODO: commissioner mgmtget
+    # TODO: commissioner mgmtset
+    # TODO: commissioner panid
+
+    #
+    # Joiner operations
+    #
+    def joiner_start(self, psk: str, provisioning_url: str = None):
+        """Start the Joiner."""
+        cmd = f'joiner start {psk}'
+        if provisioning_url is not None:
+            cmd += f' {provisioning_url}'
+
+        self.execute_command(cmd)
+
+    def joiner_stop(self):
+        """Stop the Joiner role."""
+        self.execute_command('joiner stop')
+
+    def get_joiner_id(self) -> str:
+        """Get the Joiner ID."""
+        return self.__parse_joiner_id(self.execute_command('joiner id'))
+
+    def get_joiner_port(self) -> int:
+        """Get the Joiner port."""
+        return self.__parse_int(self.execute_command(f'joinerport'))
+
+    def set_joiner_port(self, port: int):
+        """Set the Joiner port."""
+        self.execute_command(f'joinerport {port}')
+
+    # TODO: joiner discerner
+
+    #
+    # Network Data utilities
+    #
+    def get_local_prefixes(self) -> List[Tuple[Ip6Prefix, str, str, Rloc16]]:
+        """Get prefixes from local Network Data."""
+        output = self.execute_command('prefix')
+        return self.__parse_prefixes(output)
+
+    def __parse_prefixes(self, output: List[str]) -> List[Tuple[Ip6Prefix, str, str, Rloc16]]:
+        prefixes = []
+
+        for line in output:
+            if line.startswith('- '):
+                line = line[2:]
+
+            prefix, flags, prf, rloc16 = line.split()[:4]
+            prefixes.append((Ip6Prefix(prefix), flags, prf, Rloc16(rloc16, 16)))
+
+        return prefixes
+
+    def add_prefix(self, prefix: str, flags='paosr', prf='med'):
+        """Add a valid prefix to the Network Data."""
+        self.execute_command(f'prefix add {prefix} {flags} {prf}')
+
+    def remove_prefix(self, prefix: str):
+        """Invalidate a prefix in the Network Data."""
+        self.execute_command(f'prefix remove {prefix}')
+
+    def register_network_data(self):
+        self.execute_command('netdata register')
+
+    def get_network_data(self) -> Dict[str, List]:
+        output = self.execute_command('netdata show')
+
+        netdata = {}
+        if output.pop(0) != 'Prefixes:':
+            raise UnexpectedCommandOutput(output)
+
+        prefixes_output = []
+        while True:
+            line = output.pop(0)
+            if line == 'Routes:':
+                break
+            else:
+                prefixes_output.append(line)
+
+        netdata['prefixes'] = self.__parse_prefixes(prefixes_output)
+
+        print(prefixes_output, output)
+        routes_output = []
+        while True:
+            line = output.pop(0)
+            if line == 'Services:':
+                break
+            else:
+                routes_output.append(line)
+
+        netdata['routes'] = self.__parse_routes(routes_output)
+        netdata['services'] = self.__parse_services(output)
+
+        return netdata
+
+    def get_prefixes(self) -> List[Tuple[Ip6Prefix, str, str, Rloc16]]:
+        """Get network prefixes from Thread Network Data."""
+        network_data = self.get_network_data()
+        return network_data['prefixes']
+
+    def get_routes(self) -> List[Tuple[str, bool, str, Rloc16]]:
+        """Get routes from Thread Network Data."""
+        network_data = self.get_network_data()
+        return network_data['routes']
+
+    def get_services(self) -> List[Tuple[int, bytes, bytes, bool, Rloc16]]:
+        """Get services from Thread Network Data"""
+        network_data = self.get_network_data()
+        return network_data['services']
+
+    def __parse_services(self, output: List[str]) -> List[Tuple[int, bytes, bytes, bool, Rloc16]]:
+        services = []
+        for line in output:
+            line = line.split()
+
+            enterprise_number, service_data, server_data = line[:3]
+            if line[3] == 's':
+                stable, rloc16 = True, line[4]
+            else:
+                stable, rloc16 = False, line[3]
+
+            enterprise_number = int(enterprise_number)
+            service_data = self.__hex_to_bytes(service_data)
+            server_data = self.__hex_to_bytes(server_data)
+            rloc16 = Rloc16(rloc16, 16)
+
+            services.append((enterprise_number, service_data, server_data, stable, rloc16))
+
+        return services
+
+    def get_network_data_bytes(self) -> bytes:
+        """Get the raw Network Data."""
+        hexstr = self.__parse_str(self.execute_command('netdata show -x'))
+        return bytes(int(hexstr[i:i + 2], 16) for i in range(0, len(hexstr), 2))
+
+    def get_local_routes(self) -> List[Tuple[str, bool, str, Rloc16]]:
+        """Get routes from local Network Data."""
+        return self.__parse_routes(self.execute_command('route'))
+
+    def __parse_routes(self, output: List[str]) -> List[Tuple[str, bool, str, Rloc16]]:
+        routes = []
+        for line in output:
+            line = line.split()
+            print(line)
+            if line[1] == 's':
+                prefix, _, prf, rloc16 = line
+                stable = True
+            else:
+                prefix, prf, rloc16 = line
+                stable = False
+
+            rloc16 = Rloc16(rloc16, 16)
+            routes.append((prefix, stable, prf, rloc16))
+
+        return routes
+
+    def add_route(self, prefix: str, stable=True, prf='med'):
+        """Add a valid external route to the Network Data."""
+        cmd = f'route add {prefix}'
+        if stable:
+            cmd += ' s'
+
+        cmd += f' {prf}'
+        self.execute_command(cmd)
+
+    def remove_route(self, prefix: str):
+        """Invalidate a external route in the Network Data."""
+        self.execute_command(f'route remove {prefix}')
+
+    def add_service(self, enterprise_number: int, service_data: Union[str, bytes], server_data: Union[str, bytes]):
+        """Add service to the Network Data.
+
+        enterpriseNumber: IANA enterprise number
+        serviceData: hex-encoded binary service data
+        serverData: hex-encoded binary server data
+        """
+        service_data = self.__validate_hex_or_bytes(service_data)
+        server_data = self.__validate_hex_or_bytes(server_data)
+        self.execute_command(f'service add {enterprise_number} {service_data} {server_data}')
+
+    def remove_service(self, enterprise_number, service_data):
+        """Remove service from Network Data.
+
+        enterpriseNumber: IANA enterprise number
+        serviceData: hext-encoded binary service data
+        """
+        service_data = self.__validate_hex_or_bytes(service_data)
+        self.execute_command(f'service remove {enterprise_number} {service_data}')
+
+    #
+    # Dataset management
+    #
+
+    def dataset_init_buffer(self, get_active_dataset=False, get_pending_dataset=False):
+        """Initialize operational dataset buffer."""
+        if get_active_dataset and get_pending_dataset:
+            raise InvalidArgumentsError("Can not specify both `get_active_dataset` and `get_pending_dataset`.")
+
+        if get_active_dataset:
+            self.execute_command(f'dataset init active')
+        elif get_pending_dataset:
+            self.execute_command(f'dataset init pending')
+        else:
+            self.execute_command(f'dataset init new')
+
+    def dataset_commit_buffer(self, dataset: str):
+        if dataset in ('active', 'pending'):
+            cmd = f'dataset commit {dataset}'
+        else:
+            raise InvalidArgumentsError(f'Unkonwn dataset: {dataset}')
+
+        self.execute_command(cmd)
+
+    def dataset_clear_buffer(self):
+        """Reset operational dataset buffer."""
+        self.execute_command('dataset clear')
+
+    def get_dataset(self, dataset: str = 'buffer'):
+        if dataset in ('active', 'pending'):
+            cmd = f'dataset {dataset}'
+        elif dataset == 'buffer':
+            cmd = 'dataset'
+        else:
+            raise InvalidArgumentsError(f'Unkonwn dataset: {dataset}')
+
+        output = self.execute_command(cmd)
+        return self.__parse_dataset(output)
+
+    def __parse_dataset(self, output: List[str]) -> Dict[str, Any]:
+        # Example output:
+        #
+        # Active Timestamp: 1
+        # Channel: 22
+        # Channel Mask: 0x07fff800
+        # Ext PAN ID: 5c93ae980ff22d35
+        # Mesh Local Prefix: fdc7:55fe:6363:bd01::/64
+        # Master Key: d1a8348d59fb1fac1d6c4f95007d487a
+        # Network Name: OpenThread-7caa
+        # PAN ID: 0x7caa
+        # PSKc: 167d89fd169e439ca0b8266de248090f
+        # Security Policy: 0, onrcb
+
+        dataset = {}
+
+        for line in output:
+            line = line.split(': ')
+            key, val = line[0], ': '.join(line[1:])
+
+            if key == 'Active Timestamp':
+                dataset['active_timestamp'] = int(val)
+            elif key == 'Channel':
+                dataset['channel'] = int(val)
+            elif key == 'Channel Mask':
+                dataset['channel_mask'] = int(val, 16)
+            elif key == 'Ext PAN ID':
+                dataset['extpanid'] = val
+            elif key == 'Mesh Local Prefix':
+                dataset['mesh_local_prefix'] = val
+            elif key == 'Master Key':
+                dataset['masterkey'] = val
+            elif key == 'Network Name':
+                dataset['network_name'] = val
+            elif key == 'PAN ID':
+                dataset['panid'] = int(val, 16)
+            elif key == 'PSKc':
+                dataset['pskc'] = val
+            elif key == 'Security Policy':
+                rotation_time, flags = val.split(', ')
+                rotation_time = int(rotation_time)
+                dataset['security_policy'] = SecurityPolicy(rotation_time, flags)
+            else:
+                raise UnexpectedCommandOutput(output)
+
+        return dataset
+
+    def get_dataset_bytes(self, dataset: str) -> bytes:
+        if dataset in ('active', 'pending'):
+            cmd = f'dataset {dataset} -x'
+        else:
+            raise InvalidArgumentsError(f'Unkonwn dataset: {dataset}')
+
+        hexstr = self.__parse_str(self.execute_command(cmd))
+        return self.__hex_to_bytes(hexstr)
+
+    def dataset_set_buffer(self,
+                           active_timestamp: int = None,
+                           channel: int = None,
+                           channel_mask: int = None,
+                           extpanid: str = None,
+                           mesh_local_prefix: str = None,
+                           master_key: str = None,
+                           network_name: str = None,
+                           panid: int = None,
+                           pskc: str = None,
+                           security_policy: tuple = None,
+                           pending_timestamp: int = None):
+        if active_timestamp is not None:
+            self.execute_command(f'dataset activetimestamp {active_timestamp}')
+
+        if channel is not None:
+            self.execute_command(f'dataset channel {channel}')
+
+        if channel_mask is not None:
+            self.execute_command(f'dataset channelmask {channel_mask}')
+
+        if extpanid is not None:
+            self.execute_command(f'dataset extpanid {extpanid}')
+
+        if mesh_local_prefix is not None:
+            self.execute_command(f'dataset meshlocalprefix {mesh_local_prefix}')
+
+        if master_key is not None:
+            self.execute_command(f'dataset masterkey {master_key}')
+
+        if network_name is not None:
+            self.execute_command(f'dataset networkname {self.__escape_escapable(network_name)}')
+
+        if panid is not None:
+            self.execute_command(f'dataset panid {panid}')
+
+        if pskc is not None:
+            self.execute_command(f'dataset pskc {pskc}')
+
+        if security_policy is not None:
+            rotation_time, flags = security_policy
+            self.execute_command(f'dataset securitypolicy {rotation_time} {flags}')
+
+        if pending_timestamp is not None:
+            self.execute_command(f'dataset pendingtimestamp {pending_timestamp}')
+
+    # TODO: dataset mgmtgetcommand
+    # TODO: dataset mgmtsetcommand
+    # TODO: dataset set <active|pending> <dataset>
+
+    #
+    # Allowlist management
+    #
+
+    def enable_allowlist(self):
+        self.execute_command(f'macfilter addr {self.__detect_allowlist_cmd()}')
+
+    def disable_allowlist(self):
+        self.execute_command('macfilter addr disable')
+
+    def add_allowlist(self, addr: str, rssi: int = None):
+        cmd = f'macfilter addr add {addr}'
+
+        if rssi is not None:
+            cmd += f' {rssi}'
+
+        self.execute_command(cmd)
+
+    def remove_allowlist(self, addr: str):
+        self.execute_command(f'macfilter addr remove {addr}')
+
+    def clear_allowlist(self):
+        self.execute_command('macfilter addr clear')
+
+    def set_allowlist(self, allowlist: Collection[Union[str, Tuple[str, int]]]):
+        self.clear_allowlist()
+
+        if allowlist is None:
+            self.disable_allowlist()
+        else:
+            self.enable_allowlist()
+            for item in allowlist:
+                if isinstance(item, str):
+                    self.add_allowlist(item)
+                else:
+                    addr, rssi = item[0], item[1]
+                    self.add_allowlist(addr, rssi)
+
+    # TODO: denylist
+    # TODO: macfilter rss
+    # TODO: macfilter rss add <extaddr> <rss>
+    # TODO: macfilter rss add-lqi <extaddr> <lqi>
+    # TODO: macfilter rss remove <extaddr>
+    # TODO: macfilter rss clear
+
+    def __detect_allowlist_cmd(self):
+        if self.api_version >= 28:
+            return 'allowlist'
+        else:
+            return '\x77\x68\x69\x74\x65\x6c\x69\x73\x74'
+
+    #
+    # Unicast Addresses management
+    #
+    def add_ipaddr(self, ip: Union[str, ipaddress.IPv6Address]):
+        """Add an IPv6 address to the Thread interface."""
+        self.execute_command(f'ipaddr add {ip}')
+
+    def del_ipaddr(self, ip: Union[str, ipaddress.IPv6Address]):
+        """Delete an IPv6 address from the Thread interface."""
+        self.execute_command(f'ipaddr del {ip}')
+
+    def get_ipaddrs(self) -> Tuple[Ip6Addr]:
+        """Get all IPv6 addresses assigned to the Thread interface."""
+        return tuple(map(Ip6Addr, self.execute_command('ipaddr')))
+
+    def has_ipaddr(self, ip: Union[str, ipaddress.IPv6Address]):
+        """Check if a Ip6 address was added to the Thread interface."""
+        return ip in self.get_ipaddrs()
+
+    def get_ipaddr_mleid(self) -> Ip6Addr:
+        """Get Thread Mesh Local EID address."""
+        return self.__parse_ip6addr(self.execute_command('ipaddr mleid'))
+
+    def get_ipaddr_linklocal(self) -> Ip6Addr:
+        """Get Thread link-local IPv6 address."""
+        return self.__parse_ip6addr(self.execute_command('ipaddr linklocal'))
+
+    def get_ipaddr_rloc(self) -> Ip6Addr:
+        """Get Thread Routing Locator (RLOC) address."""
+        return self.__parse_ip6addr(self.execute_command('ipaddr rloc'))
+
+    #
+    # Multicast Addresses management
+    #
+
+    def add_ipmaddr(self, ip: Union[str, ipaddress.IPv6Address]):
+        """Subscribe the Thread interface to the IPv6 multicast address."""
+        self.execute_command(f'ipmaddr add {ip}')
+
+    def del_ipmaddr(self, ip: Union[str, ipaddress.IPv6Address]):
+        """Unsubscribe the Thread interface to the IPv6 multicast address."""
+        self.execute_command(f'ipmaddr del {ip}')
+
+    def get_ipmaddrs(self) -> Tuple[Ip6Addr]:
+        """Get all IPv6 multicast addresses subscribed to the Thread interface."""
+        return tuple(map(Ip6Addr, self.execute_command('ipmaddr')))
+
+    def has_ipmaddr(self, ip: Union[str, ipaddress.IPv6Address]):
+        """Check if a Ip6 multicast address was subscribed by the Thread interface."""
+        return ip in self.get_ipmaddrs()
+
+    def get_ipmaddr_promiscuous(self) -> bool:
+        """Get multicast promiscuous mode."""
+        return self.__parse_Enabled_or_Disabled(self.execute_command("ipmaddr promiscuous"))
+
+    def enable_ipmaddr_promiscuous(self):
+        """Enable multicast promiscuous mode."""
+        self.execute_command('ipmaddr promiscuous enable')
+
+    def disable_ipmaddr_promiscuous(self):
+        """Disable multicast promiscuous mode."""
+        self.execute_command('ipmaddr promiscuous disable')
+
+    #
+    # Backbone Router Utilities
+    #
+
+    # TODO: bbr mgmt ...
+
+    def enable_backbone_router(self):
+        """Enable Backbone Router Service for Thread 1.2 FTD.
+
+        SRV_DATA.ntf would be triggerred for attached device if there is no Backbone Router Service in Thread Network Data.
+        """
+        self.execute_command('bbr enable')
+
+    def disable_backbone_router(self):
+        """Disable Backbone Router Service for Thread 1.2 FTD.
+
+        SRV_DATA.ntf would be triggerred if Backbone Router is Primary state.
+        """
+        self.execute_command('bbr disable')
+
+    def get_backbone_router_state(self) -> str:
+        """Get local Backbone state (Disabled or Primary or Secondary) for Thread 1.2 FTD."""
+        self.__parse_str(self.execute_command('bbr state'))
+
+    def get_primary_backbone_router_info(self) -> Optional[dict]:
+        """Show current Primary Backbone Router information for Thread 1.2 device."""
+        output = self.execute_command('bbr')
+
+        if len(output) < 1:
+            raise UnexpectedCommandOutput(output)
+
+        line = output[0]
+        if line == 'BBR Primary: None':
+            return None
+
+        if line != 'BBR Primary:':
+            raise UnexpectedCommandOutput(output)
+
+        # Example output:
+        # BBR Primary:
+        # server16: 0xE400
+        # seqno:    10
+        # delay:    120 secs
+        # timeout:  300 secs
+
+        dataset = {}
+
+        for line in output[1:]:
+            key, val = line.split(':')
+            key, val = key.strip(), val.strip()
+            if key == 'server16':
+                dataset[key] = int(val, 16)
+            elif key == 'seqno':
+                dataset[key] = int(val)
+            elif key == 'delay':
+                if not val.endswith(' secs'):
+                    raise UnexpectedCommandOutput(output)
+                dataset[key] = int(val.split()[0])
+            elif key == 'timeout':
+                if not val.endswith(' secs'):
+                    raise UnexpectedCommandOutput(output)
+                dataset[key] = int(val.split()[0])
+            else:
+                raise UnexpectedCommandOutput(output)
+
+        return dataset
+
+    def register_backbone_router_dataset(self):
+        """Register Backbone Router Service for Thread 1.2 FTD.
+
+        SRV_DATA.ntf would be triggerred for attached device.
+        """
+        self.execute_command('bbr register')
+
+    def get_backbone_router_config(self) -> dict:
+        """Show local Backbone Router configuration for Thread 1.2 FTD."""
+        output = self.execute_command('bbr config')
+        # Example output:
+        # seqno:    10
+        # delay:    120 secs
+        # timeout:  300 secs
+
+        config = {}
+
+        for line in output:
+            key, val = line.split(':')
+            key, val = key.strip(), val.strip()
+            if key == 'seqno':
+                config[key] = int(val)
+            elif key in ('delay', 'timeout'):
+                if not line.endswith(' secs'):
+                    raise UnexpectedCommandOutput(output)
+                config[key] = int(val.split()[0])
+            else:
+                raise UnexpectedCommandOutput(output)
+
+        return config
+
+    def set_backbone_router_config(self, seqno: int = None, delay: int = None, timeout: int = None):
+        """Configure local Backbone Router configuration for Thread 1.2 FTD.
+
+        Call register_backbone_router_dataset() to explicitly register Backbone Router service to Leader for Secondary Backbone Router.
+        """
+        if seqno is None and delay is None and timeout is None:
+            raise InvalidArgumentsError("Please specify seqno or delay or timeout")
+
+        cmd = 'bbr config'
+        if seqno is not None:
+            cmd += f' seqno {seqno}'
+
+        if delay is not None:
+            cmd += f' delay {delay}'
+
+        if timeout is not None:
+            cmd += f' timeout {timeout}'
+
+        self.execute_command(cmd)
+
+    def get_backbone_router_jitter(self) -> int:
+        """Get jitter (in seconds) for Backbone Router registration for Thread 1.2 FTD."""
+        return self.__parse_int(self.execute_command('bbr jitter'))
+
+    def set_backbone_router_jitter(self, val: int):
+        """Set jitter (in seconds) for Backbone Router registration for Thread 1.2 FTD."""
+        self.execute_command(f'bbr jitter {val}')
+
+    #
+    # Thread 1.2 and DUA/MLR utilities
+    #
+
+    def get_domain_name(self) -> str:
+        """Get the Thread Domain Name for Thread 1.2 device."""
+        return self.__parse_str(self.execute_command('domainname'))
+
+    def set_domain_name(self, name: str):
+        """Set the Thread Domain Name for Thread 1.2 device."""
+        self.execute_command('domainname %s' % self.__escape_escapable(name))
+
+    # TODO: dua iid
+    # TODO: dua iid <iid>
+    # TODO: dua iid clear
+    # TODO: mlr reg <ipaddr> ... [timeout]
+
+    #
+    # Link metrics management
+    #
+    # TODO: linkmetrics mgmt <ipaddr> forward <seriesid> [ldraX][pqmr]
+    # TODO: linkmetrics probe <ipaddr> <seriesid> <length>
+    # TODO: linkmetrics query <ipaddr> single [pqmr]
+    # TODO: linkmetrics query <ipaddr> forward <seriesid>
+    # TODO: linkquality <extaddr>
+    # TODO: linkquality <extaddr> <linkquality>
+    #
+
+    #
+    # Logging
+    #
+
+    def get_log_level(self) -> int:
+        """Get the log level."""
+        return self.__parse_int(self.execute_command('log level'))
+
+    def set_log_level(self, level: int):
+        """Set the log level."""
+        self.execute_command(f'log level {level}')
+
+    #
+    # Device performance related information
+    #
+
+    def get_message_buffer_info(self) -> dict:
+        """Get the current message buffer information."""
+        output = self.execute_command('bufferinfo')
+
+        info = {}
+
+        def _parse_val(val):
+            vals = val.split()
+            return int(vals[0]) if len(vals) == 1 else tuple(map(int, vals))
+
+        for line in output:
+            key, val = line.split(':')
+            key, val = key.strip(), val.strip()
+            info[key.replace(' ', '_')] = _parse_val(val)
+
+        return info
+
+    @constant_property
+    def counter_names(self):
+        """Get the supported counter names."""
+        return tuple(self.execute_command('counters'))
+
+    def get_counter(self, name: str) -> Counter:
+        """Reset the counter value."""
+        output = self.execute_command(f'counters {name}')
+
+        counter = Counter()
+        for line in output:
+            k, v = line.strip().split(': ')
+            counter[k] = int(v)
+
+        return counter
+
+    def reset_counter(self, name: str):
+        """Reset the counter value."""
+        self.execute_command(f'counters {name} reset')
+
+    def get_eidcache(self) -> Dict[Ip6Addr, Rloc16]:
+        """Get the EID-to-RLOC cache entries."""
+        output = self.execute_command('eidcache')
+        cache = {}
+
+        for line in output:
+            ip, rloc16 = line.split()
+
+            cache[Ip6Addr(ip)] = Rloc16(rloc16, 16)
+
+        return cache
+
+    #
+    # UDP utilities
+    #
+
+    def udp_open(self):
+        """Opens the example socket."""
+        self.execute_command('udp open')
+
+    def udp_close(self):
+        """Opens the example socket."""
+        self.execute_command('udp close')
+
+    def udp_bind(self, ip: str, port: int):
+        """Assigns a name (i.e. IPv6 address and port) to the example socket.
+
+        :param ip: the IPv6 address or the unspecified IPv6 address (::).
+        :param port: the UDP port
+        """
+        self.execute_command(f'udp bind {ip} {port}')
+
+    def udp_connect(self, ip: str, port: int):
+        """Specifies the peer with which the socket is to be associated.
+
+        ip: the peer's IPv6 address.
+        port: the peer's UDP port.
+        """
+        self.execute_command(f'udp connect {ip} {port}')
+
+    def udp_send(self, ip: str = None, port: int = None, text: str = None, random_bytes: int = None, hex: str = None):
+        """Send a few bytes over UDP.
+
+        ip: the IPv6 destination address.
+        port: the UDP destination port.
+        type: the type of the message: _ -t: text payload in the value, same as without specifying the type. _ -s: autogenerated payload with specified length indicated in the value.
+        * -x: binary data in hexadecimal representation in the value.
+        """
+        if (ip is None) != (port is None):
+            raise InvalidArgumentsError("Please specify both `ip` and `port`.")
+
+        if (text is not None) + (random_bytes is not None) + (hex is not None) != 1:
+            raise InvalidArgumentsError("Please specify `text` or `random_bytes` or `hex`.")
+
+        cmd = 'udp send'
+
+        if ip is not None:
+            cmd += f' {ip} {port}'
+
+        if text is not None:
+            cmd += f' -t {text}'
+        elif random_bytes is not None:
+            cmd += f' -s {random_bytes}'
+        elif hex is not None:
+            self.__validate_hex(hex)
+            cmd += f' -x {hex}'
+
+        self.execute_command(cmd)
+
+    def udp_get_link_security(self) -> bool:
+        """Gets whether the link security is enabled or disabled."""
+        return self.__parse_Enabled_or_Disabled(self.execute_command('udp linksecurity'))
+
+    def udp_enable_link_security(self):
+        """Enable link security."""
+        self.execute_command('udp linksecurity enable')
+
+    def udp_disable_link_security(self):
+        """Disable link security."""
+        self.execute_command('udp linksecurity disable')
+
+    #
+    # CoAP CLI (test) utilities
+    #
+    def coap_start(self):
+        """Starts the application coap service."""
+        self.execute_command('coap start')
+
+    def coap_stop(self):
+        """Stops the application coap service."""
+        self.execute_command('coap stop')
+
+    def coap_get(self, addr: str, uri_path: str, type: str = "con"):
+        cmd = f'coap get {addr} {uri_path} {type}'
+        self.execute_command(cmd)
+
+    def coap_put(self, addr: str, uri_path: str, type: str = "con", payload: str = None):
+        cmd = f'coap put {addr} {uri_path} {type}'
+
+        if payload is not None:
+            cmd += f' {payload}'
+
+        self.execute_command(cmd)
+
+    def coap_post(self, addr: str, uri_path: str, type: str = "con", payload: str = None):
+        cmd = f'coap post {addr} {uri_path} {type}'
+
+        if payload is not None:
+            cmd += f' {payload}'
+
+        self.execute_command(cmd)
+
+    def coap_delete(self, addr: str, uri_path: str, type: str = "con", payload: str = None):
+        cmd = f'coap delete {addr} {uri_path} {type}'
+
+        if payload is not None:
+            cmd += f' {payload}'
+
+        self.execute_command(cmd)
+
+    def coap_get_test_resource_path(self) -> str:
+        """Gets the URI path for the test resource."""
+        return self.__parse_str(self.execute_command('coap resource'))
+
+    def coap_set_test_resource_path(self, path: str):
+        """Sets the URI path for the test resource."""
+        self.execute_command(f'coap resource {path}')
+
+    def coap_test_set_resource_content(self, content: str):
+        """Sets the content sent by the test resource. If a CoAP client is observing the resource, a notification is sent to that client."""
+        self.execute_command(f'coap set {content}')
+
+    # TODO: coap observe <address> <uri-path> [type]
+    # TODO: coap cancel
+    # TODO: coap parameters <type> ["default"|<ack_timeout> <ack_random_factor_numerator> <ack_random_factor_denominator> <max_retransmit>]
+    # TODO: CoAP Secure utilities
+
+    #
+    # Other TODOs
+    #
+    # TODO: netstat
+    # TODO: networkdiagnostic get <addr> <type> ..
+    # TODO: networkdiagnostic reset <addr> <type> ..
+    # TODO: parent
+    # TODO: pskc [-p] <key>|<passphrase>
+    #
+
+    #
+    # Private methods
+    #
+
+    def __parse_str(self, output: List[str]) -> str:
+        if len(output) != 1:
+            raise UnexpectedCommandOutput(output)
+
+        return output[0]
+
+    def __parse_int_list(self, output: List[str]) -> List[int]:
+        line = self.__parse_str(output)
+        return list(map(int, line.strip().split()))
+
+    def __parse_ip6addr(self, output: List[str]) -> Ip6Addr:
+        return Ip6Addr(self.__parse_str(output))
+
+    def __parse_int(self, output: List[str], base=10) -> int:
+        if len(output) != 1:
+            raise UnexpectedCommandOutput(output)
+
+        return int(output[0], base)
+
+    def __parse_master_key(self, output: List[str]) -> str:
+        masterkey = self.__parse_str(output)
+
+        try:
+            self.__validate_master_key(masterkey)
+        except ValueError:
+            raise UnexpectedCommandOutput(output)
+
+        return masterkey
+
+    def __validate_master_key(self, masterkey: str):
+        if len(masterkey) != 32:
+            raise ValueError(masterkey)
+
+        int(masterkey, 16)
+
+    def __parse_hex64b(self, output: List[str]) -> str:
+        extaddr = self.__parse_str(output)
+
+        try:
+            self.__validate_hex64b(extaddr)
+        except ValueError:
+            raise UnexpectedCommandOutput(output)
+
+        return extaddr
+
+    __parse_extaddr = __parse_hex64b
+    __parse_extpanid = __parse_hex64b
+    __parse_eui64 = __parse_hex64b
+    __parse_joiner_id = __parse_hex64b
+
+    def __validate_hex64b(self, extaddr: str):
+        if len(extaddr) != 16:
+            raise ValueError(extaddr)
+
+        self.__validate_hex(extaddr)
+
+    def __validate_hex(self, hexstr: str):
+        if len(hexstr) % 2 != 0:
+            raise ValueError(hexstr)
+
+        for i in range(0, len(hexstr), 2):
+            int(hexstr[i:i + 2], 16)
+
+    __validate_extaddr = __validate_hex64b
+    __validate_extpanid = __validate_hex64b
+
+    def __parse_Enabled_or_Disabled(self, output: List[str]) -> bool:
+        return self.__parse_values(output, Enabled=True, Disabled=False)
+
+    def __parse_values(self, output: List[str], **vals) -> Any:
+        val = self.__parse_str(output)
+        if val not in vals:
+            raise UnexpectedCommandOutput(output)
+
+        return vals[val]
+
+    def __validate_hex_or_bytes(self, data: Union[str, bytes]) -> str:
+        if isinstance(data, bytes):
+            return ''.join('%02x' % c for c in data)
+        else:
+            self.__validate_hex(data)
+            return data
+
+    def __hex_to_bytes(self, hexstr: str) -> bytes:
+        self.__validate_hex(hexstr)
+        return bytes(int(hexstr[i:i + 2], 16) for i in range(0, len(hexstr), 2))
+
+    def __escape_escapable(self, s: str) -> str:
+        """Escape CLI escapable characters in the given string.
+        """
+        escapable_chars = '\\ \t\r\n'
+        for char in escapable_chars:
+            s = s.replace(char, '\\%s' % char)
+        return s
+
+    def __wait_reset(self):
+        # reset would restart the otbr-agent executable. It's risky to send commands after reset too quickly because
+        # it might cause ot-ctl to quit abnormally.
+        # So we sleep for a while after reset.
+        time.sleep(3)
+
+
+def connect_cli_sim(executable: str, nodeid: int, simulator: Optional[Simulator] = None) -> OTCI:
+    cli_handler = connectors.OtCliSim(executable, nodeid, simulator=simulator)
+    cmd_handler = OtCliCommandRunner(cli_handler)
+    return OTCI(cmd_handler)
+
+
+def connect_cli_serial(dev: str, baudrate=115200) -> OTCI:
+    cli_handler = connectors.OtCliSerial(dev, baudrate)
+    cmd_handler = OtCliCommandRunner(cli_handler)
+    return OTCI(cmd_handler)
+
+
+def connect_ncp_sim(executable: str, nodeid: int, simulator: Optional[Simulator] = None) -> OTCI:
+    ncp_handler = connectors.OtNcpSim(executable, nodeid, simulator=simulator)
+    cmd_handler = OtCliCommandRunner(ncp_handler, is_spinel_cli=True)
+    return OTCI(cmd_handler)
+
+
+def connect_otbr_ssh(host: str, port: int = 22, username='pi', password='raspberry'):
+    cmd_handler = OtbrSshCommandRunner(host, port, username, password)
+    return OTCI(cmd_handler)
+
+
+def connect_cmd_handler(cmd_handler: OTCommandHandler) -> OTCI:
+    return OTCI(cmd_handler)
diff --git a/tools/otci/otci/types.py b/tools/otci/otci/types.py
new file mode 100644
index 0000000..f5d39a1
--- /dev/null
+++ b/tools/otci/otci/types.py
@@ -0,0 +1,139 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2020, The OpenThread Authors.
+#  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.
+#
+import ipaddress
+from collections import namedtuple
+
+
+class ChildId(int):
+    """Represents a Child ID."""
+    pass
+
+
+class RouterId(int):
+    """Represents a Router ID."""
+    pass
+
+
+class Rloc16(int):
+    """Represents a RLOC16."""
+
+    def __repr__(self):
+        return '0x%04x' % self
+
+
+class PartitionId(int):
+    """Represents a Thread Network Partition ID."""
+    pass
+
+
+class DeviceMode(str):
+    """Represents a device mode."""
+
+    def __new__(cls, o: str):
+        ins = str.__new__(cls, o)
+
+        if ins != '-':
+            for c in ins:
+                if c not in 'rdn':
+                    raise ValueError(o)
+
+        # check for empty mode (SED should use "-")
+        if not ins:
+            raise ValueError(o)
+
+        # check for duplicate chars
+        if len(ins) != len(set(ins)):
+            raise ValueError(o)
+
+        return ins
+
+
+class ThreadState(str):
+    """Represents a Thread state."""
+    _VALID_VALUES = {'disabled', 'detached', 'child', 'router', 'leader'}
+
+    def __new__(cls, o: str):
+        ins = str.__new__(cls, o)
+
+        if ins not in ThreadState._VALID_VALUES:
+            raise ValueError(o)
+
+        return ins
+
+
+class Ip6Addr(ipaddress.IPv6Address):
+    """Represents an IPv6 address."""
+
+    def __eq__(self, other):
+        if isinstance(other, str):
+            other = ipaddress.IPv6Address(other)
+
+        return super().__eq__(other)
+
+    def __repr__(self):
+        return self.compressed
+
+    def __hash__(self):
+        return super().__hash__()
+
+
+class Ip6Prefix(ipaddress.IPv6Network):
+    """Represents an IPv6 prefix."""
+
+    def __eq__(self, other):
+        if isinstance(other, str):
+            other = ipaddress.IPv6Network(other)
+
+        return super().__eq__(other)
+
+    def __repr__(self):
+        return self.compressed
+
+    def __hash__(self):
+        return super().__hash__()
+
+
+SecurityPolicy = namedtuple('SecurityPolicy', ['rotation_time', 'flags'])
+"""Represents a Security Policy configuration."""
+
+
+class RouterTableEntry(dict):
+
+    @property
+    def is_link_established(self):
+        return bool(self['link'])
+
+
+if __name__ == '__main__':
+    assert Ip6Addr('2001:0:0:0:0:0:0:1') == '2001::1'
+    assert repr(Ip6Addr('2001:0:0:0:0:0:0:1')) == '2001::1'
+    assert str(Ip6Addr('2001:0:0:0:0:0:0:1')) == '2001::1'
+    assert Ip6Prefix('2001:0:0:0::/64') == '2001::/64'
+    assert repr(Ip6Prefix('2001:0:0:0::/64')) == '2001::/64'
+    assert str(Ip6Prefix('2001:0:0:0::/64')) == '2001::/64'
diff --git a/tools/otci/otci/utils.py b/tools/otci/otci/utils.py
new file mode 100644
index 0000000..3a52a82
--- /dev/null
+++ b/tools/otci/otci/utils.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2020, The OpenThread Authors.
+#  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.
+#
+import functools
+from typing import Union, Collection, Any, Pattern
+
+
+def match_line(line: str, expect_line: Union[str, Pattern, Collection[Any]]) -> bool:
+    """Checks if a line is expected (matched by one of the given patterns)."""
+    if isinstance(expect_line, Pattern):
+        match = expect_line.match(line)
+    elif isinstance(expect_line, str):
+        match = (line == expect_line)
+    else:
+        match = any(match_line(line, x) for x in expect_line)
+
+    return match
+
+
+def cached(func):
+    """Decorator cached makes the function to cache its result and return it in duplicate calls."""
+    prop_name = '__cached_' + func.__name__
+
+    @functools.wraps(func)
+    def _cached_func(self):
+        try:
+            return getattr(self, prop_name)
+        except AttributeError:
+            val = func(self)
+            setattr(self, prop_name, val)
+            return val
+
+    return _cached_func
+
+
+def constant_property(func):
+    """A constant property is a property that only evaluated once."""
+    return property(cached(func))
diff --git a/tests/scripts/expect/v1_2-rcp.exp b/tools/otci/setup.py
old mode 100755
new mode 100644
similarity index 68%
rename from tests/scripts/expect/v1_2-rcp.exp
rename to tools/otci/setup.py
index 776d880..f3d6868
--- a/tests/scripts/expect/v1_2-rcp.exp
+++ b/tools/otci/setup.py
@@ -1,4 +1,4 @@
-#!/usr/bin/expect -f
+#!/usr/bin/env python3
 #
 #  Copyright (c) 2020, The OpenThread Authors.
 #  All rights reserved.
@@ -26,40 +26,25 @@
 #  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 #  POSSIBILITY OF SUCH DAMAGE.
 #
+import setuptools
 
-source "tests/scripts/expect/_common.exp"
+with open("README.md", "r") as fh:
+    long_description = fh.read()
 
-spawn_node 1
-
-send "panid 0xface\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-wait_for "state" "leader"
-expect "Done"
-
-send "ipaddr mleid\n"
-expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})}
-set addr $expect_out(1,string)
-expect "Done"
-
-spawn_node 2 mtd
-
-send "panid 0xface\n"
-expect "Done"
-send "mode -\n"
-expect "Done"
-send "csl period 5000\n"
-expect "Done"
-send "ifconfig up\n"
-expect "Done"
-send "thread start\n"
-expect "Done"
-wait_for "state" "child"
-expect "Done"
-send "ping $addr\n"
-expect "16 bytes from $addr: icmp_seq=1"
-
-dispose_all
+setuptools.setup(
+    name="otci-openthread",
+    version="0.0.1",
+    author="The OpenThread Authors",
+    description="OpenThread Controller Interface",
+    long_description=long_description,
+    long_description_content_type="text/markdown",
+    url="https://github.com/openthread/openthread",
+    packages=setuptools.find_packages(),
+    classifiers=[
+        "Programming Language :: Python :: 3",
+        "License :: OSI Approved :: BSD 3-Clause License",
+        "Operating System :: OS Independent",
+    ],
+    python_requires='>=3.6',
+    install_requires=['pySerial', 'paramiko', 'pyspinel'],
+)
diff --git a/tools/otci/tests/test_otci.py b/tools/otci/tests/test_otci.py
new file mode 100644
index 0000000..0a15835
--- /dev/null
+++ b/tools/otci/tests/test_otci.py
@@ -0,0 +1,525 @@
+#!/usr/bin/env python3
+#
+#  Copyright (c) 2020, The OpenThread Authors.
+#  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.
+#
+import json
+import logging
+import os
+import subprocess
+import unittest
+
+import otci
+from otci.errors import CommandError
+
+logging.basicConfig(level=logging.INFO)
+
+TEST_CHANNEL = 22
+TEST_NETWORK_NAME = 'OT CI'
+TEST_PANID = 0xeeee
+TEST_MASTERKEY = 'ffeeddccbbaa99887766554433221100'
+
+REAL_DEVICE = int(os.getenv('REAL_DEVICE', '0'))
+
+
+class TestOTCI(unittest.TestCase):
+
+    def testCliRealDevice(self):
+        if not REAL_DEVICE:
+            self.skipTest('not for virtual device')
+
+        if os.getenv('OTBR_SSH'):
+            node = otci.connect_otbr_ssh(os.getenv('OTBR_SSH'))
+        elif os.getenv('OT_CLI_SERIAL'):
+            node = otci.connect_cli_serial(os.getenv('OT_CLI_SERIAL'))
+        else:
+            self.fail("Please set OT_CLI_SERIAL or OTBR_SSH to test the real device.")
+
+        node.factory_reset()
+
+        self._test_otci_single_node(node)
+
+    def testCliSimRealTime(self):
+        if REAL_DEVICE:
+            self.skipTest('not for real device')
+
+        subprocess.check_call('rm -rf tmp/', shell=True)
+        VIRTUAL_TIME = int(os.getenv('VIRTUAL_TIME', "1"))
+
+        import simulator
+
+        if VIRTUAL_TIME:
+            sim = simulator.VirtualTime(use_message_factory=False)
+        else:
+            sim = None
+
+        if os.getenv('OT_CLI'):
+            executable = os.getenv('OT_CLI')
+            connector = otci.connect_cli_sim
+        elif os.getenv('OT_NCP'):
+            executable = os.getenv('OT_NCP')
+            connector = otci.connect_ncp_sim
+        else:
+            self.fail("Please set OT_CLI to test virtual device")
+
+        node1 = connector(executable, 1, simulator=sim)
+        self._test_otci_single_node(node1)
+
+        node1.factory_reset()
+
+        node2 = connector(executable, 2, simulator=sim)
+        node3 = connector(executable, 3, simulator=sim)
+        node4 = connector(executable, 4, simulator=sim)
+
+        self._test_otci_example(node1, node2)
+
+        node1.factory_reset()
+        node2.factory_reset()
+
+        self._test_otci_multi_nodes(node1, node2, node3, node4)
+
+    def _test_otci_single_node(self, leader):
+        logging.info('leader version: %r', leader.version)
+        logging.info('leader thread version: %r', leader.thread_version)
+        logging.info('API version: %r', leader.api_version)
+        logging.info('log level: %r', leader.get_log_level())
+
+        leader.enable_promiscuous()
+        self.assertTrue(leader.get_promiscuous())
+        leader.disable_promiscuous()
+        self.assertFalse(leader.get_promiscuous())
+        try:
+            logging.info("RCP version: %r", leader.get_rcp_version())
+        except CommandError:
+            pass
+
+        self.assertTrue(leader.get_router_eligible())
+        leader.disable_router_eligible()
+        self.assertFalse(leader.get_router_eligible())
+        leader.enable_router_eligible()
+
+        self.assertFalse(leader.get_ifconfig_state())
+        # ifconfig up
+        leader.ifconfig_up()
+        self.assertTrue(leader.get_ifconfig_state())
+
+        logging.info('leader eui64 = %r', leader.get_eui64())
+        logging.info('leader extpanid = %r', leader.get_extpanid())
+        logging.info('leader masterkey = %r', leader.get_master_key())
+
+        extaddr = leader.get_extaddr()
+        self.assertEqual(16, len(extaddr))
+        int(extaddr, 16)
+        new_extaddr = 'aabbccddeeff0011'
+        leader.set_extaddr(new_extaddr)
+        self.assertEqual(new_extaddr, leader.get_extaddr())
+
+        leader.set_network_name(TEST_NETWORK_NAME)
+
+        leader.set_master_key(TEST_MASTERKEY)
+        self.assertEqual(TEST_MASTERKEY, leader.get_master_key())
+
+        leader.set_panid(TEST_PANID)
+        self.assertEqual(TEST_PANID, leader.get_panid())
+
+        leader.set_channel(TEST_CHANNEL)
+        self.assertEqual(TEST_CHANNEL, leader.get_channel())
+
+        leader.set_network_name(TEST_NETWORK_NAME)
+        self.assertEqual(TEST_NETWORK_NAME, leader.get_network_name())
+
+        self.assertEqual('rdn', leader.get_mode())
+        leader.set_mode('-')
+        self.assertEqual('-', leader.get_mode())
+        leader.set_mode('rdn')
+        self.assertEqual('rdn', leader.get_mode())
+
+        logging.info('leader weight: %d', leader.get_leader_weight())
+        leader.set_leader_weight(72)
+
+        logging.info('domain name: %r', leader.get_domain_name())
+        leader.set_domain_name("DefaultDomain2")
+        self.assertEqual("DefaultDomain2", leader.get_domain_name())
+
+        self.assertEqual(leader.get_preferred_partition_id(), 0)
+        leader.set_preferred_partition_id(0xabcddead)
+        self.assertEqual(leader.get_preferred_partition_id(), 0xabcddead)
+
+        leader.thread_start()
+        leader.wait(5)
+        self.assertEqual('leader', leader.get_state())
+        self.assertEqual(0xabcddead, leader.get_leader_data()['partition_id'])
+        logging.info('leader key sequence counter = %d', leader.get_key_sequence_counter())
+
+        rloc16 = leader.get_rloc16()
+        leader_id = leader.get_router_id()
+        self.assertEqual(rloc16, leader_id << 10)
+
+        self.assertFalse(leader.get_child_list())
+        self.assertEqual({}, leader.get_child_table())
+
+        leader.enable_allowlist()
+        leader.add_allowlist(leader.get_extaddr())
+        leader.remove_allowlist(leader.get_extaddr())
+        leader.set_allowlist([leader.get_extaddr()])
+        leader.disable_allowlist()
+
+        leader.add_ipmaddr('ff04::1')
+        leader.del_ipmaddr('ff04::1')
+        leader.add_ipmaddr('ff04::2')
+        logging.info('leader ipmaddrs: %r', leader.get_ipmaddrs())
+        self.assertFalse(leader.has_ipmaddr('ff04::1'))
+        self.assertTrue(leader.has_ipmaddr('ff04::2'))
+        self.assertTrue(leader.get_ipaddr_rloc())
+        self.assertTrue(leader.get_ipaddr_linklocal())
+        self.assertTrue(leader.get_ipaddr_mleid())
+
+        leader.add_ipaddr('2001::1')
+        leader.del_ipaddr('2001::1')
+        leader.add_ipaddr('2001::2')
+        logging.info('leader ipaddrs: %r', leader.get_ipaddrs())
+        self.assertFalse(leader.has_ipaddr('2001::1'))
+        self.assertTrue(leader.has_ipaddr('2001::2'))
+
+        logging.info('leader bbr state: %r', leader.get_backbone_router_state())
+        bbr_config = leader.get_backbone_router_config()
+        logging.info('leader bbr config: %r', bbr_config)
+        logging.info('leader PBBR: %r', leader.get_primary_backbone_router_info())
+
+        leader.set_backbone_router_config(seqno=bbr_config['seqno'] + 1, delay=10, timeout=301)
+        self.assertEqual({
+            'seqno': bbr_config['seqno'] + 1,
+            'delay': 10,
+            'timeout': 301
+        }, leader.get_backbone_router_config())
+
+        leader.enable_backbone_router()
+        leader.wait(3)
+
+        logging.info('leader bbr state: %r', leader.get_backbone_router_state())
+        logging.info('leader bbr config: %r', leader.get_backbone_router_config())
+        logging.info('leader PBBR: %r', leader.get_primary_backbone_router_info())
+
+        logging.info('leader bufferinfo: %r', leader.get_message_buffer_info())
+
+        logging.info('child ipaddrs: %r', leader.get_child_ipaddrs())
+        logging.info('child ipmax: %r', leader.get_child_ip_max())
+        leader.set_child_ip_max(2)
+        self.assertEqual(2, leader.get_child_ip_max())
+        logging.info('childmax: %r', leader.get_max_children())
+
+        logging.info('counter names: %r', leader.counter_names)
+        for counter_name in leader.counter_names:
+            logging.info('counter %s: %r', counter_name, leader.get_counter(counter_name))
+            leader.reset_counter(counter_name)
+            self.assertTrue(all(x == 0 for x in leader.get_counter(counter_name).values()))
+
+        logging.info("CSL config: %r", leader.get_csl_config())
+        leader.config_csl(channel=13, period=100, timeout=200)
+        logging.info("CSL config: %r", leader.get_csl_config())
+
+        logging.info("EID-to-RLOC cache: %r", leader.get_eidcache())
+
+        logging.info("ipmaddr promiscuous: %r", leader.get_ipmaddr_promiscuous())
+        leader.enable_ipmaddr_promiscuous()
+        self.assertTrue(leader.get_ipmaddr_promiscuous())
+        leader.disable_ipmaddr_promiscuous()
+        self.assertFalse(leader.get_ipmaddr_promiscuous())
+
+        logging.info("leader data: %r", leader.get_leader_data())
+        logging.info("leader neighbor list: %r", leader.get_neighbor_list())
+        logging.info("leader neighbor table: %r", leader.get_neighbor_table())
+        logging.info("Leader external routes: %r", leader.get_local_routes())
+        leader.add_route('2002::/64')
+        leader.register_network_data()
+        logging.info("Leader external routes: %r", leader.get_local_routes())
+
+        self.assertEqual(1, len(leader.get_router_list()))
+        self.assertEqual(1, len(leader.get_router_table()))
+        logging.info("Leader router table: %r", leader.get_router_table())
+        self.assertFalse(list(leader.get_router_table().values())[0].is_link_established)
+
+        logging.info('scan: %r', leader.scan())
+        logging.info('scan energy: %r', leader.scan_energy())
+
+        leader.add_service(44970, '112233', 'aabbcc')
+        leader.register_network_data()
+        leader.add_service(44971, b'\x11\x22\x33', b'\xaa\xbb\xcc\xdd')
+
+        leader.add_prefix("2001::/64")
+
+        logging.info("network data: %r", leader.get_network_data())
+        logging.info("network data raw: %r", leader.get_network_data_bytes())
+        self.assertEqual(leader.get_network_data()['prefixes'], leader.get_prefixes())
+        self.assertEqual(leader.get_network_data()['routes'], leader.get_routes())
+        self.assertEqual(leader.get_network_data()['services'], leader.get_services())
+
+        logging.info("local prefixes: %r", leader.get_local_prefixes())
+        logging.info("local routes: %r", leader.get_local_routes())
+
+        logging.info('txpower %r', leader.get_txpower())
+        leader.set_txpower(-10)
+        self.assertEqual(-10, leader.get_txpower())
+
+        self.assertTrue(leader.is_singleton())
+
+        leader.coap_start()
+        leader.coap_set_test_resource_path('test')
+        leader.coap_test_set_resource_content('helloworld')
+        leader.coap_get(leader.get_ipaddr_rloc(), 'test')
+        leader.coap_put(leader.get_ipaddr_rloc(), 'test', 'con', 'xxx')
+        leader.coap_post(leader.get_ipaddr_rloc(), 'test', 'con', 'xxx')
+        leader.coap_delete(leader.get_ipaddr_rloc(), 'test', 'con', 'xxx')
+        leader.wait(1)
+        leader.coap_stop()
+
+        leader.udp_open()
+        leader.udp_bind("::", 1234)
+        leader.udp_send(leader.get_ipaddr_rloc(), 1234, text='hello')
+        leader.udp_send(leader.get_ipaddr_rloc(), 1234, random_bytes=3)
+        leader.udp_send(leader.get_ipaddr_rloc(), 1234, hex='112233')
+        leader.wait(1)
+        leader.udp_close()
+
+        logging.info('dataset: %r', leader.get_dataset())
+        logging.info('dataset active: %r', leader.get_dataset('active'))
+
+        leader.dataset_init_buffer()
+        leader.dataset_commit_buffer('pending')
+        leader.dataset_init_buffer(get_active_dataset=True)
+        leader.dataset_init_buffer(get_pending_dataset=True)
+
+        logging.info('dataset: %r', leader.get_dataset())
+        logging.info('dataset active: %r', leader.get_dataset('active'))
+        logging.info('dataset pending: %r', leader.get_dataset('pending'))
+
+        logging.info('dataset active -x: %r', leader.get_dataset_bytes('active'))
+        logging.info('dataset pending -x: %r', leader.get_dataset_bytes('pending'))
+
+    def _test_otci_example(self, node1, node2):
+        node1.dataset_init_buffer()
+        node1.dataset_set_buffer(network_name='test',
+                                 master_key='00112233445566778899aabbccddeeff',
+                                 panid=0xface,
+                                 channel=11)
+        node1.dataset_commit_buffer('active')
+
+        node1.ifconfig_up()
+        node1.thread_start()
+        node1.wait(5)
+        assert node1.get_state() == "leader"
+
+        node1.commissioner_start()
+        node1.wait(3)
+
+        node1.commissioner_add_joiner("TEST123", eui64='*')
+
+        node2.ifconfig_up()
+        node2.set_router_selection_jitter(1)
+
+        node2.joiner_start("TEST123")
+        node2.wait(10, expect_line="Join success")
+        node2.thread_start()
+        node2.wait(5)
+        assert node2.get_state() == "router"
+
+    def _test_otci_multi_nodes(self, leader, commissioner, child1, child2):
+        self.assertFalse(leader.get_ifconfig_state())
+
+        # ifconfig up
+        leader.ifconfig_up()
+        self.assertTrue(leader.get_ifconfig_state())
+
+        logging.info('leader eui64 = %r', leader.get_eui64())
+        logging.info('leader extpanid = %r', leader.get_extpanid())
+        logging.info('leader masterkey = %r', leader.get_master_key())
+
+        extaddr = leader.get_extaddr()
+        self.assertEqual(16, len(extaddr))
+        int(extaddr, 16)
+        new_extaddr = 'aabbccddeeff0011'
+        leader.set_extaddr(new_extaddr)
+        self.assertEqual(new_extaddr, leader.get_extaddr())
+
+        leader.set_network_name(TEST_NETWORK_NAME)
+
+        leader.set_master_key(TEST_MASTERKEY)
+        self.assertEqual(TEST_MASTERKEY, leader.get_master_key())
+
+        leader.set_panid(TEST_PANID)
+        self.assertEqual(TEST_PANID, leader.get_panid())
+
+        leader.set_channel(TEST_CHANNEL)
+        self.assertEqual(TEST_CHANNEL, leader.get_channel())
+
+        leader.set_network_name(TEST_NETWORK_NAME)
+        self.assertEqual(TEST_NETWORK_NAME, leader.get_network_name())
+
+        self.assertEqual('rdn', leader.get_mode())
+
+        leader.thread_start()
+        leader.wait(5)
+        self.assertEqual('leader', leader.get_state())
+        logging.info('leader key sequence counter = %d', leader.get_key_sequence_counter())
+
+        rloc16 = leader.get_rloc16()
+        leader_id = leader.get_router_id()
+        self.assertEqual(rloc16, leader_id << 10)
+
+        commissioner.ifconfig_up()
+        commissioner.set_channel(TEST_CHANNEL)
+        commissioner.set_panid(TEST_PANID)
+        commissioner.set_network_name(TEST_NETWORK_NAME)
+        commissioner.set_router_selection_jitter(1)
+        commissioner.set_master_key(TEST_MASTERKEY)
+        commissioner.thread_start()
+
+        commissioner.wait(5)
+
+        self.assertEqual('router', commissioner.get_state())
+
+        for dst_ip in leader.get_ipaddrs():
+            commissioner.ping(dst_ip, size=10, count=1, interval=2, hoplimit=3)
+
+        self.assertEqual('disabled', commissioner.get_commissioiner_state())
+        commissioner.commissioner_start()
+        commissioner.wait(5)
+        self.assertEqual('active', commissioner.get_commissioiner_state())
+
+        logging.info('commissioner.get_network_id_timeout() = %d', commissioner.get_network_id_timeout())
+        commissioner.set_network_id_timeout(60)
+        self.assertEqual(60, commissioner.get_network_id_timeout())
+
+        commissioner.commissioner_add_joiner('TEST123', eui64='*')
+        commissioner.wait(3)
+
+        child1.ifconfig_up()
+
+        logging.info("child1 scan: %r", child1.scan())
+        logging.info("child1 scan energy: %r", child1.scan_energy())
+
+        child1.set_mode('rn')
+        child1.set_router_selection_jitter(1)
+
+        child1.joiner_start('TEST123')
+        logging.info('joiner id = %r', child1.get_joiner_id())
+        child1.wait(10, expect_line="Join success")
+
+        child1.enable_allowlist()
+        child1.disable_allowlist()
+        child1.add_allowlist(commissioner.get_extaddr())
+        child1.remove_allowlist(commissioner.get_extaddr())
+        child1.set_allowlist([commissioner.get_extaddr()])
+
+        child1.thread_start()
+        child1.wait(3)
+        self.assertEqual('child', child1.get_state())
+
+        child1.thread_stop()
+
+        child1.set_mode('n')
+        child1.set_poll_period(1000)
+        self.assertEqual(1000, child1.get_poll_period())
+
+        child1.thread_start()
+        child1.wait(3)
+        self.assertEqual('child', child1.get_state())
+
+        child2.ifconfig_up()
+        child2.set_mode('rn')
+        child2.set_router_selection_jitter(1)
+
+        child2.joiner_start('TEST123')
+        logging.info('joiner id = %r', child2.get_joiner_id())
+        child2.wait(10, expect_line="Join success")
+
+        child2.enable_allowlist()
+        child2.disable_allowlist()
+        child2.add_allowlist(commissioner.get_extaddr())
+        child2.remove_allowlist(commissioner.get_extaddr())
+        child2.set_allowlist([commissioner.get_extaddr()])
+
+        child2.thread_start()
+        child2.wait(3)
+        self.assertEqual('child', child2.get_state())
+
+        child_table = commissioner.get_child_table()
+        logging.info('commissioiner child table: \n%s\n', json.dumps(child_table, indent=True))
+        child_list = commissioner.get_child_list()
+        logging.info('commissioiner child list: %r', child_list)
+        for child_id in child_list:
+            logging.info('child %s info: %r', child_id, commissioner.get_child_info(child_id))
+
+        logging.info('child1 info: %r', commissioner.get_child_info(child1.get_rloc16()))
+        logging.info('child2 info: %r', commissioner.get_child_info(child2.get_rloc16()))
+
+        self.assertEqual(set(commissioner.get_child_list()), set(commissioner.get_child_table().keys()))
+
+        child1.add_ipmaddr('ff04::1')
+        child1.del_ipmaddr('ff04::1')
+        child1.add_ipmaddr('ff04::2')
+        logging.info('child1 ipmaddrs: %r', child1.get_ipmaddrs())
+        self.assertFalse(child1.has_ipmaddr('ff04::1'))
+        self.assertTrue(child1.has_ipmaddr('ff04::2'))
+
+        child1.add_ipaddr('2001::1')
+        child1.del_ipaddr('2001::1')
+        child1.add_ipaddr('2001::2')
+        logging.info('child1 ipaddrs: %r', child1.get_ipaddrs())
+        self.assertFalse(child1.has_ipaddr('2001::1'))
+        self.assertTrue(child1.has_ipaddr('2001::2'))
+
+        logging.info('child ipaddrs: %r', commissioner.get_child_ipaddrs())
+
+        logging.info("EID-to-RLOC cache: %r", leader.get_eidcache())
+
+        logging.info("leader neighbor list: %r", leader.get_neighbor_list())
+        logging.info("leader neighbor table: %r", leader.get_neighbor_table())
+        logging.info("prefixes: %r", commissioner.get_local_prefixes())
+        commissioner.add_prefix('2001::/64')
+        commissioner.register_network_data()
+        commissioner.wait(1)
+        logging.info("prefixes: %r", commissioner.get_local_prefixes())
+
+        self.assertEqual(2, len(leader.get_router_list()))
+        self.assertEqual(2, len(leader.get_router_table()))
+        logging.info('leader router table: %r', leader.get_router_table())
+        self.assertEqual({False, True},
+                         set(router.is_link_established for router in leader.get_router_table().values()))
+
+        self.assertFalse(leader.is_singleton())
+
+        # Shutdown
+        leader.thread_stop()
+        logging.info("node state: %s", leader.get_state())
+        leader.ifconfig_down()
+        self.assertFalse(leader.get_ifconfig_state())
+
+        leader.close()
+
+
+if __name__ == '__main__':
+    unittest.main()