You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by cc...@apache.org on 2016/06/23 21:57:41 UTC

incubator-mynewt-core git commit: log2smtest.rb: generates BLE SM unit tests.

Repository: incubator-mynewt-core
Updated Branches:
  refs/heads/develop e942cec79 -> e0c73d247


log2smtest.rb: generates BLE SM unit tests.


Project: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/commit/e0c73d24
Tree: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/tree/e0c73d24
Diff: http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/diff/e0c73d24

Branch: refs/heads/develop
Commit: e0c73d247bb96c7a825894a469b34845b92ced60
Parents: e942cec
Author: Christopher Collins <cc...@apache.org>
Authored: Thu Jun 23 14:56:58 2016 -0700
Committer: Christopher Collins <cc...@apache.org>
Committed: Thu Jun 23 14:56:58 2016 -0700

----------------------------------------------------------------------
 net/nimble/host/tools/log2smtest.rb | 1010 ++++++++++++++++++++++++++++++
 1 file changed, 1010 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-mynewt-core/blob/e0c73d24/net/nimble/host/tools/log2smtest.rb
----------------------------------------------------------------------
diff --git a/net/nimble/host/tools/log2smtest.rb b/net/nimble/host/tools/log2smtest.rb
new file mode 100755
index 0000000..cc7612a
--- /dev/null
+++ b/net/nimble/host/tools/log2smtest.rb
@@ -0,0 +1,1010 @@
+#!/usr/bin/env ruby
+
+### This script converts a bletiny log into a security manager unit test.  The
+### input log must contain the connection establishment and complete pairing
+### procedure.
+###
+### Arguments: None
+### Stdin: bletiny log file
+
+$PAIR_ALG_STRINGS = {
+    0 => [ 'BLE_SM_PAIR_ALG_JW',        'just works',           'jw' ],
+    1 => [ 'BLE_SM_PAIR_ALG_PASSKEY',   'passkey entry',        'pk' ],
+    2 => [ 'BLE_SM_PAIR_ALG_OOB',       'out of band',          'ob' ],
+    3 => [ 'BLE_SM_PAIR_ALG_NUMCMP',    'numeric comparison',   'nc' ]
+}
+
+$ADDR_TYPE_STRINGS = {
+    0 => 'BLE_ADDR_TYPE_PUBLIC',
+    1 => 'BLE_ADDR_TYPE_RANDOM',
+    2 => 'BLE_ADDR_TYPE_RPA_PUB_DEFAULT',
+    3 => 'BLE_ADDR_TYPE_RPA_RND_DEFAULT',
+}
+
+$ACTION_STRINGS = {
+    0 => 'BLE_SM_IOACT_NONE',
+    1 => 'BLE_SM_IOACT_OOB',
+    2 => 'BLE_SM_IOACT_INPUT',
+    3 => 'BLE_SM_IOACT_DISP',
+    4 => 'BLE_SM_IOACT_NUMCMP',
+}
+
+$prev_idx = 0
+$ctxt = {}
+
+def test_case_name
+    type_str = $ctxt[:sc] ? "sc" : "lgcy"
+    init_str = $ctxt[:we_are_init] ? "us" : "peer"
+    alg_str = $PAIR_ALG_STRINGS[$ctxt[:pair_alg]][2]
+    iio_cap_str = "iio#{$ctxt[:pair_req][:io_cap]}"
+    rio_cap_str = "rio#{$ctxt[:pair_rsp][:io_cap]}"
+    bonding_str = "b#{$ctxt[:bonding] ? 1 : 0}"
+    iat_str = "iat#{$ctxt[:addrs][:init_type]}"
+    rat_str = "rat#{$ctxt[:addrs][:resp_type]}"
+    ikey_str = "ik#{$ctxt[:pair_rsp][:init_key_dist]}"
+    rkey_str = "rk#{$ctxt[:pair_rsp][:resp_key_dist]}"
+
+    "ble_sm_" +
+    "#{type_str}_#{init_str}_#{alg_str}_#{iio_cap_str}_#{rio_cap_str}_" +
+    "#{bonding_str}_#{iat_str}_#{rat_str}_#{ikey_str}_#{rkey_str}"
+end
+
+def test_case_comment
+<<-eos
+/**
+ * #{$ctxt[:sc] ? 'Secure connections' : 'Legacy'} pairing
+ * Master: #{$ctxt[:we_are_init] ? "us" : "peer"}
+ * Pair algorithm: #{$PAIR_ALG_STRINGS[$ctxt[:pair_alg]][1]}
+ * Initiator IO capabilities: #{$ctxt[:pair_req][:io_cap]}
+ * Responder IO capabilities: #{$ctxt[:pair_rsp][:io_cap]}
+ * Bonding: #{$ctxt[:bonding]}
+ * Initiator address type: #{$ADDR_TYPE_STRINGS[$ctxt[:addrs][:init_type]]}
+ * Responder address type: #{$ADDR_TYPE_STRINGS[$ctxt[:addrs][:resp_type]]}
+ * Initiator key distribution: #{$ctxt[:pair_rsp][:init_key_dist]}
+ * Responder key distribution: #{$ctxt[:pair_rsp][:resp_key_dist]}
+ */
+eos
+end
+
+def to_hex_s(byte)
+    if byte.is_a?(String)
+        byte = s_to_i(byte)
+    end
+
+    "0x#{byte.to_s(16).rjust(2, '0')}"
+end
+
+# to_i(0) but interpret leading zeros as decimal.
+def s_to_i(s)
+    if s[0..1] == "0x"
+        return s.to_i(16)
+    else
+        return s.to_i(10)
+    end
+end
+
+def invalid_byte_line(msg, line)
+    str = "invalid byte line"
+    if msg != nil
+        str += ": #{msg}"
+    end
+
+    str += "; line=#{line}"
+
+    raise str
+end
+
+def token_string_to_bytes(line, delim = ' ')
+    tokens = line.split(delim)
+    bytes = []
+    tokens.each do |token|
+        begin
+            byte = token.to_i(16)
+            bytes << byte
+        rescue
+            invalid_byte_line("token=#{token}", line)
+        end
+    end
+
+    return bytes
+end
+
+def txrx_prefix(is_tx)
+    if is_tx
+        return "tx"
+    else
+        return "rx"
+    end
+end
+
+def reqrsp_s(is_req)
+    reqrsp = nil
+    if is_req
+        return "req"
+    else
+        return "rsp"
+    end
+end
+
+def bytes_to_arr_body(bytes, indent)
+    lines = []
+
+    idx = 0
+    while idx < bytes.size
+        slice_len = nil
+        if bytes.size - idx >= 8
+            slice_len = 8
+        else
+            slice_len = bytes.size - idx
+        end
+
+        slice = bytes[idx...(idx + slice_len)]
+        line = ' ' * indent +
+            slice.map{|b| to_hex_s(b)}.join(", ") + ","
+        lines << line
+
+        idx += slice_len
+    end
+
+    return lines.join("\n") << "\n"
+end
+
+def bytes_to_arr(bytes, name, indent)
+    str = "#{' ' * indent}.#{name} = {\n"
+    str << bytes_to_arr_body(bytes, indent + 4)
+    str << "#{' ' * indent}},"
+
+    return str
+end
+
+def addr_string_to_bytes(addr_string)
+    token_string_to_bytes(addr_string, ':').reverse
+end
+
+def parse_pair_cmd(line, is_req)
+    suffix = reqrsp_s(is_req)
+    re = %r{
+        pair\s#{suffix};
+        \s
+        conn=\d+
+        \s
+        io_cap=(?<io_cap>\d+)
+        \s
+        oob_data_flag=(?<oob_data_flag>\d+)
+        \s
+        authreq=(?<authreq>0x[0-9a-f]+)
+        \s
+        mac_enc_key_size=(?<max_enc_key_size>\d+)
+        \s
+        init_key_dist=(?<init_key_dist>\d+)
+        \s
+        resp_key_dist=(?<resp_key_dist>\d+)
+    }x
+
+    m = re.match(line)
+    if m == nil
+        return nil
+    end
+
+    cmd = {}
+    cmd[:io_cap] = s_to_i(m[:io_cap])
+    cmd[:oob_data_flag] = s_to_i(m[:oob_data_flag])
+    cmd[:authreq] = s_to_i(m[:authreq])
+    cmd[:max_enc_key_size] = s_to_i(m[:max_enc_key_size])
+    cmd[:init_key_dist] = s_to_i(m[:init_key_dist])
+    cmd[:resp_key_dist] = s_to_i(m[:resp_key_dist])
+
+    return cmd
+end
+
+def parse_privkey(line)
+    if !(line =~ /our privkey=(.+)/)
+        return nil
+    end
+    return token_string_to_bytes($1)
+end
+
+def parse_public_key(line, is_tx)
+    prefix = txrx_prefix(is_tx)
+    if !(line =~ /#{prefix}ed sm command: public key; conn=\d+ x=(.+) y=(.+)/)
+        return nil
+    end
+
+    pubkey = {}
+    pubkey[:x] = token_string_to_bytes($1)
+    pubkey[:y] = token_string_to_bytes($2)
+
+    if pubkey[:x].size != 32
+        raise "invalid public key: x length incorrect; line=#{line}"
+    end
+
+    if pubkey[:y].size != 32
+        raise "invalid public key: y length incorrect; line=#{line}"
+    end
+
+    return pubkey
+end
+
+def parse_confirm(line, is_tx)
+    prefix = txrx_prefix(is_tx)
+    if !(line =~ /#{prefix}ed sm command: confirm; conn=\d+ value=(.+)/)
+        return nil
+    end
+
+    bytes = token_string_to_bytes($1)
+    if bytes.size != 16
+        raise "invalid confirm line (length mismatch): #{line}"
+    end
+
+    return { :value => bytes }
+end
+
+def parse_random(line, is_tx)
+    prefix = txrx_prefix(is_tx)
+    if !(line =~ /#{prefix}ed sm command: random; conn=\d+ value=(.+)/)
+        return nil
+    end
+
+    bytes = token_string_to_bytes($1)
+    if bytes.size != 16
+        raise "invalid random line (length mismatch): #{line}"
+    end
+
+    return { :value => bytes }
+end
+
+def parse_stk(line)
+    if !(line =~ /^    out=(.+)/)
+        return nil
+    end
+
+    bytes = token_string_to_bytes($1)
+    if bytes.size != 16
+        raise "invalid stk line (length mismatch): #{line}"
+    end
+
+    return bytes
+end
+
+def parse_dhkey_check(line, is_tx)
+    prefix = txrx_prefix(is_tx)
+    if !(line =~ /#{prefix}ed sm command: dhkey check; conn=\d+ value=(.+)/)
+        return nil
+    end
+
+    bytes = token_string_to_bytes($1)
+    if bytes.size != 16
+        raise "invalid dhkey_check line (length mismatch): #{line}"
+    end
+
+    return { :value => bytes }
+end
+
+def parse_ltk(line)
+    if !(line =~ /persisting.+ltk=([^ ]+)/)
+        return nil
+    end
+
+    bytes = $1.split(":")
+    if bytes.size != 16
+        raise "invalid ltk line (length mismatch): exp=16 got=#{bytes.size} " +
+              "line=#{line}"
+    end
+
+    return bytes
+end
+
+def parse_enc_info(line, is_tx)
+    prefix = txrx_prefix(is_tx)
+    if !(line =~ /#{prefix}ed sm command: enc info; conn=\d+ ltk=(.+)/)
+        return nil
+    end
+
+    bytes = token_string_to_bytes($1)
+    if bytes.size != 16
+        raise "invalid enc info line (length mismatch): #{line}"
+    end
+
+    return { :ltk => bytes }
+end
+
+def parse_master_id(line, is_tx)
+    prefix = txrx_prefix(is_tx)
+    if !(line =~ /#{prefix}ed sm command: master id; conn=\d+ ediv=(.+) rand=(.+)/)
+        return nil
+    end
+
+    return {
+        :ediv => s_to_i($1),
+        :rand => s_to_i($2),
+    }
+end
+
+def parse_id_info(line, is_tx)
+    prefix = txrx_prefix(is_tx)
+    if !(line =~ /#{prefix}ed sm command: id info; conn=\d+ irk=(.+)/)
+        return nil
+    end
+
+    bytes = token_string_to_bytes($1)
+    if bytes.size != 16
+        raise "invalid id info line (length mismatch): #{line}"
+    end
+
+    return { :irk => bytes }
+end
+
+def parse_id_addr_info(line, is_tx)
+    prefix = txrx_prefix(is_tx)
+    if !(line =~ /#{prefix}ed sm command: id addr info; conn=\d+ addr_type=(\d+) addr=(.+)/)
+        return nil
+    end
+
+    bytes = addr_string_to_bytes($2)
+    if bytes.size != 6
+        raise "invalid id addr info line (length mismatch): #{line}"
+    end
+
+    return {
+        :addr_type => s_to_i($1),
+        :addr => bytes,
+    }
+end
+
+def parse_sign_info(line, is_tx)
+    prefix = txrx_prefix(is_tx)
+    if !(line =~ /#{prefix}ed sm command: sign info; conn=\d+ sig_key=(.+)/)
+        return nil
+    end
+
+    bytes = token_string_to_bytes($1)
+    if bytes.size != 16
+        raise "invalid sign info line (length mismatch): #{line}"
+    end
+
+    return {
+        :sig_key => bytes,
+    }
+end
+
+def parse_passkey_info(line)
+    passkey_info = {}
+
+    case line 
+    when /passkey action event; action=4 numcmp=(\d+)/
+        passkey_info[:action] = 4
+        passkey_info[:numcmp] = $1.to_i(10)
+    when /^b passkey conn=\d+ action=1 oob=(\S+)/
+        passkey_info[:action] = 1
+        passkey_info[:oob] = token_string_to_bytes($1, ':')
+    when /^b passkey conn=\d+ action=2 key=(\d+)/
+        passkey_info[:action] = 2
+        passkey_info[:key] = $1.to_i(10)
+    when /b passkey conn=\d+ action=3 key=(\d+)/
+        passkey_info[:action] = 3
+        passkey_info[:key] = $1.to_i(10)
+    else
+        return nil
+    end
+
+    return passkey_info
+end
+
+def parse_addrs(line)
+    if !(line =~ /our_ota_addr_type=(\d+) our_ota_addr=(\S+) our_id_addr_type=(\d+) our_id_addr=(\S+) peer_ota_addr_type=(\d+) peer_ota_addr=(\S+) peer_id_addr_type=(\d+) peer_id_addr=(\S+)/)
+        return nil
+    end
+
+    our_ota_addr_bytes = addr_string_to_bytes($2)
+    our_id_addr_bytes = addr_string_to_bytes($4)
+    peer_ota_addr_bytes = addr_string_to_bytes($6)
+    peer_id_addr_bytes = addr_string_to_bytes($8)
+
+    if $ctxt[:we_are_init]
+        init_id_bytes = our_id_addr_bytes
+        init_ota_bytes = our_ota_addr_bytes
+        resp_id_bytes = peer_id_addr_bytes
+        resp_ota_bytes = peer_ota_addr_bytes
+        init_addr_type = s_to_i($1)
+        resp_addr_type = s_to_i($5)
+    else
+        init_id_bytes = peer_id_addr_bytes
+        init_ota_bytes = peer_ota_addr_bytes
+        resp_id_bytes = our_id_addr_bytes
+        resp_ota_bytes = our_ota_addr_bytes
+        init_addr_type = s_to_i($5)
+        resp_addr_type = s_to_i($1)
+    end
+
+    if init_id_bytes == init_ota_bytes
+        init_ota_bytes = [0] * 6
+    end
+    if resp_id_bytes == resp_ota_bytes
+        resp_ota_bytes = [0] * 6
+    end
+
+    return {
+        :init_type => init_addr_type,
+        :resp_type => resp_addr_type,
+        :init_id_addr => init_id_bytes,
+        :resp_id_addr => resp_id_bytes,
+        :init_rpa => init_ota_bytes,
+        :resp_rpa => resp_ota_bytes,
+    }
+end
+
+def detect_initiator(lines)
+    lines.each do |line|
+        if line =~ /txed sm command: pair req/
+            $ctxt[:we_are_init] = true
+        elsif line =~ /txed sm command: pair rsp/
+            $ctxt[:we_are_init] = false
+        end
+    end
+
+    if $ctxt[:we_are_init] == nil
+        raise "could not detect which peer is the initiator"
+    end
+end
+
+def pair_cmd_to_s(cmd, is_req)
+    suffix = reqrsp_s(is_req)
+    return <<-eos
+        .pair_#{suffix} = {
+            .io_cap = #{to_hex_s(cmd[:io_cap])},
+            .oob_data_flag = #{to_hex_s(cmd[:oob_data_flag])},
+            .authreq = #{to_hex_s(cmd[:authreq])},
+            .max_enc_key_size = #{to_hex_s(cmd[:max_enc_key_size])},
+            .init_key_dist = #{to_hex_s(cmd[:init_key_dist])},
+            .resp_key_dist = #{to_hex_s(cmd[:resp_key_dist])},
+        },
+    eos
+end
+
+def privkey_to_s(privkey)
+    return bytes_to_arr(privkey, "our_priv_key", 8)
+end
+
+def public_key_to_s(public_key, is_req)
+    suffix = reqrsp_s(is_req)
+    return <<-eos
+        .public_key_#{suffix} = {
+#{bytes_to_arr(public_key[:x], "x", 12)}
+#{bytes_to_arr(public_key[:y], "y", 12)}
+        },
+    eos
+end
+
+def confirm_to_s(confirm, is_req, idx)
+    return <<-eos
+        .confirm_#{reqrsp_s(is_req)}[#{idx}] = {
+#{bytes_to_arr(confirm[:value], "value", 12)}
+        },
+    eos
+end
+
+def random_to_s(random, is_req, idx)
+    return <<-eos
+        .random_#{reqrsp_s(is_req)}[#{idx}] = {
+#{bytes_to_arr(random[:value], "value", 12)}
+        },
+    eos
+end
+
+def ltk_to_s(ltk)
+    return bytes_to_arr(ltk, "ltk", 8)
+end
+
+def stk_to_s(stk)
+    return bytes_to_arr(stk, "stk", 8)
+end
+
+def enc_info_to_s(id_info, is_req)
+    return <<-eos
+        .enc_info_#{reqrsp_s(is_req)} = {
+#{bytes_to_arr(id_info[:ltk], "ltk", 12)}
+        },
+    eos
+end
+
+def master_id_to_s(master_id, is_req)
+    return <<-eos
+        .master_id_#{reqrsp_s(is_req)} = {
+            .ediv = 0x#{master_id[:ediv].to_s(16)},
+            .rand_val = 0x#{master_id[:rand].to_s(16)},
+        },
+    eos
+end
+
+def id_info_to_s(id_info, is_req)
+    return <<-eos
+        .id_info_#{reqrsp_s(is_req)} = {
+#{bytes_to_arr(id_info[:irk], "irk", 12)}
+        },
+    eos
+end
+
+def id_addr_info_to_s(id_addr_info, is_req)
+    return <<-eos
+        .id_addr_info_#{reqrsp_s(is_req)} = {
+            .addr_type = #{id_addr_info[:addr_type]},
+#{bytes_to_arr(id_addr_info[:addr], "bd_addr", 12)}
+        },
+    eos
+end
+
+def sign_info_to_s(sign_info, is_req)
+    return <<-eos
+        .sign_info_#{reqrsp_s(is_req)} = {
+#{bytes_to_arr(sign_info[:sig_key], "sig_key", 12)}
+        },
+    eos
+end
+
+def passkey_info_fill(passkey_info)
+    case passkey_info[:action]
+    # None
+    when 0
+        $ctxt[:pair_alg] = 0
+        $ctxt[:authenticated] = false
+
+    # OOB
+    when 1
+        $ctxt[:pair_alg] = 2
+        $ctxt[:authenticated] = true
+
+    # Input
+    when 2
+        $ctxt[:pair_alg] = 1
+        $ctxt[:authenticated] = true
+
+    # Display
+    when 3
+        $ctxt[:pair_alg] = 1
+        $ctxt[:authenticated] = true
+
+    # Numeric comparison
+    when 4
+        $ctxt[:pair_alg] = 3
+        $ctxt[:authenticated] = true
+
+    else
+        raise "invalid MITM action: #{passkey_info[:action]}"
+    end
+end
+
+def passkey_info_s
+    passkey_info = $ctxt[:passkey_info]
+    action_str = $ACTION_STRINGS[passkey_info[:action]]
+
+    result = <<-eos
+        .pair_alg = #{$ctxt[:pair_alg]},
+        .authenticated = #{$ctxt[:authenticated]},
+        .passkey_info = {
+            .passkey = {
+                .action = #{action_str},
+    eos
+
+    if passkey_info[:key] != nil
+        result << <<-eos
+                .passkey = #{passkey_info[:key].to_i},
+        eos
+    end
+    if passkey_info[:oob] != nil
+        result << <<-eos
+#{bytes_to_arr(passkey_info[:oob], "oob", 16)}
+        eos
+    end
+    if passkey_info[:numcmp] != nil
+        result << <<-eos
+                .numcmp_accept = 1,
+        eos
+    end
+
+    result << <<-eos
+            },
+    eos
+
+    if passkey_info[:numcmp] != nil
+        result << <<-eos
+            .exp_numcmp = #{passkey_info[:numcmp].to_i},
+        eos
+    end
+
+    result << <<-eos
+        },
+    eos
+end
+
+def addrs_to_s(addrs)
+    s = ''
+
+    init_type = addrs[:init_type]
+    resp_type = addrs[:resp_type]
+
+    if init_type != 0
+        s += "        .init_addr_type = #{$ADDR_TYPE_STRINGS[init_type]},\n"
+    end
+    s += bytes_to_arr(addrs[:init_id_addr], "init_id_addr", 8) + "\n"
+    if init_type >= 2
+        s += bytes_to_arr(addrs[:init_rpa], "init_rpa", 8) + "\n"
+    end
+
+    if resp_type != 0
+        s += "        .resp_addr_type = #{$ADDR_TYPE_STRINGS[resp_type]},\n"
+    end
+    s += bytes_to_arr(addrs[:resp_id_addr], "resp_id_addr", 8) + "\n"
+    if resp_type >= 2
+        s += bytes_to_arr(addrs[:resp_rpa], "resp_rpa", 8) + "\n"
+    end
+
+    return s
+end
+
+def dhkey_check_to_s(dhkey_check, is_req)
+    return <<-eos
+        .dhkey_check_#{reqrsp_s(is_req)} = {
+#{bytes_to_arr(dhkey_check[:value], "value", 12)}
+        },
+    eos
+end
+
+def extract_one(lines, ignore_prev = false)
+    if ignore_prev
+        start = 0
+    else
+        start = $prev_idx
+    end
+
+    (start...lines.size).each do |idx|
+        line = lines[idx]
+        result = yield(line)
+        if result != nil
+            if !ignore_prev
+                $prev_idx = idx
+            end
+            return result
+        end
+    end
+
+    return nil
+end
+
+def extract_pair_req(lines)
+    return extract_one(lines) {|line| parse_pair_cmd(line, true)}
+end
+
+def extract_pair_rsp(lines)
+    return extract_one(lines) {|line| parse_pair_cmd(line, false)}
+end
+
+def extract_privkey(lines)
+    return extract_one(lines) {|line| parse_privkey(line)}
+end
+
+def extract_public_key_req(lines)
+    return extract_one(lines) do |line|
+        parse_public_key(line, $ctxt[:we_are_init])
+    end
+end
+
+def extract_public_key_rsp(lines)
+    return extract_one(lines) do |line|
+        parse_public_key(line, !$ctxt[:we_are_init])
+    end
+end
+
+def extract_confirm_req(lines)
+    return extract_one(lines) do |line|
+        parse_confirm(line, $ctxt[:we_are_init])
+    end
+end
+
+def extract_confirm_rsp(lines)
+    return extract_one(lines) do |line|
+        parse_confirm(line, !$ctxt[:we_are_init])
+    end
+end
+
+def extract_random_req(lines)
+    return extract_one(lines) do |line|
+        parse_random(line, $ctxt[:we_are_init])
+    end
+end
+
+def extract_random_rsp(lines)
+    return extract_one(lines) do |line|
+        parse_random(line, !$ctxt[:we_are_init])
+    end
+end
+
+def extract_confirm_random(lines)
+    confirm_reqs = []
+    confirm_rsps = []
+    random_reqs = []
+    random_rsps = []
+
+    idx = 0
+    loop do
+        confirm_req = extract_confirm_req(lines)
+        if confirm_req != nil
+            confirm_reqs << confirm_req
+        end
+
+        confirm_rsp = extract_confirm_rsp(lines)
+        break if confirm_rsp == nil
+        if idx >= 20
+            raise "too many confirm rsps (>20)"
+        end
+        confirm_rsps << confirm_rsp
+
+        random_req = extract_random_req(lines)
+        break if random_req == nil
+        random_reqs << random_req
+
+        random_rsp = extract_random_rsp(lines)
+        break if random_rsp == nil
+        random_rsps << random_rsp
+
+        idx += 1
+    end
+
+    return confirm_reqs, confirm_rsps, random_reqs, random_rsps
+end
+
+def extract_stk(lines)
+    return extract_one(lines, true) do |line|
+        parse_stk(line)
+    end
+end
+
+def extract_dhkey_check_req(lines)
+    return extract_one(lines) do |line|
+        parse_dhkey_check(line, $ctxt[:we_are_init])
+    end
+end
+
+def extract_dhkey_check_rsp(lines)
+    return extract_one(lines) do |line|
+        parse_dhkey_check(line, !$ctxt[:we_are_init])
+    end
+end
+
+def extract_enc_info_req(lines)
+    return extract_one(lines) do |line|
+        parse_enc_info(line, !$ctxt[:we_are_init])
+    end
+end
+
+def extract_enc_info_rsp(lines)
+    return extract_one(lines) do |line|
+        parse_enc_info(line, $ctxt[:we_are_init])
+    end
+end
+
+def extract_master_id_req(lines)
+    return extract_one(lines) do |line|
+        parse_master_id(line, !$ctxt[:we_are_init])
+    end
+end
+
+def extract_master_id_rsp(lines)
+    return extract_one(lines) do |line|
+        parse_master_id(line, $ctxt[:we_are_init])
+    end
+end
+
+def extract_id_info_req(lines)
+    return extract_one(lines) do |line|
+        parse_id_info(line, !$ctxt[:we_are_init])
+    end
+end
+
+def extract_id_info_rsp(lines)
+    return extract_one(lines) do |line|
+        parse_id_info(line, $ctxt[:we_are_init])
+    end
+end
+
+def extract_id_addr_info_req(lines)
+    return extract_one(lines) do |line|
+        parse_id_addr_info(line, !$ctxt[:we_are_init])
+    end
+end
+
+def extract_id_addr_info_rsp(lines)
+    return extract_one(lines) do |line|
+        parse_id_addr_info(line, $ctxt[:we_are_init])
+    end
+end
+
+def extract_sign_info_req(lines)
+    return extract_one(lines) do |line|
+        parse_sign_info(line, !$ctxt[:we_are_init])
+    end
+end
+
+def extract_sign_info_rsp(lines)
+    return extract_one(lines) do |line|
+        parse_sign_info(line, $ctxt[:we_are_init])
+    end
+end
+
+def extract_ltk(lines)
+    return extract_one(lines) do |line|
+        parse_ltk(line)
+    end
+end
+
+def extract_passkey_info(lines)
+    passkey_info = extract_one(lines, true) do |line|
+        parse_passkey_info(line)
+    end
+
+    if passkey_info == nil
+        passkey_info = { :action => 0 }
+    end
+
+    return passkey_info
+end
+
+def extract_addrs(lines)
+    return extract_one(lines) do |line|
+        parse_addrs(line)
+    end
+end
+
+
+lines = STDIN.readlines
+
+detect_initiator(lines)
+$ctxt[:pair_req] = extract_pair_req(lines)
+$ctxt[:pair_rsp] = extract_pair_rsp(lines)
+$ctxt[:privkey] = extract_privkey(lines)
+$ctxt[:public_key_req] = extract_public_key_req(lines)
+$ctxt[:public_key_rsp] = extract_public_key_rsp(lines)
+$ctxt[:confirm_reqs], $ctxt[:confirm_rsps], $ctxt[:random_reqs], $ctxt[:random_rsps] = extract_confirm_random(lines)
+$ctxt[:passkey_info] = extract_passkey_info(lines)
+$ctxt[:dhkey_check_req] = extract_dhkey_check_req(lines)
+$ctxt[:dhkey_check_rsp] = extract_dhkey_check_rsp(lines)
+$ctxt[:enc_info_req] = extract_enc_info_req(lines)
+$ctxt[:master_id_req] = extract_master_id_req(lines)
+$ctxt[:id_info_req] = extract_id_info_req(lines)
+$ctxt[:id_addr_info_req] = extract_id_addr_info_req(lines)
+$ctxt[:sign_info_req] = extract_sign_info_req(lines)
+$ctxt[:enc_info_rsp] = extract_enc_info_rsp(lines)
+$ctxt[:master_id_rsp] = extract_master_id_rsp(lines)
+$ctxt[:id_info_rsp] = extract_id_info_rsp(lines)
+$ctxt[:id_addr_info_rsp] = extract_id_addr_info_rsp(lines)
+$ctxt[:sign_info_rsp] = extract_sign_info_rsp(lines)
+$ctxt[:addrs] = extract_addrs(lines)
+$ctxt[:ltk] = extract_ltk(lines)
+$ctxt[:stk] = extract_stk(lines)
+
+expected_confirm_rsps = nil
+expected_random_reqs = nil
+expected_random_rsps = nil
+if $ctxt[:confirm_reqs].size == 0
+    expected_confirm_rsps = 1
+    expected_random_reqs = 1
+    expected_random_rsps = 1
+else
+    expected_confirm_rsps = $ctxt[:confirm_reqs].size
+    expected_random_reqs = $ctxt[:random_reqs].size
+    expected_random_rsps = $ctxt[:random_rsps].size
+end
+
+if $ctxt[:confirm_rsps].size != expected_confirm_rsps
+    raise "wrong number of confirm responses " +
+          "(exp=#{expected_confirm_rsps}; got=#{$ctxt[:confirm_rsps].size}"
+end
+
+if $ctxt[:random_reqs].size != expected_random_reqs
+    raise "wrong number of random requests " +
+          "(exp=#{expected_random_reqs}; got=#{$ctxt[:random_reqs].size}"
+end
+
+if $ctxt[:random_rsps].size != expected_random_rsps
+    raise "wrong number of random responses " +
+          "(exp=#{expected_random_rsps}; got=#{$ctxt[:random_rsps].size}"
+end
+
+passkey_info_fill($ctxt[:passkey_info])
+
+$ctxt[:sc] = $ctxt[:public_key_req] != nil
+$ctxt[:bonding] = $ctxt[:pair_req][:authreq] & 1 == 1 &&
+                  $ctxt[:pair_rsp][:authreq] & 1 == 1
+
+puts test_case_comment()
+puts <<-eos
+TEST_CASE(#{test_case_name()})
+{
+    struct ble_sm_test_params params;
+
+    params = (struct ble_sm_test_params) {
+eos
+
+puts addrs_to_s($ctxt[:addrs])
+
+puts pair_cmd_to_s($ctxt[:pair_req], true)
+puts pair_cmd_to_s($ctxt[:pair_rsp], false)
+
+if $ctxt[:sc]
+    puts privkey_to_s($ctxt[:privkey])
+    puts public_key_to_s($ctxt[:public_key_req], true)
+    puts public_key_to_s($ctxt[:public_key_req], false)
+end
+
+$ctxt[:confirm_rsps].size.times do |i|
+    confirm_req = $ctxt[:confirm_reqs][i]
+    confirm_rsp = $ctxt[:confirm_rsps][i]
+    random_req = $ctxt[:random_reqs][i]
+    random_rsp = $ctxt[:random_rsps][i]
+
+    if confirm_req != nil
+        puts confirm_to_s(confirm_req, true, i)
+    end
+
+    puts confirm_to_s(confirm_rsp, false, i)
+    puts random_to_s(random_req, true, i)
+    puts random_to_s(random_rsp, false, i)
+end
+
+if $ctxt[:sc]
+    puts dhkey_check_to_s($ctxt[:dhkey_check_req], true)
+    puts dhkey_check_to_s($ctxt[:dhkey_check_rsp], false)
+end
+
+if $ctxt[:enc_info_req] != nil
+    puts enc_info_to_s($ctxt[:enc_info_req], true)
+end
+if $ctxt[:master_id_req] != nil
+    puts master_id_to_s($ctxt[:master_id_req], true)
+end
+if $ctxt[:id_info_req] != nil
+    puts id_info_to_s($ctxt[:id_info_req], true)
+end
+if $ctxt[:id_addr_info_req] != nil
+    puts id_addr_info_to_s($ctxt[:id_addr_info_req], true)
+end
+if $ctxt[:sign_info_req] != nil
+    puts sign_info_to_s($ctxt[:sign_info_req], true)
+end
+if $ctxt[:enc_info_rsp] != nil
+    puts enc_info_to_s($ctxt[:enc_info_rsp], false)
+end
+if $ctxt[:master_id_rsp] != nil
+    puts master_id_to_s($ctxt[:master_id_rsp], false)
+end
+if $ctxt[:id_info_rsp] != nil
+    puts id_info_to_s($ctxt[:id_info_rsp], false)
+end
+if $ctxt[:id_addr_info_rsp] != nil
+    puts id_addr_info_to_s($ctxt[:id_addr_info_rsp], false)
+end
+if $ctxt[:sign_info_rsp] != nil
+    puts sign_info_to_s($ctxt[:sign_info_rsp], false)
+end
+if $ctxt[:sc]
+    puts ltk_to_s($ctxt[:ltk])
+else
+    puts stk_to_s($ctxt[:stk])
+end
+puts passkey_info_s()
+
+puts '    };'
+
+if $ctxt[:sc]
+    if $ctxt[:we_are_init]
+        puts '    ble_sm_test_util_us_sc_good(&params);'
+    else
+        puts '    ble_sm_test_util_peer_sc_good(&params);'
+    end
+else
+    if $ctxt[:we_are_init]
+        puts '    ble_sm_test_util_us_lgcy_good(&params);'
+    else
+        puts '    ble_sm_test_util_peer_lgcy_good(&params);'
+    end
+end
+puts '}'