• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/bin/bash
2#
3#  Copyright (c) 2017, The OpenThread Authors.
4#  All rights reserved.
5#
6#  Redistribution and use in source and binary forms, with or without
7#  modification, are permitted provided that the following conditions are met:
8#  1. Redistributions of source code must retain the above copyright
9#     notice, this list of conditions and the following disclaimer.
10#  2. Redistributions in binary form must reproduce the above copyright
11#     notice, this list of conditions and the following disclaimer in the
12#     documentation and/or other materials provided with the distribution.
13#  3. Neither the name of the copyright holder nor the
14#     names of its contributors may be used to endorse or promote products
15#     derived from this software without specific prior written permission.
16#
17#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27#  POSSIBILITY OF SUCH DAMAGE.
28#
29# Test thread commissioning along with openthread.
30#
31# Usage:
32#   ./meshcop                        # test with latest openthread.
33#   NO_CLEAN=1 ./meshcop             # test with existing binaries in ${TEST_BASE}.
34#   TEST_CASE=mdns_service ./meshcop # test the meshcop mDNS service.
35set -euxo pipefail
36
37# The test case to run. available cases are:
38# - commissioning
39# - mdns_service
40readonly TEST_CASE="${TEST_CASE:-commissioning}"
41
42# Get our starting directory and remember it
43readonly ORIGIN_PWD="$(pwd)"
44readonly SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
45
46#---------------------------------------
47# Configurations
48#---------------------------------------
49readonly OT_RCP="ot-rcp"
50readonly OT_CLI="${OT_CLI:-ot-cli-ftd}"
51readonly ABS_TOP_BUILDDIR="$(cd "${top_builddir:-"${SCRIPT_DIR}"/../../}" && pwd)"
52readonly ABS_TOP_SRCDIR="$(cd "${top_srcdir:-"${SCRIPT_DIR}"/../../}" && pwd)"
53readonly NO_CLEAN="${NO_CLEAN:-1}"
54readonly IGNORE_INSTALLED="${IGNORE_INSTALLED:-0}"
55readonly OTBR_USE_WEB_COMMISSIONER="${USE_WEB_COMMISSIONER:-0}"
56
57#----------------------------------------
58# Test constants
59#----------------------------------------
60readonly TEST_BASE=/tmp/test-otbr
61
62readonly OTBR_AGENT=otbr-agent
63readonly OTBR_WEB=otbr-web
64readonly OT_COMMISSIONER_CLI=commissioner-cli
65
66readonly STAGE_DIR="${TEST_BASE}/stage"
67readonly BUILD_DIR="${TEST_BASE}/build"
68readonly OTBR_PSKC_PATH="${ABS_TOP_BUILDDIR}/tools/pskc"
69readonly OTBR_AGENT_PATH="${ABS_TOP_BUILDDIR}/src/agent/${OTBR_AGENT}"
70readonly OTBR_DBUS_CONF="${ABS_TOP_BUILDDIR}/src/agent/otbr-agent.conf"
71readonly OTBR_WEB_PATH="${ABS_TOP_BUILDDIR}/src/web/${OTBR_WEB}"
72readonly OT_CTL="${ABS_TOP_BUILDDIR}/third_party/openthread/repo/src/posix/ot-ctl"
73
74# The node ids
75readonly LEADER_NODE_ID=1
76readonly JOINER_NODE_ID=2
77
78# Web GUI
79readonly OTBR_WEB_HOST=127.0.0.1
80readonly OTBR_WEB_PORT=8773
81readonly OTBR_WEB_URL="http://${OTBR_WEB_HOST}:${OTBR_WEB_PORT}"
82
83# External commissioner
84readonly OT_COMMISSIONER_PATH=${BUILD_DIR}/ot-commissioner/build/src/app/cli/commissioner-cli
85readonly OT_COMMISSIONER_CONFIG=${BUILD_DIR}/ot-commissioner/src/app/etc/commissioner/non-ccm-config.json
86
87#
88# NOTE Joiner pass phrase:
89#   Must be at least 6 bytes long
90#   And this example has: J ZERO ONE N E R
91#   We cannot use letter O and I because Q O I Z are not allowed per spec
92readonly OT_JOINER_PASSPHRASE=J01NER
93
94# 18b430 is the nest EUI prefix.
95readonly OT_JOINER_EUI64="18b430000000000${JOINER_NODE_ID}"
96
97# The border agent, and ncp needs a pass phrase.
98readonly OT_AGENT_PASSPHRASE=MYPASSPHRASE
99
100# The network needs a name.
101readonly OT_NETWORK_NAME=MyTestNetwork
102
103# The TUN device for OpenThread border router.
104readonly TUN_NAME=wpan0
105
106# The default meshcop service instance name
107readonly OT_SERVICE_INSTANCE='OpenThread\(\\032\| \)BorderRouter\(\\032\| \)#[0-9A-F][0-9A-F][0-9A-F][0-9A-F]'
108
109echo "ORIGIN_PWD: ${ORIGIN_PWD}"
110echo "TEST_BASE: ${TEST_BASE}"
111echo "ABS_TOP_SRCDIR=${ABS_TOP_SRCDIR}"
112echo "ABS_TOP_BUILDDIR=${ABS_TOP_BUILDDIR}"
113
114#----------------------------------------
115# Helper functions
116#----------------------------------------
117
118die()
119{
120    exit_message="$*"
121    echo " *** ERROR: $*"
122    exit 1
123}
124
125exists_or_die()
126{
127    [[ -f $1 ]] || die "Missing file: $1"
128}
129
130executable_or_die()
131{
132    [[ -x $1 ]] || die "Missing executable: $1"
133}
134
135random_channel()
136{
137    echo $((11 + "${RANDOM}" % 16))
138}
139
140random_panid()
141{
142    printf "0x%04x" "${RANDOM}"
143}
144
145random_xpanid()
146{
147    printf "%04x%04x%04x%04x" "${RANDOM}" "${RANDOM}" "${RANDOM}" "${RANDOM}"
148}
149
150random_networkkey()
151{
152    printf "%04x%04x%04x%04x%04x%04x%04x%04x" "${RANDOM}" "${RANDOM}" "${RANDOM}" "${RANDOM}" "${RANDOM}" "${RANDOM}" "${RANDOM}" "${RANDOM}"
153}
154
155write_syslog()
156{
157    logger -s -p syslog.alert "OPENTHREAD_TEST: $*"
158}
159
160output_logs()
161{
162    write_syslog 'All apps should be dead now'
163
164    # part 1
165    # ------
166    #
167    # On travis (the CI server), we can't see what went into the
168    # syslog.  So this is here so we can see the output.
169    #
170    # part 2
171    # ------
172    #
173    # If we run locally, it is sometimes helpful for our victim (you
174    # the developer) to have logs split upto various files to help
175    # that victim, we'll GREP the log files according.
176    #
177    # Wait 5 seconds for the "logs to flush"
178    sleep 5
179
180    cd "${ORIGIN_PWD}"
181    echo 'START_LOG: SYSLOG ==================='
182    tee complete-syslog.log </var/log/syslog
183    echo 'START_LOG: BR-AGENT ================='
184    grep "${OTBR_AGENT}" /var/log/syslog | tee otbr-agent.log
185    echo 'START_LOG: OT-COMISSIONER ========='
186    cat "${OT_COMMISSIONER_LOG}"
187    echo 'START_LOG: OT-RCP ==================='
188    grep "${OT_RCP}" /var/log/syslog | tee "${OT_RCP}.log"
189    echo 'START_LOG: OT-CLI ==================='
190    grep "${OT_CLI}" /var/log/syslog | tee "${OT_CLI}.log"
191    echo '====================================='
192    echo 'Hint, for each log Search backwards for: "START_LOG: <NAME>"'
193    echo '====================================='
194}
195
196build_dependencies()
197{
198    # Clean up old stuff
199    if [[ ${NO_CLEAN} != 1 ]]; then
200        [[ ! -d ${STAGE_DIR} ]] || rm -rf "${STAGE_DIR}"
201        [[ ! -d ${BUILD_DIR} ]] || rm -rf "${BUILD_DIR}"
202    fi
203
204    [[ -d ${STAGE_DIR} ]] || mkdir -p "${STAGE_DIR}"
205    [[ -d ${BUILD_DIR} ]] || mkdir -p "${BUILD_DIR}"
206
207    # As above, these steps are broken up
208    ot_cli=$(command -v "${OT_CLI}")
209    ot_rcp=$(command -v "${OT_RCP}")
210
211    if
212        [ "${TEST_CASE}" == "commissioning" ] \
213            && [[ ${OTBR_USE_WEB_COMMISSIONER} != 1 ]]
214    then
215        ot_commissioner_build
216    fi
217
218    write_syslog "TEST: BUILD COMPLETE"
219}
220
221test_setup()
222{
223    # message for general failures
224    exit_message="JOINER FAILED"
225
226    executable_or_die "${OTBR_AGENT_PATH}"
227    executable_or_die "${OTBR_WEB_PATH}"
228
229    # Remove flashes
230    sudo rm -vrf "${TEST_BASE}/tmp"
231    # OPENTHREAD_POSIX_DAEMON_SOCKET_LOCK
232    sudo rm -vf "/tmp/openthread.lock"
233
234    build_dependencies
235
236    # We will be creating a lot of log information
237    # Rotate logs so we have a clean and empty set of logs uncluttered with other stuff
238    if [[ -f /etc/logrotate.conf ]]; then
239        sudo logrotate -f /etc/logrotate.conf || true
240    fi
241
242    # From now on - all exits are TRAPPED
243    # When they occur, we call the function: output_logs'.
244    trap test_teardown EXIT
245}
246
247test_teardown()
248{
249    # Capture the exit code so we can return it below
250    readonly EXIT_CODE=$?
251    write_syslog "EXIT ${EXIT_CODE} - output logs"
252
253    sudo pkill -f "${OTBR_AGENT}" || true
254    sudo pkill -f "${OTBR_WEB}" || true
255    sudo pkill -f "${OT_COMMISSIONER_CLI}" || true
256    sudo pkill -f "${OT_CLI}" || true
257    wait
258
259    if [[ ${NO_CLEAN} != 1 ]]; then
260        echo 'clearing all'
261        sudo rm /etc/dbus-1/system.d/otbr-agent.conf || true
262        sudo rm -rf "${STAGE_DIR}" || true
263        sudo rm -rf "${BUILD_DIR}" || true
264
265        output_logs
266    fi
267
268    echo "EXIT ${EXIT_CODE}: MESSAGE: ${exit_message}"
269    exit ${EXIT_CODE}
270}
271
272ba_start()
273{
274    exists_or_die "${OTBR_DBUS_CONF}"
275    sudo cp "${OTBR_DBUS_CONF}" /etc/dbus-1/system.d
276
277    write_syslog "AGENT: kill old"
278    sudo killall "${OTBR_AGENT}" || true
279    sleep 5
280    write_syslog "AGENT: starting"
281
282    # we launch this in the background
283    (
284        set -e
285        set -x
286
287        cd "${ORIGIN_PWD}"
288
289        # check version
290        sudo "${OTBR_AGENT_PATH}" -V
291        # check invalid arguments
292        sudo "${OTBR_AGENT_PATH}" -x && exit $?
293
294        [[ ! -d tmp ]] || sudo rm -rf tmp
295        sudo "${OTBR_AGENT_PATH}" -I "${TUN_NAME}" -v -d 6 "spinel+hdlc+forkpty://${ot_rcp}?forkpty-arg=${LEADER_NODE_ID}" &
296    )
297
298    # wait for it to complete
299    sleep 10
300
301    pidof ${OTBR_AGENT} || die "AGENT: failed to start"
302    write_syslog "AGENT: start complete"
303}
304
305web_start()
306{
307    write_syslog "WEB: kill old"
308    sudo killall "${OTBR_WEB}" || true
309    write_syslog "WEB: starting"
310    (
311        set -e
312        set -x
313
314        cd "${ORIGIN_PWD}"
315        sudo "${OTBR_WEB_PATH}" -I "${TUN_NAME}" -p "${OTBR_WEB_PORT}" -a "${OTBR_WEB_HOST}" &
316    )
317    sleep 15
318
319    pidof ${OTBR_WEB} || die "WEB: failed to start"
320    write_syslog "WEB: start complete"
321}
322
323network_form()
324{
325    readonly OT_PANID="$(random_panid)"
326    readonly OT_XPANID="$(random_xpanid)"
327    readonly OT_NETWORK_KEY="$(random_networkkey)"
328    readonly OT_CHANNEL="$(random_channel)"
329
330    curl --header "Content-Type: application/json" --request POST --data "{\"networkKey\":\"${OT_NETWORK_KEY}\",\"prefix\":\"fd11:22::\",\"defaultRoute\":true,\"extPanId\":\"${OT_XPANID}\",\"panId\":\"${OT_PANID}\",\"passphrase\":\"${OT_AGENT_PASSPHRASE}\",\"channel\":${OT_CHANNEL},\"networkName\":\"${OT_NETWORK_NAME}\"}" "${OTBR_WEB_URL}"/form_network | grep "success" || die "WEB: form failed"
331    sleep 15
332    # verify mDNS is working as expected.
333    local mdns_result="${TEST_BASE}"/mdns_result.log
334    avahi-browse -art | tee "${mdns_result}"
335    OT_BORDER_AGENT_PORT=$(grep -GA3 '^=.\+'"${OT_SERVICE_INSTANCE}"'.\+_meshcop._udp' "${mdns_result}" | head -n4 | grep port | grep -ao '[0-9]\{5\}')
336    rm "${mdns_result}"
337}
338
339ot_commissioner_build()
340{
341    if [[ -x ${OT_COMMISSIONER_PATH} ]]; then
342        return 0
343    fi
344
345    (mkdir -p "${BUILD_DIR}/ot-commissioner" \
346        && cd "${BUILD_DIR}/ot-commissioner" \
347        && (git --git-dir=.git rev-parse --is-inside-work-tree || git --git-dir=.git init .) \
348        && git fetch --depth 1 https://github.com/openthread/ot-commissioner.git main \
349        && git checkout FETCH_HEAD \
350        && ./script/bootstrap.sh \
351        && mkdir build && cd build \
352        && cmake -GNinja -DCMAKE_BUILD_TYPE=Release .. \
353        && ninja)
354}
355
356ot_commissioner_start()
357{
358    write_syslog "COMMISSIONER: kill old"
359    sudo killall "${OT_COMMISSIONER_CLI}" || true
360
361    readonly OT_PSKC="$("${OTBR_PSKC_PATH}" "${OT_AGENT_PASSPHRASE}" "${OT_XPANID}" "${OT_NETWORK_NAME}")"
362    readonly OT_COMMISSIONER_LOG="${TEST_BASE}"/commissioner.log
363
364    local commissioner_config_file="${TEST_BASE}"/ot-commissioner.json
365
366    sed "s/3aa55f91ca47d1e4e71a08cb35e91591/${OT_PSKC}/g" "${OT_COMMISSIONER_CONFIG}" >"${commissioner_config_file}"
367
368    expect -f- <<EOF &
369spawn ${OT_COMMISSIONER_PATH} ${commissioner_config_file}
370set timeout 1
371expect_after {
372    timeout { exit 1 }
373}
374send "start :: $OT_BORDER_AGENT_PORT\n"
375expect "done"
376sleep 5
377send "active\n"
378expect "true"
379send "joiner enable meshcop 0x${OT_JOINER_EUI64} ${OT_JOINER_PASSPHRASE}\n"
380expect "done"
381wait
382EOF
383
384    sleep 10
385}
386
387web_commissioner_start()
388{
389    curl --header "Content-Type: application/json" --request POST --data "{\"pskd\":\"${OT_JOINER_PASSPHRASE}\", \"passphrase\":\"${OT_AGENT_PASSPHRASE}\"}" "${OTBR_WEB_URL}"/commission
390    sleep 15
391}
392
393joiner_start()
394{
395    write_syslog 'JOINER START'
396    cd ${TEST_BASE}
397    sudo expect -f- <<EOF || die 'JOINER FAILED'
398spawn ${ot_cli} ${JOINER_NODE_ID}
399send "ifconfig up\r\n"
400expect "Done"
401send "joiner start ${OT_JOINER_PASSPHRASE}\r\n"
402set timeout 20
403expect {
404  "Join success" {
405    send_user "succeeded to find join success"
406    send "exit\r\n"
407  }
408  timeout {
409    send_user "Failed to find join success"
410    exit 1
411  }
412}
413EOF
414    exit_message="JOINER SUCCESS COMPLETE"
415}
416
417scan_meshcop_service()
418{
419    if command -v dns-sd; then
420        timeout 2 dns-sd -Z _meshcop._udp local. || true
421    else
422        avahi-browse -aprt || true
423    fi
424}
425
426test_meshcop_service()
427{
428    local network_name="ot-test-net"
429    local xpanid="4142434445464748"
430    local xpanid_txt="ABCDEFGH"
431    local extaddr="4142434445464748"
432    local extaddr_txt="ABCDEFGH"
433    local passphrase="SECRET"
434    local service
435
436    test_setup
437    ba_start
438    sudo "${OT_CTL}" factoryreset
439    sleep 1
440    sudo "${OT_CTL}" dataset init new
441    sudo "${OT_CTL}" dataset networkname ${network_name}
442    sudo "${OT_CTL}" dataset extpanid ${xpanid}
443    sudo "${OT_CTL}" dataset pskc -p ${passphrase}
444    sudo "${OT_CTL}" dataset commit active
445    sudo "${OT_CTL}" ifconfig up
446    sudo "${OT_CTL}" extaddr ${extaddr}
447    sudo "${OT_CTL}" thread start
448    sleep 20
449
450    sudo "${OT_CTL}" state | grep "leader"
451
452    service="$(scan_meshcop_service)"
453    grep "${OT_SERVICE_INSTANCE}._meshcop\._udp" <<<"${service}"
454    grep "rv=1" <<<"${service}"
455    grep "tv=1\.3\.0" <<<"${service}"
456    grep "nn=${network_name}" <<<"${service}"
457    grep "xp=${xpanid_txt}" <<<"${service}"
458    grep "xa=${extaddr_txt}" <<<"${service}"
459
460    # TODO: enable the checks after enabling Thread 1.2 for tests.
461    #grep "dn=${domain_name}" <<< "${service}"
462    #grep "sq=" <<< "${service}"
463    #grep "bb=" <<< "${service}"
464
465    # The binary values are not printable with dns-sd.
466    grep "sb=" <<<"${service}"
467    grep "at=" <<<"${service}"
468    grep "pt=" <<<"${service}"
469
470    # Test if the meshcop service is published when thread is not on
471    sudo "${OT_CTL}" dataset init active
472    sudo "${OT_CTL}" dataset pskc 00000000000000000000000000000000
473    sudo "${OT_CTL}" dataset commit active
474    sleep 2
475    service="$(scan_meshcop_service)"
476    grep -q "${OT_SERVICE_INSTANCE}._meshcop\._udp" <<<"${service}"
477
478    # Test if the meshcop service is published again when a non-zero
479    # PSKc is set back.
480    sudo "${OT_CTL}" dataset init active
481    sudo "${OT_CTL}" dataset pskc 11223344556677889900aabbccddeeff
482    sudo "${OT_CTL}" dataset commit active
483    sleep 2
484    service="$(scan_meshcop_service)"
485    grep "${OT_SERVICE_INSTANCE}._meshcop\._udp" <<<"${service}"
486
487    # Test if the meshcop service's 'nn' field is updated
488    # when the network name is changed.
489    local new_network_name="ot-test-net-new"
490    sudo "${OT_CTL}" dataset init active
491    sudo "${OT_CTL}" dataset networkname ${new_network_name}
492    sudo "${OT_CTL}" dataset commit active
493    sleep 2
494    service="$(scan_meshcop_service)"
495    grep "${OT_SERVICE_INSTANCE}._meshcop\._udp" <<<"${service}"
496    grep "nn=${new_network_name}" <<<"${service}"
497
498    # Test if the discriminator is updated when extaddr is changed.
499    local new_extaddr="4847464544434241"
500    local new_extaddr_txt="HGFEDCBA"
501    sudo "${OT_CTL}" thread stop
502    sudo "${OT_CTL}" extaddr ${new_extaddr}
503    sudo "${OT_CTL}" thread start
504    sleep 5
505    service="$(scan_meshcop_service)"
506    grep "${OT_SERVICE_INSTANCE}._meshcop\._udp" <<<"${service}"
507    grep "xa=${new_extaddr_txt}" <<<"${service}"
508
509    # Test if the meshcop service is published when Thread is stopped.
510    sudo "${OT_CTL}" thread stop
511    sleep 2
512    service="$(scan_meshcop_service)"
513    grep -q "${OT_SERVICE_INSTANCE}._meshcop\._udp" <<<"${service}"
514
515    sudo "${OT_CTL}" thread start
516    sleep 5
517    service="$(scan_meshcop_service)"
518    grep "${OT_SERVICE_INSTANCE}._meshcop\._udp" <<<"${service}"
519
520    # Test if the the meshcop service is unpublished when otbr-agent stops.
521    sudo killall "${OTBR_AGENT}"
522    sleep 10
523    service="$(scan_meshcop_service)"
524    if grep -q "${OT_SERVICE_INSTANCE}._meshcop\._udp" <<<"${service}"; then
525        die "unexpect meshcop service when otbr-agent exits!"
526    fi
527}
528
529test_commissioning()
530{
531    test_setup
532    ba_start
533    web_start
534    network_form
535    if [[ ${OTBR_USE_WEB_COMMISSIONER} == 1 ]]; then
536        web_commissioner_start
537    else
538        ot_commissioner_start
539    fi
540    joiner_start
541}
542
543main()
544{
545    if [ "${TEST_CASE}" == "mdns_service" ]; then
546        test_meshcop_service
547    else
548        test_commissioning
549    fi
550}
551
552main "$@"
553