test.py 34 KB

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