1#!/bin/bash
2#
3#  Copyright (c) 2018, 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
30set -e
31set -x
32
33die()
34{
35    echo " *** ERROR: " "$*"
36    exit 1
37}
38
39at_exit()
40{
41    EXIT_CODE=$?
42
43    sudo killall expect || true
44    sudo killall ot-ctl || true
45    sudo killall ot-daemon || true
46    sudo killall ot-cli || true
47    sudo killall ot-rcp || true
48    sudo killall socat || true
49
50    exit $EXIT_CODE
51}
52
53wait_for_socat()
54{
55    if [[ "$(head -n2 "$SOCAT_OUTPUT" | wc -l | tr -d ' ')" == 2 ]]; then
56        RADIO_PTY=$(head -n1 "$SOCAT_OUTPUT" | grep -o '/dev/.\+')
57        CORE_PTY=$(head -n2 "$SOCAT_OUTPUT" | tail -n1 | grep -o '/dev/.\+')
58        return 0
59    else
60        echo 'Still waiting for socat'
61    fi
62    return 1
63}
64
65wait_for_leader()
66{
67    if grep -q leader "$OT_OUTPUT"; then
68        return 0
69    else
70        echo 'Still waiting for leader'
71    fi
72    return 1
73}
74
75timeout_run()
76{
77    local count="$1"
78    local exit_code
79    shift 1
80
81    while [[ $count != 0 && $exit_code != 0 ]]; do
82        count=$((count - 1))
83        "$@" && return 0 || exit_code=$?
84        sleep 1
85    done
86
87    return $exit_code
88}
89
90do_build()
91{
92    ./script/cmake-build simulation
93    ./script/cmake-build posix -DOT_PLATFORM_NETIF=1 -DOT_PLATFORM_UDP=1 -DOT_UDP_FORWARD=0 -DOT_POSIX_MAX_POWER_TABLE=1 -DOT_DAEMON="${OT_DAEMON}" -DOT_READLINE="${OT_READLINE}"
94}
95
96do_check()
97{
98    trap at_exit INT TERM EXIT
99
100    sudo rm -rf tmp
101
102    SOCAT_OUTPUT=/tmp/ot-socat
103    OT_OUTPUT=/tmp/ot-output
104    socat -d -d pty,raw,echo=0 pty,raw,echo=0 >/dev/null 2>$SOCAT_OUTPUT &
105    timeout_run 10 wait_for_socat
106    echo 'RADIO_PTY' "$RADIO_PTY"
107    echo 'CORE_PTY' "$CORE_PTY"
108
109    RADIO_NCP_PATH="$PWD/build/simulation/examples/apps/ncp/ot-rcp"
110
111    # shellcheck disable=SC2094
112    $RADIO_NCP_PATH 1 >"$RADIO_PTY" <"$RADIO_PTY" &
113
114    # Cover setting a valid network interface name.
115    VALID_NETIF_NAME="wan$(date +%H%M%S)"
116    readonly VALID_NETIF_NAME
117
118    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"
119
120    if [[ ${OT_DAEMON} == 'on' ]]; then
121        sudo -E "$PWD/build/posix/src/posix/ot-daemon" -d7 -v -I "${VALID_NETIF_NAME}" "${RADIO_URL}" 2>&1 | tee "${OT_OUTPUT}" &
122        sleep 3
123        # macOS cannot explicitly set network interface name
124        NETIF_NAME=$(grep -o 'Thread interface: .\+' "${OT_OUTPUT}" | cut -d: -f2 | tr -d ' \r\n')
125        OT_CTL_PATH="$PWD/build/posix/src/posix/ot-ctl"
126        if [[ ${OT_DAEMON_ALLOW_ALL} == 1 ]]; then
127            OT_CTL=("${OT_CTL_PATH}")
128        else
129            OT_CTL=(sudo "${OT_CTL_PATH}")
130        fi
131        "${OT_CTL[@]}" -I "${NETIF_NAME}" panid 0xface | grep 'Done' || die 'failed to set panid with ot-ctl'
132
133        # verify supports options in OpenThread commands without separator --
134        "${OT_CTL[@]}" -I "${NETIF_NAME}" pskc -p 123456 | grep 'Done' || die 'unable to set pskc'
135
136        # verify this reset and factoryreset end immediately
137        "${OT_CTL[@]}" -I "${NETIF_NAME}" reset
138        # sleep a while for daemon ready
139        sleep 2
140        "${OT_CTL[@]}" -I "${NETIF_NAME}" factoryreset
141        # sleep a while for daemon ready
142        sleep 2
143
144        OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH=640
145        readonly OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH
146        local -r kMaxStringLength="$((OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH - 1))"
147
148        # verify success if command length doesn't exceed the limit
149        for len in $(seq 1 ${kMaxStringLength}); do
150            "${OT_CTL[@]}" -I "${NETIF_NAME}" "$(printf '1%.0s' $(seq 1 "${len}"))"
151        done
152
153        # verify failure if command length exceeds the limit
154        len=${OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH}
155        if "${OT_CTL[@]}" -I "${NETIF_NAME}" "$(printf '1%.0s' $(seq 1 "${len}"))"; then
156            die
157        fi
158        OT_CLI_CMD="${OT_CTL[*]} -I ${NETIF_NAME}"
159    else
160        OT_CLI="$PWD/build/posix/src/posix/ot-cli"
161        sudo "${OT_CLI}" -I "${VALID_NETIF_NAME}" -n "${RADIO_URL}"
162
163        # Cover setting a too long(max is 15 characters) network interface name.
164        # Expect exit code to be 2(OT_EXIT_INVALID_ARGUMENTS).
165        INVALID_NETIF_NAME="wan0123456789123"
166        readonly INVALID_NETIF_NAME
167        sudo "${OT_CLI}" -I "${INVALID_NETIF_NAME}" -n "${RADIO_URL}" || test $? = 2
168
169        OT_CLI_CMD="$PWD/build/posix/src/posix/ot-cli ${RADIO_URL}"
170    fi
171
172    sudo expect <<EOF | tee "${OT_OUTPUT}" &
173spawn ${OT_CLI_CMD}
174expect_after {
175    timeout { error }
176}
177send "region\r\n"
178expect "US"
179expect "Done"
180send "dataset init new\r\n"
181expect "Done"
182send "dataset commit active\r\n"
183expect "Done"
184send "routerselectionjitter 1\r\n"
185expect "Done"
186send "ifconfig up\r\n"
187expect "Done"
188send "thread start\r\n"
189expect "Done"
190sleep 10
191send "state\r\n"
192expect "leader"
193expect "Done"
194send "extaddr\r\n"
195expect "Done"
196send "dataset active\r\n"
197expect "Done"
198send "ipaddr\r\n"
199expect "Done"
200send "coex\r\n"
201expect "Done"
202send "coap start\r\n"
203expect "Done"
204send "coap resource TestResource\r\n"
205expect "Done"
206send "coap set TestContent\r\n"
207expect "Done"
208set timeout -1
209expect eof
210EOF
211
212    sleep 5
213
214    # wait until the node becomes leader
215    timeout_run 10 wait_for_leader
216
217    # wait coap service start
218    sleep 5
219
220    netstat -an | grep -q 5683 || die 'Application CoAP port is not available!'
221
222    extaddr=$(grep -ao -A +1 'extaddr' $OT_OUTPUT | tail -n1 | tr -d '\r\n\0')
223    echo "Extended address is: ${extaddr}"
224
225    prefix=$(grep -ao 'Mesh Local Prefix: [0-9a-f:]\+' $OT_OUTPUT | cut -d: -f2- | tr -d ' \r\n')
226    LEADER_ALOC="${prefix}ff:fe00:fc00"
227
228    # skip testing CoAP for https://github.com/openthread/openthread/issues/6363
229    [[ $OSTYPE == "linux-gnu"* ]] || return 0
230
231    if [[ ${OT_DAEMON} == 'on' ]]; then
232        sudo killall -9 expect || true
233        sudo killall -9 ot-ctl || true
234        NETIF_INDEX=$(ip link show "${NETIF_NAME}" | cut -f 1 -d ":" | head -n 1)
235        sudo PATH="$(dirname "${OT_CTL_PATH}"):${PATH}" \
236            python3 "$PWD/tests/scripts/misc/test_multicast_join.py" "${NETIF_INDEX}" "${NETIF_NAME}" \
237            || die 'multicast group join failed'
238    fi
239
240    # Retrievie test resource through application CoAP
241    coap_response=$(coap-client -B 5 -m GET "coap://[${LEADER_ALOC}]:5683/TestResource")
242    echo "CoAP response is: ${coap_response}"
243
244    # Verify CoAP response contains the test content
245    if [[ ${coap_response} == *TestContent* ]]; then
246        echo 'Success'
247    else
248        die 'Failed to access application CoAP'
249    fi
250
251    # Retrievie extended address through network diagnostic get
252    coap_response=$(echo -n '120100' | xxd -r -p | coap-client -B 5 -m POST "coap://[${LEADER_ALOC}]:61631/d/dg" -f-)
253
254    # Verify Tmf CoAP is blocked
255    if [[ -z ${coap_response} ]]; then
256        die 'Tmf is not blocked'
257    fi
258}
259
260main()
261{
262    if [[ $# == 0 ]]; then
263        do_build
264        do_check
265        return 0
266    fi
267
268    while [[ $# != 0 ]]; do
269        case $1 in
270            build)
271                do_build
272                ;;
273            check)
274                do_check
275                ;;
276            *)
277                echo "Unknown action: $1"
278                return 1
279                ;;
280        esac
281        shift
282    done
283}
284
285main "$@"
286