|
| 1 | +local t = require('luatest') |
| 2 | +local fiber = require('fiber') |
| 3 | +local json = require('json') |
| 4 | +local g = t.group('basic') |
| 5 | + |
| 6 | +local username = 'guest' |
| 7 | +local password = '' |
| 8 | + |
| 9 | +---@type luatest.server |
| 10 | +local server = t.Server:new({ |
| 11 | + alias = 'server', |
| 12 | + box_cfg = { |
| 13 | + listen = '127.0.0.1:3301', |
| 14 | + }, |
| 15 | + net_box_port = 3301, |
| 16 | + net_box_uri = '127.0.0.1:3301', |
| 17 | +}) |
| 18 | + |
| 19 | +g.before_all(function() |
| 20 | + server:start({wait_until_ready = true}) |
| 21 | +end) |
| 22 | + |
| 23 | +g.after_all(function() |
| 24 | + server:stop() |
| 25 | +end) |
| 26 | + |
| 27 | +local connection = require 'connection' |
| 28 | + |
| 29 | +function g.test_connect() |
| 30 | + ---@type connection |
| 31 | + local cnn = connection:new(server.net_box.host, server.net_box.port) |
| 32 | + cnn.connwait:get(1) |
| 33 | + t.assert_equals(cnn.state, connection.S2S.CONNECTED, 'connection has been established') |
| 34 | + cnn:close() |
| 35 | + t.assert_equals(cnn.state, connection.S2S.NOTCONNECTED, 'connection has been closed') |
| 36 | +end |
| 37 | + |
| 38 | +---@class connection.greeter: connection |
| 39 | +local greeter = require 'obj'.class({}, 'connection.greeter', connection) |
| 40 | + |
| 41 | +function greeter:on_connect_io() |
| 42 | + self:super(greeter, 'on_connect_io')() |
| 43 | + self.stage = 'greeting' |
| 44 | +end |
| 45 | + |
| 46 | +function greeter:on_greeting_read() |
| 47 | + local avail = self.avail |
| 48 | + local greeting_size = 128 |
| 49 | + if avail < greeting_size then return end |
| 50 | + |
| 51 | + local ffi = require 'ffi' |
| 52 | + local str = ffi.string(self.rbuf, greeting_size) |
| 53 | + self.avail = avail - greeting_size -- consume the greeting |
| 54 | + |
| 55 | + local _, salt_b64 = unpack(string.split(str, '\n')) |
| 56 | + local digest = require 'digest' |
| 57 | + |
| 58 | + local salt = digest.base64_decode(salt_b64):sub(1, 20) |
| 59 | + local step1 = digest.sha1(password) |
| 60 | + local step2 = digest.sha1(step1) |
| 61 | + local step3 = digest.sha1(salt .. step2) |
| 62 | + |
| 63 | + local function xor(s1, s2, n) |
| 64 | + local r = table.new(n, 0) |
| 65 | + for i = 1, n do |
| 66 | + r[i] = string.char(bit.bxor(s1:byte(i), s2:byte(i))) |
| 67 | + end |
| 68 | + return table.concat(r, '') |
| 69 | + end |
| 70 | + |
| 71 | + local scramble = xor(step1, step3, #salt) |
| 72 | + |
| 73 | + -- now construct auth packet |
| 74 | + local msgpack = require('msgpack') |
| 75 | + local key = { |
| 76 | + REQUEST_TYPE = 0x00, |
| 77 | + SYNC = 0x01, |
| 78 | + TUPLE = 0x21, |
| 79 | + USER_NAME = 0x23, |
| 80 | + } |
| 81 | + local val = { |
| 82 | + AUTH = 0x07, |
| 83 | + } |
| 84 | + local hdr = { |
| 85 | + [key.REQUEST_TYPE] = val.AUTH, |
| 86 | + [key.SYNC] = 0x01, |
| 87 | + } |
| 88 | + local bdy = { |
| 89 | + [key.USER_NAME] = username, |
| 90 | + [key.TUPLE] = {'chap-sha1', scramble}, |
| 91 | + } |
| 92 | + local buf = msgpack.encode(hdr) .. msgpack.encode(bdy) |
| 93 | + local size = msgpack.encode(#buf) |
| 94 | + local pkt = table.concat({ |
| 95 | + string.char(0xce), |
| 96 | + -- prepend \0-bytes to buffer |
| 97 | + ("\x00\x00\x00\x00"):sub(1, 4 - #size) .. size, |
| 98 | + buf |
| 99 | + },'') |
| 100 | + |
| 101 | + self:push_write(pkt) |
| 102 | + self.stage = 'fetching_schema' |
| 103 | + self:flush() |
| 104 | +end |
| 105 | + |
| 106 | +function greeter:on_fetching_schema_read(is_last) |
| 107 | + local msgpack = require('msgpack') |
| 108 | + |
| 109 | + local ptr = self.rbuf |
| 110 | + local avail = tonumber(self.avail) |
| 111 | + local tail = ptr + avail |
| 112 | + |
| 113 | + local sz |
| 114 | + sz, ptr = msgpack.decode(ptr, tonumber(tail-ptr)) |
| 115 | + if sz == nil then |
| 116 | + -- not enough data |
| 117 | + return |
| 118 | + end |
| 119 | + self:log('D', 'size:%s', sz) |
| 120 | + |
| 121 | + if avail < sz then |
| 122 | + -- not enough data |
| 123 | + self:log('D', 'not enough data, need %s, have %s', sz, avail) |
| 124 | + return |
| 125 | + end |
| 126 | + |
| 127 | + local hdr |
| 128 | + hdr, ptr = msgpack.decode(ptr, tonumber(tail-ptr)) |
| 129 | + if hdr == nil then |
| 130 | + -- not enough data |
| 131 | + return |
| 132 | + end |
| 133 | + self:log('D', 'hdr:%s', json.encode(hdr)) |
| 134 | + |
| 135 | + local bdy |
| 136 | + bdy, ptr = msgpack.decode(ptr, tonumber(tail-ptr)) |
| 137 | + if bdy == nil then |
| 138 | + -- not enough data |
| 139 | + return |
| 140 | + end |
| 141 | + self:log('D', 'bdy:%s', json.encode(bdy)) |
| 142 | + self.avail = tail - ptr |
| 143 | + |
| 144 | + self.on_schema:put({ |
| 145 | + header = hdr, |
| 146 | + body = bdy, |
| 147 | + }) |
| 148 | +end |
| 149 | + |
| 150 | +---Tarantool greeter |
| 151 | +function greeter:on_read(is_last) |
| 152 | + if self.stage == 'greeting' then |
| 153 | + return self:on_greeting_read(is_last) |
| 154 | + elseif self.stage == 'fetching_schema' then |
| 155 | + return self:on_fetching_schema_read(is_last) |
| 156 | + else |
| 157 | + self:log('E', 'unknown stage %s', self.stage) |
| 158 | + self.avail = 0 |
| 159 | + end |
| 160 | +end |
| 161 | + |
| 162 | +function g.test_greeting() |
| 163 | + local cnn = greeter:new(server.net_box.host, server.net_box.port) |
| 164 | + cnn.on_schema = fiber.channel() |
| 165 | + cnn.connwait:get(1) |
| 166 | + t.assert_equals(cnn.state, connection.S2S.CONNECTED, 'connection has been established') |
| 167 | + |
| 168 | + local packet = cnn.on_schema:get(5) |
| 169 | + t.assert(packet, "packet with schema must be received") |
| 170 | + |
| 171 | + local schema_version = server:exec(function() |
| 172 | + return box.info.schema_version or box.internal.schema_version() |
| 173 | + end) |
| 174 | + |
| 175 | + t.assert_items_equals(packet.header, { |
| 176 | + [0x00] = 0x00, -- REQUEST_TYPE:OK |
| 177 | + [0x01] = 0x01, -- SYNC:1 |
| 178 | + [0x05] = schema_version, -- SCHEMA_ID:83 |
| 179 | + }, "packet.header is okay") |
| 180 | + |
| 181 | + t.assert_items_equals(packet.body, {}, "packet body is empty") |
| 182 | + t.assert_equals(cnn.state, connection.S2S.CONNECTED, 'connection has been established') |
| 183 | +end |
0 commit comments