test.py 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008
  1. #!/usr/bin/env python
  2. #
  3. # SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
  4. # SPDX-License-Identifier: Apache-2.0
  5. # Utility for testing the web server. Test cases:
  6. # Assume the device supports 'n' simultaneous open sockets
  7. #
  8. # HTTP Server Tests
  9. #
  10. # 0. Firmware Settings:
  11. # - Create a dormant thread whose sole job is to call httpd_stop() when instructed
  12. # - Measure the following before httpd_start() is called:
  13. # - current free memory
  14. # - current free sockets
  15. # - Measure the same whenever httpd_stop is called
  16. # - Register maximum possible URI handlers: should be successful
  17. # - Register one more URI handler: should fail
  18. # - Deregister on URI handler: should be successful
  19. # - Register on more URI handler: should succeed
  20. # - Register separate handlers for /hello, /hello/type_html. Also
  21. # ensure that /hello/type_html is registered BEFORE /hello. (tests
  22. # that largest matching URI is picked properly)
  23. # - Create URI handler /adder. Make sure it uses a custom free_ctx
  24. # structure to free it up
  25. # 1. Using Standard Python HTTP Client
  26. # - simple GET on /hello (returns Hello World. Ensures that basic
  27. # firmware tests are complete, or returns error)
  28. # - POST on /hello (should fail)
  29. # - PUT on /hello (should fail)
  30. # - simple POST on /echo (returns whatever the POST data)
  31. # - simple PUT on /echo (returns whatever the PUT data)
  32. # - GET on /echo (should fail)
  33. # - simple GET on /hello/type_html (returns Content type as text/html)
  34. # - simple GET on /hello/status_500 (returns HTTP status 500)
  35. # - simple GET on /false_uri (returns HTTP status 404)
  36. # - largest matching URI handler is picked is already verified because
  37. # of /hello and /hello/type_html tests
  38. #
  39. #
  40. # 2. Session Tests
  41. # - Sessions + Pipelining basics:
  42. # - Create max supported sessions
  43. # - On session i,
  44. # - send 3 back-to-back POST requests with data i on /adder
  45. # - read back 3 responses. They should be i, 2i and 3i
  46. # - Tests that
  47. # - pipelining works
  48. # - per-session context is maintained for all supported
  49. # sessions
  50. # - Close all sessions
  51. #
  52. # - Cleanup leftover data: Tests that the web server properly cleans
  53. # up leftover data
  54. # - Create a session
  55. # - POST on /leftover_data with 52 bytes of data (data includes
  56. # \r\n)(the handler only
  57. # reads first 10 bytes and returns them, leaving the rest of the
  58. # bytes unread)
  59. # - GET on /hello (should return 'Hello World')
  60. # - POST on /false_uri with 52 bytes of data (data includes \r\n)
  61. # (should return HTTP 404)
  62. # - GET on /hello (should return 'Hello World')
  63. #
  64. # - Test HTTPd Asynchronous response
  65. # - Create a session
  66. # - GET on /async_data
  67. # - returns 'Hello World!' as a response
  68. # - the handler schedules an async response, which generates a second
  69. # response 'Hello Double World!'
  70. #
  71. # - Spillover test
  72. # - Create max supported sessions with the web server
  73. # - GET /hello on all the sessions (should return Hello World)
  74. # - Create one more session, this should fail
  75. # - GET /hello on all the sessions (should return Hello World)
  76. #
  77. # - Timeout test
  78. # - Create a session and only Send 'GE' on the same (simulates a
  79. # client that left the network halfway through a request)
  80. # - Wait for recv-wait-timeout
  81. # - Server should automatically close the socket
  82. # ############ TODO TESTS #############
  83. # 3. Stress Tests
  84. #
  85. # - httperf
  86. # - Run the following httperf command:
  87. # httperf --server=10.31.130.126 --wsess=8,50,0.5 --rate 8 --burst-length 2
  88. #
  89. # - The above implies that the test suite will open
  90. # - 8 simultaneous connections with the server
  91. # - the rate of opening the sessions will be 8 per sec. So in our
  92. # case, a new connection will be opened every 0.2 seconds for 1 second
  93. # - The burst length 2 indicates that 2 requests will be sent
  94. # simultaneously on the same connection in a single go
  95. # - 0.5 seconds is the time between sending out 2 bursts
  96. # - 50 is the total number of requests that will be sent out
  97. #
  98. # - So in the above example, the test suite will open 8
  99. # connections, each separated by 0.2 seconds. On each connection
  100. # it will send 2 requests in a single burst. The bursts on a
  101. # single connection will be separated by 0.5 seconds. A total of
  102. # 25 bursts (25 x 2 = 50) will be sent out
  103. # 4. Leak Tests
  104. # - Simple Leak test
  105. # - Simple GET on /hello/restart (returns success, stop web server, measures leaks, restarts webserver)
  106. # - Simple GET on /hello/restart_results (returns the leak results)
  107. # - Leak test with open sockets
  108. # - Open 8 sessions
  109. # - Simple GET on /hello/restart (returns success, stop web server,
  110. # measures leaks, restarts webserver)
  111. # - All sockets should get closed
  112. # - Simple GET on /hello/restart_results (returns the leak results)
  113. import argparse
  114. import http.client
  115. import logging
  116. import random
  117. import socket
  118. import string
  119. import sys
  120. import threading
  121. import time
  122. _verbose_ = False
  123. class Session(object):
  124. def __init__(self, addr, port, timeout=15):
  125. self.client = socket.create_connection((addr, int(port)), timeout=timeout)
  126. self.target = addr
  127. self.status = 0
  128. self.encoding = ''
  129. self.content_type = ''
  130. self.content_len = 0
  131. def send_err_check(self, request, data=None):
  132. rval = True
  133. try:
  134. self.client.sendall(request.encode())
  135. if data:
  136. self.client.sendall(data.encode())
  137. except socket.error as err:
  138. self.client.close()
  139. logging.info('Socket Error in send :{}'.format(err))
  140. rval = False
  141. return rval
  142. def send_get(self, path, headers=None):
  143. request = 'GET ' + path + ' HTTP/1.1\r\nHost: ' + self.target
  144. if headers:
  145. for field, value in headers.items():
  146. request += '\r\n' + field + ': ' + value
  147. request += '\r\n\r\n'
  148. return self.send_err_check(request)
  149. def send_put(self, path, data, headers=None):
  150. request = 'PUT ' + path + ' HTTP/1.1\r\nHost: ' + self.target
  151. if headers:
  152. for field, value in headers.items():
  153. request += '\r\n' + field + ': ' + value
  154. request += '\r\nContent-Length: ' + str(len(data)) + '\r\n\r\n'
  155. return self.send_err_check(request, data)
  156. def send_post(self, path, data, headers=None):
  157. request = 'POST ' + path + ' HTTP/1.1\r\nHost: ' + self.target
  158. if headers:
  159. for field, value in headers.items():
  160. request += '\r\n' + field + ': ' + value
  161. request += '\r\nContent-Length: ' + str(len(data)) + '\r\n\r\n'
  162. return self.send_err_check(request, data)
  163. def read_resp_hdrs(self):
  164. try:
  165. state = 'nothing'
  166. resp_read = ''
  167. while True:
  168. char = self.client.recv(1).decode()
  169. if char == '\r' and state == 'nothing':
  170. state = 'first_cr'
  171. elif char == '\n' and state == 'first_cr':
  172. state = 'first_lf'
  173. elif char == '\r' and state == 'first_lf':
  174. state = 'second_cr'
  175. elif char == '\n' and state == 'second_cr':
  176. state = 'second_lf'
  177. else:
  178. state = 'nothing'
  179. resp_read += char
  180. if state == 'second_lf':
  181. break
  182. # Handle first line
  183. line_hdrs = resp_read.splitlines()
  184. line_comp = line_hdrs[0].split()
  185. self.status = line_comp[1]
  186. del line_hdrs[0]
  187. self.encoding = ''
  188. self.content_type = ''
  189. headers = dict()
  190. # Process other headers
  191. for h in range(len(line_hdrs)):
  192. line_comp = line_hdrs[h].split(':')
  193. if line_comp[0] == 'Content-Length':
  194. self.content_len = int(line_comp[1])
  195. if line_comp[0] == 'Content-Type':
  196. self.content_type = line_comp[1].lstrip()
  197. if line_comp[0] == 'Transfer-Encoding':
  198. self.encoding = line_comp[1].lstrip()
  199. if len(line_comp) == 2:
  200. headers[line_comp[0]] = line_comp[1].lstrip()
  201. return headers
  202. except socket.error as err:
  203. self.client.close()
  204. logging.info('Socket Error in recv :{}'.format(err))
  205. return None
  206. def read_resp_data(self):
  207. try:
  208. read_data = ''
  209. if self.encoding != 'chunked':
  210. while len(read_data) != self.content_len:
  211. read_data += self.client.recv(self.content_len).decode()
  212. else:
  213. chunk_data_buf = ''
  214. while (True):
  215. # Read one character into temp buffer
  216. read_ch = self.client.recv(1)
  217. # Check CRLF
  218. if (read_ch == '\r'):
  219. read_ch = self.client.recv(1).decode()
  220. if (read_ch == '\n'):
  221. # If CRLF decode length of chunk
  222. chunk_len = int(chunk_data_buf, 16)
  223. # Keep adding to contents
  224. self.content_len += chunk_len
  225. rem_len = chunk_len
  226. while (rem_len):
  227. new_data = self.client.recv(rem_len)
  228. read_data += new_data
  229. rem_len -= len(new_data)
  230. chunk_data_buf = ''
  231. # Fetch remaining CRLF
  232. if self.client.recv(2) != '\r\n':
  233. # Error in packet
  234. logging.info('Error in chunked data')
  235. return None
  236. if not chunk_len:
  237. # If last chunk
  238. break
  239. continue
  240. chunk_data_buf += '\r'
  241. # If not CRLF continue appending
  242. # character to chunked data buffer
  243. chunk_data_buf += read_ch
  244. return read_data
  245. except socket.error as err:
  246. self.client.close()
  247. logging.info('Socket Error in recv :{}'.format(err))
  248. return None
  249. def close(self):
  250. self.client.close()
  251. def test_val(text, expected, received):
  252. if expected != received:
  253. logging.info(' Fail!')
  254. logging.info(' [reason] {}:'.format(text))
  255. logging.info(' expected: {}'.format(expected))
  256. logging.info(' received: {}'.format(received))
  257. return False
  258. return True
  259. class adder_thread (threading.Thread):
  260. def __init__(self, id, dut, port):
  261. threading.Thread.__init__(self)
  262. self.id = id
  263. self.dut = dut
  264. self.depth = 3
  265. self.session = Session(dut, port)
  266. def run(self):
  267. self.response = []
  268. # Pipeline 3 requests
  269. if (_verbose_):
  270. logging.info(' Thread: Using adder start {}'.format(self.id))
  271. for _ in range(self.depth):
  272. self.session.send_post('/adder', str(self.id))
  273. time.sleep(2)
  274. for _ in range(self.depth):
  275. self.session.read_resp_hdrs()
  276. self.response.append(self.session.read_resp_data())
  277. def adder_result(self):
  278. if len(self.response) != self.depth:
  279. logging.info('Error : missing response packets')
  280. return False
  281. for i in range(len(self.response)):
  282. if not test_val('Thread' + str(self.id) + ' response[' + str(i) + ']',
  283. str(self.id * (i + 1)), str(self.response[i])):
  284. return False
  285. return True
  286. def close(self):
  287. self.session.close()
  288. def get_hello(dut, port):
  289. # GET /hello should return 'Hello World!'
  290. logging.info("[test] GET /hello returns 'Hello World!' =>")
  291. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  292. conn.request('GET', '/hello')
  293. resp = conn.getresponse()
  294. if not test_val('status_code', 200, resp.status):
  295. conn.close()
  296. return False
  297. if not test_val('data', 'Hello World!', resp.read().decode()):
  298. conn.close()
  299. return False
  300. if not test_val('data', 'text/html', resp.getheader('Content-Type')):
  301. conn.close()
  302. return False
  303. logging.info('Success')
  304. conn.close()
  305. return True
  306. def put_hello(dut, port):
  307. # PUT /hello returns 405'
  308. logging.info('[test] PUT /hello returns 405 =>')
  309. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  310. conn.request('PUT', '/hello', 'Hello')
  311. resp = conn.getresponse()
  312. if not test_val('status_code', 405, resp.status):
  313. conn.close()
  314. return False
  315. logging.info('Success')
  316. conn.close()
  317. return True
  318. def post_hello(dut, port):
  319. # POST /hello returns 405'
  320. logging.info('[test] POST /hello returns 405 =>')
  321. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  322. conn.request('POST', '/hello', 'Hello')
  323. resp = conn.getresponse()
  324. if not test_val('status_code', 405, resp.status):
  325. conn.close()
  326. return False
  327. logging.info('Success')
  328. conn.close()
  329. return True
  330. def post_echo(dut, port):
  331. # POST /echo echoes data'
  332. logging.info('[test] POST /echo echoes data =>')
  333. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  334. conn.request('POST', '/echo', 'Hello')
  335. resp = conn.getresponse()
  336. if not test_val('status_code', 200, resp.status):
  337. conn.close()
  338. return False
  339. if not test_val('data', 'Hello', resp.read().decode()):
  340. conn.close()
  341. return False
  342. logging.info('Success')
  343. conn.close()
  344. return True
  345. def put_echo(dut, port):
  346. # PUT /echo echoes data'
  347. logging.info('[test] PUT /echo echoes data =>')
  348. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  349. conn.request('PUT', '/echo', 'Hello')
  350. resp = conn.getresponse()
  351. if not test_val('status_code', 200, resp.status):
  352. conn.close()
  353. return False
  354. if not test_val('data', 'Hello', resp.read().decode()):
  355. conn.close()
  356. return False
  357. logging.info('Success')
  358. conn.close()
  359. return True
  360. def get_echo(dut, port):
  361. # GET /echo returns 404'
  362. logging.info('[test] GET /echo returns 405 =>')
  363. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  364. conn.request('GET', '/echo')
  365. resp = conn.getresponse()
  366. if not test_val('status_code', 405, resp.status):
  367. conn.close()
  368. return False
  369. logging.info('Success')
  370. conn.close()
  371. return True
  372. def get_test_headers(dut, port):
  373. # GET /test_header returns data of Header2'
  374. logging.info('[test] GET /test_header =>')
  375. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  376. custom_header = {'Header1': 'Value1', 'Header3': 'Value3'}
  377. header2_values = ['', ' ', 'Value2', ' Value2', 'Value2 ', ' Value2 ']
  378. for val in header2_values:
  379. custom_header['Header2'] = val
  380. conn.request('GET', '/test_header', headers=custom_header)
  381. resp = conn.getresponse()
  382. if not test_val('status_code', 200, resp.status):
  383. conn.close()
  384. return False
  385. hdr_val_start_idx = val.find('Value2')
  386. if hdr_val_start_idx == -1:
  387. if not test_val('header: Header2', '', resp.read().decode()):
  388. conn.close()
  389. return False
  390. else:
  391. if not test_val('header: Header2', val[hdr_val_start_idx:], resp.read().decode()):
  392. conn.close()
  393. return False
  394. resp.read()
  395. logging.info('Success')
  396. conn.close()
  397. return True
  398. def get_hello_type(dut, port):
  399. # GET /hello/type_html returns text/html as Content-Type'
  400. logging.info('[test] GET /hello/type_html has Content-Type of text/html =>')
  401. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  402. conn.request('GET', '/hello/type_html')
  403. resp = conn.getresponse()
  404. if not test_val('status_code', 200, resp.status):
  405. conn.close()
  406. return False
  407. if not test_val('data', 'Hello World!', resp.read().decode()):
  408. conn.close()
  409. return False
  410. if not test_val('data', 'text/html', resp.getheader('Content-Type')):
  411. conn.close()
  412. return False
  413. logging.info('Success')
  414. conn.close()
  415. return True
  416. def get_hello_status(dut, port):
  417. # GET /hello/status_500 returns status 500'
  418. logging.info('[test] GET /hello/status_500 returns status 500 =>')
  419. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  420. conn.request('GET', '/hello/status_500')
  421. resp = conn.getresponse()
  422. if not test_val('status_code', 500, resp.status):
  423. conn.close()
  424. return False
  425. logging.info('Success')
  426. conn.close()
  427. return True
  428. def get_false_uri(dut, port):
  429. # GET /false_uri returns status 404'
  430. logging.info('[test] GET /false_uri returns status 404 =>')
  431. conn = http.client.HTTPConnection(dut, int(port), timeout=15)
  432. conn.request('GET', '/false_uri')
  433. resp = conn.getresponse()
  434. if not test_val('status_code', 404, resp.status):
  435. conn.close()
  436. return False
  437. logging.info('Success')
  438. conn.close()
  439. return True
  440. def parallel_sessions_adder(dut, port, max_sessions):
  441. # POSTs on /adder in parallel sessions
  442. logging.info('[test] POST {pipelined} on /adder in ' + str(max_sessions) + ' sessions =>')
  443. t = []
  444. # Create all sessions
  445. for i in range(max_sessions):
  446. t.append(adder_thread(i, dut, port))
  447. for i in range(len(t)):
  448. t[i].start()
  449. for i in range(len(t)):
  450. t[i].join()
  451. res = True
  452. for i in range(len(t)):
  453. if not test_val('Thread' + str(i) + ' Failed', t[i].adder_result(), True):
  454. res = False
  455. t[i].close()
  456. if (res):
  457. logging.info('Success')
  458. return res
  459. def async_response_test(dut, port):
  460. # Test that an asynchronous work is executed in the HTTPD's context
  461. # This is tested by reading two responses over the same session
  462. logging.info('[test] Test HTTPD Work Queue (Async response) =>')
  463. s = Session(dut, port)
  464. s.send_get('/async_data')
  465. s.read_resp_hdrs()
  466. if not test_val('First Response', 'Hello World!', s.read_resp_data()):
  467. s.close()
  468. return False
  469. s.read_resp_hdrs()
  470. if not test_val('Second Response', 'Hello Double World!', s.read_resp_data()):
  471. s.close()
  472. return False
  473. s.close()
  474. logging.info('Success')
  475. return True
  476. def leftover_data_test(dut, port):
  477. # Leftover data in POST is purged (valid and invalid URIs)
  478. logging.info('[test] Leftover data in POST is purged (valid and invalid URIs) =>')
  479. s = http.client.HTTPConnection(dut + ':' + port, timeout=15)
  480. s.request('POST', url='/leftover_data', body='abcdefghijklmnopqrstuvwxyz\r\nabcdefghijklmnopqrstuvwxyz')
  481. resp = s.getresponse()
  482. if not test_val('Partial data', 'abcdefghij', resp.read().decode()):
  483. s.close()
  484. return False
  485. s.request('GET', url='/hello')
  486. resp = s.getresponse()
  487. if not test_val('Hello World Data', 'Hello World!', resp.read().decode()):
  488. s.close()
  489. return False
  490. s.request('POST', url='/false_uri', body='abcdefghijklmnopqrstuvwxyz\r\nabcdefghijklmnopqrstuvwxyz')
  491. resp = s.getresponse()
  492. if not test_val('False URI Status', str(404), str(resp.status)):
  493. s.close()
  494. return False
  495. # socket would have been closed by server due to error
  496. s.close()
  497. s = http.client.HTTPConnection(dut + ':' + port, timeout=15)
  498. s.request('GET', url='/hello')
  499. resp = s.getresponse()
  500. if not test_val('Hello World Data', 'Hello World!', resp.read().decode()):
  501. s.close()
  502. return False
  503. s.close()
  504. logging.info('Success')
  505. return True
  506. def spillover_session(dut, port, max_sess):
  507. # Session max_sess_sessions + 1 is rejected
  508. logging.info('[test] Session max_sess_sessions ({}) + 1 is rejected =>'.format(max_sess))
  509. s = []
  510. _verbose_ = True
  511. for i in range(max_sess + 1):
  512. if (_verbose_):
  513. logging.info('Executing {}'.format(i))
  514. try:
  515. a = http.client.HTTPConnection(dut + ':' + port, timeout=15)
  516. a.request('GET', url='/hello')
  517. resp = a.getresponse()
  518. if not test_val('Connection ' + str(i), 'Hello World!', resp.read().decode()):
  519. a.close()
  520. break
  521. s.append(a)
  522. except Exception:
  523. if (_verbose_):
  524. logging.info('Connection {} rejected'.format(i))
  525. a.close()
  526. break
  527. # Close open connections
  528. for a in s:
  529. a.close()
  530. # Check if number of connections is equal to max_sess
  531. logging.info(['Fail','Success'][len(s) == max_sess])
  532. return (len(s) == max_sess)
  533. def recv_timeout_test(dut, port):
  534. logging.info('[test] Timeout occurs if partial packet sent =>')
  535. s = Session(dut, port)
  536. s.client.sendall(b'GE')
  537. s.read_resp_hdrs()
  538. resp = s.read_resp_data()
  539. if not test_val('Request Timeout', 'Server closed this connection', resp):
  540. s.close()
  541. return False
  542. s.close()
  543. logging.info('Success')
  544. return True
  545. def packet_size_limit_test(dut, port, test_size):
  546. logging.info('[test] send size limit test =>')
  547. retry = 5
  548. while (retry):
  549. retry -= 1
  550. logging.info('data size = {}'.format(test_size))
  551. s = http.client.HTTPConnection(dut + ':' + port, timeout=15)
  552. random_data = ''.join(string.printable[random.randint(0,len(string.printable)) - 1] for _ in list(range(test_size)))
  553. path = '/echo'
  554. s.request('POST', url=path, body=random_data)
  555. resp = s.getresponse()
  556. if not test_val('Error', '200', str(resp.status)):
  557. if test_val('Error', '500', str(resp.status)):
  558. logging.info('Data too large to be allocated')
  559. test_size = test_size // 10
  560. else:
  561. logging.info('Unexpected error')
  562. s.close()
  563. logging.info('Retry...')
  564. continue
  565. resp = resp.read().decode()
  566. result = (resp == random_data)
  567. if not result:
  568. test_val('Data size', str(len(random_data)), str(len(resp)))
  569. s.close()
  570. logging.info('Retry...')
  571. continue
  572. s.close()
  573. logging.info('Success')
  574. return True
  575. logging.info('Failed')
  576. return False
  577. def arbitrary_termination_test(dut, port):
  578. logging.info('[test] Arbitrary termination test =>')
  579. cases = [
  580. {
  581. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nCustom: SomeValue\r\n\r\n',
  582. 'code': '200',
  583. 'header': 'SomeValue'
  584. },
  585. {
  586. 'request': 'POST /echo HTTP/1.1\nHost: ' + dut + '\r\nCustom: SomeValue\r\n\r\n',
  587. 'code': '200',
  588. 'header': 'SomeValue'
  589. },
  590. {
  591. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\nCustom: SomeValue\r\n\r\n',
  592. 'code': '200',
  593. 'header': 'SomeValue'
  594. },
  595. {
  596. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nCustom: SomeValue\n\r\n',
  597. 'code': '200',
  598. 'header': 'SomeValue'
  599. },
  600. {
  601. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nCustom: SomeValue\r\n\n',
  602. 'code': '200',
  603. 'header': 'SomeValue'
  604. },
  605. {
  606. 'request': 'POST /echo HTTP/1.1\nHost: ' + dut + '\nCustom: SomeValue\n\n',
  607. 'code': '200',
  608. 'header': 'SomeValue'
  609. },
  610. {
  611. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nContent-Length: 5\n\r\nABCDE',
  612. 'code': '200',
  613. 'body': 'ABCDE'
  614. },
  615. {
  616. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nContent-Length: 5\r\n\nABCDE',
  617. 'code': '200',
  618. 'body': 'ABCDE'
  619. },
  620. {
  621. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nContent-Length: 5\n\nABCDE',
  622. 'code': '200',
  623. 'body': 'ABCDE'
  624. },
  625. {
  626. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nContent-Length: 5\n\n\rABCD',
  627. 'code': '200',
  628. 'body': '\rABCD'
  629. },
  630. {
  631. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\r\nCustom: SomeValue\r\r\n\r\r\n',
  632. 'code': '400'
  633. },
  634. {
  635. 'request': 'POST /echo HTTP/1.1\r\r\nHost: ' + dut + '\r\n\r\n',
  636. 'code': '400'
  637. },
  638. {
  639. 'request': 'POST /echo HTTP/1.1\r\n\rHost: ' + dut + '\r\n\r\n',
  640. 'code': '400'
  641. },
  642. {
  643. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\rCustom: SomeValue\r\n',
  644. 'code': '400'
  645. },
  646. {
  647. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nCustom: Some\rValue\r\n',
  648. 'code': '400'
  649. },
  650. {
  651. 'request': 'POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nCustom- SomeValue\r\n\r\n',
  652. 'code': '400'
  653. }
  654. ]
  655. for case in cases:
  656. s = Session(dut, port)
  657. s.client.sendall((case['request']).encode())
  658. resp_hdrs = s.read_resp_hdrs()
  659. resp_body = s.read_resp_data()
  660. s.close()
  661. if not test_val('Response Code', case['code'], s.status):
  662. return False
  663. if 'header' in case.keys():
  664. resp_hdr_val = None
  665. if 'Custom' in resp_hdrs.keys():
  666. resp_hdr_val = resp_hdrs['Custom']
  667. if not test_val('Response Header', case['header'], resp_hdr_val):
  668. return False
  669. if 'body' in case.keys():
  670. if not test_val('Response Body', case['body'], resp_body):
  671. return False
  672. logging.info('Success')
  673. return True
  674. def code_500_server_error_test(dut, port):
  675. logging.info('[test] 500 Server Error test =>')
  676. s = Session(dut, port)
  677. # Sending a very large content length will cause malloc to fail
  678. content_len = 2**30
  679. s.client.sendall(('POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nContent-Length: ' + str(content_len) + '\r\n\r\nABCD').encode())
  680. s.read_resp_hdrs()
  681. s.read_resp_data()
  682. if not test_val('Server Error', '500', s.status):
  683. s.close()
  684. return False
  685. s.close()
  686. logging.info('Success')
  687. return True
  688. def code_501_method_not_impl(dut, port):
  689. logging.info('[test] 501 Method Not Implemented =>')
  690. s = Session(dut, port)
  691. path = '/hello'
  692. s.client.sendall(('ABC ' + path + ' HTTP/1.1\r\nHost: ' + dut + '\r\n\r\n').encode())
  693. s.read_resp_hdrs()
  694. s.read_resp_data()
  695. # Presently server sends back 400 Bad Request
  696. # if not test_val("Server Error", "501", s.status):
  697. # s.close()
  698. # return False
  699. if not test_val('Server Error', '400', s.status):
  700. s.close()
  701. return False
  702. s.close()
  703. logging.info('Success')
  704. return True
  705. def code_505_version_not_supported(dut, port):
  706. logging.info('[test] 505 Version Not Supported =>')
  707. s = Session(dut, port)
  708. path = '/hello'
  709. s.client.sendall(('GET ' + path + ' HTTP/2.0\r\nHost: ' + dut + '\r\n\r\n').encode())
  710. s.read_resp_hdrs()
  711. s.read_resp_data()
  712. if not test_val('Server Error', '505', s.status):
  713. s.close()
  714. return False
  715. s.close()
  716. logging.info('Success')
  717. return True
  718. def code_400_bad_request(dut, port):
  719. logging.info('[test] 400 Bad Request =>')
  720. s = Session(dut, port)
  721. path = '/hello'
  722. s.client.sendall(('XYZ ' + path + ' HTTP/1.1\r\nHost: ' + dut + '\r\n\r\n').encode())
  723. s.read_resp_hdrs()
  724. s.read_resp_data()
  725. if not test_val('Client Error', '400', s.status):
  726. s.close()
  727. return False
  728. s.close()
  729. logging.info('Success')
  730. return True
  731. def code_404_not_found(dut, port):
  732. logging.info('[test] 404 Not Found =>')
  733. s = Session(dut, port)
  734. path = '/dummy'
  735. s.client.sendall(('GET ' + path + ' HTTP/1.1\r\nHost: ' + dut + '\r\n\r\n').encode())
  736. s.read_resp_hdrs()
  737. s.read_resp_data()
  738. if not test_val('Client Error', '404', s.status):
  739. s.close()
  740. return False
  741. s.close()
  742. logging.info('Success')
  743. return True
  744. def code_405_method_not_allowed(dut, port):
  745. logging.info('[test] 405 Method Not Allowed =>')
  746. s = Session(dut, port)
  747. path = '/hello'
  748. s.client.sendall(('POST ' + path + ' HTTP/1.1\r\nHost: ' + dut + '\r\n\r\n').encode())
  749. s.read_resp_hdrs()
  750. s.read_resp_data()
  751. if not test_val('Client Error', '405', s.status):
  752. s.close()
  753. return False
  754. s.close()
  755. logging.info('Success')
  756. return True
  757. def code_408_req_timeout(dut, port):
  758. logging.info('[test] 408 Request Timeout =>')
  759. s = Session(dut, port)
  760. s.client.sendall(('POST /echo HTTP/1.1\r\nHost: ' + dut + '\r\nContent-Length: 10\r\n\r\nABCD').encode())
  761. s.read_resp_hdrs()
  762. s.read_resp_data()
  763. if not test_val('Client Error', '408', s.status):
  764. s.close()
  765. return False
  766. s.close()
  767. logging.info('Success')
  768. return True
  769. def code_411_length_required(dut, port):
  770. logging.info('[test] 411 Length Required =>')
  771. s = Session(dut, port)
  772. path = '/echo'
  773. s.client.sendall(('POST ' + path + ' HTTP/1.1\r\nHost: ' + dut + '\r\nContent-Type: text/plain\r\nTransfer-Encoding: chunked\r\n\r\n').encode())
  774. s.read_resp_hdrs()
  775. s.read_resp_data()
  776. # Presently server sends back 400 Bad Request
  777. # if not test_val("Client Error", "411", s.status):
  778. # s.close()
  779. # return False
  780. if not test_val('Client Error', '400', s.status):
  781. s.close()
  782. return False
  783. s.close()
  784. logging.info('Success')
  785. return True
  786. def send_getx_uri_len(dut, port, length):
  787. s = Session(dut, port)
  788. method = 'GET '
  789. version = ' HTTP/1.1\r\n'
  790. path = '/' + 'x' * (length - len(method) - len(version) - len('/'))
  791. s.client.sendall(method.encode())
  792. time.sleep(1)
  793. s.client.sendall(path.encode())
  794. time.sleep(1)
  795. s.client.sendall((version + 'Host: ' + dut + '\r\n\r\n').encode())
  796. s.read_resp_hdrs()
  797. s.read_resp_data()
  798. s.close()
  799. return s.status
  800. def code_414_uri_too_long(dut, port, max_uri_len):
  801. logging.info('[test] 414 URI Too Long =>')
  802. status = send_getx_uri_len(dut, port, max_uri_len)
  803. if not test_val('Client Error', '404', status):
  804. return False
  805. status = send_getx_uri_len(dut, port, max_uri_len + 1)
  806. if not test_val('Client Error', '414', status):
  807. return False
  808. logging.info('Success')
  809. return True
  810. def send_postx_hdr_len(dut, port, length):
  811. s = Session(dut, port)
  812. path = '/echo'
  813. host = 'Host: ' + dut
  814. custom_hdr_field = '\r\nCustom: '
  815. custom_hdr_val = 'x' * (length - len(host) - len(custom_hdr_field) - len('\r\n\r\n') + len('0'))
  816. request = ('POST ' + path + ' HTTP/1.1\r\n' + host + custom_hdr_field + custom_hdr_val + '\r\n\r\n').encode()
  817. s.client.sendall(request[:length // 2])
  818. time.sleep(1)
  819. s.client.sendall(request[length // 2:])
  820. hdr = s.read_resp_hdrs()
  821. resp = s.read_resp_data()
  822. s.close()
  823. if hdr and ('Custom' in hdr):
  824. return (hdr['Custom'] == custom_hdr_val), resp
  825. return False, s.status
  826. def code_431_hdr_too_long(dut, port, max_hdr_len):
  827. logging.info('[test] 431 Header Too Long =>')
  828. res, status = send_postx_hdr_len(dut, port, max_hdr_len)
  829. if not res:
  830. return False
  831. res, status = send_postx_hdr_len(dut, port, max_hdr_len + 1)
  832. if not test_val('Client Error', '431', status):
  833. return False
  834. logging.info('Success')
  835. return True
  836. def test_upgrade_not_supported(dut, port):
  837. logging.info('[test] Upgrade Not Supported =>')
  838. s = Session(dut, port)
  839. # path = "/hello"
  840. s.client.sendall(('OPTIONS * HTTP/1.1\r\nHost:' + dut + '\r\nUpgrade: TLS/1.0\r\nConnection: Upgrade\r\n\r\n').encode())
  841. s.read_resp_hdrs()
  842. s.read_resp_data()
  843. if not test_val('Client Error', '400', s.status):
  844. s.close()
  845. return False
  846. s.close()
  847. logging.info('Success')
  848. return True
  849. if __name__ == '__main__':
  850. # Execution begins here...
  851. # Configuration
  852. # Max number of threads/sessions
  853. max_sessions = 7
  854. max_uri_len = 512
  855. max_hdr_len = 512
  856. parser = argparse.ArgumentParser(description='Run HTTPD Test')
  857. parser.add_argument('-4','--ipv4', help='IPv4 address')
  858. parser.add_argument('-6','--ipv6', help='IPv6 address')
  859. parser.add_argument('-p','--port', help='Port')
  860. args = vars(parser.parse_args())
  861. dut4 = args['ipv4']
  862. dut6 = args['ipv6']
  863. port = args['port']
  864. dut = dut4
  865. _verbose_ = True
  866. logging.info('### Basic HTTP Client Tests')
  867. get_hello(dut, port)
  868. post_hello(dut, port)
  869. put_hello(dut, port)
  870. post_echo(dut, port)
  871. get_echo(dut, port)
  872. put_echo(dut, port)
  873. get_hello_type(dut, port)
  874. get_hello_status(dut, port)
  875. get_false_uri(dut, port)
  876. get_test_headers(dut, port)
  877. logging.info('### Error code tests')
  878. code_500_server_error_test(dut, port)
  879. code_501_method_not_impl(dut, port)
  880. code_505_version_not_supported(dut, port)
  881. code_400_bad_request(dut, port)
  882. code_404_not_found(dut, port)
  883. code_405_method_not_allowed(dut, port)
  884. code_408_req_timeout(dut, port)
  885. code_414_uri_too_long(dut, port, max_uri_len)
  886. code_431_hdr_too_long(dut, port, max_hdr_len)
  887. test_upgrade_not_supported(dut, port)
  888. # Not supported yet (Error on chunked request)
  889. # code_411_length_required(dut, port)
  890. logging.info('### Sessions and Context Tests')
  891. parallel_sessions_adder(dut, port, max_sessions)
  892. leftover_data_test(dut, port)
  893. async_response_test(dut, port)
  894. spillover_session(dut, port, max_sessions)
  895. recv_timeout_test(dut, port)
  896. packet_size_limit_test(dut, port, 50 * 1024)
  897. arbitrary_termination_test(dut, port)
  898. get_hello(dut, port)
  899. sys.exit()