| 1 |
;;; Copyright (C) 2008 David Lichteblau |
|---|
| 2 |
;;; See LICENSE for details. |
|---|
| 3 |
|
|---|
| 4 |
#| |
|---|
| 5 |
(load "test.lisp") |
|---|
| 6 |
|# |
|---|
| 7 |
|
|---|
| 8 |
(defpackage :ssl-test |
|---|
| 9 |
(:use :cl)) |
|---|
| 10 |
(in-package :ssl-test) |
|---|
| 11 |
|
|---|
| 12 |
(defvar *port* 8080) |
|---|
| 13 |
(defvar *cert* "/home/david/newcert.pem") |
|---|
| 14 |
(defvar *key* "/home/david/newkey.pem") |
|---|
| 15 |
|
|---|
| 16 |
(eval-when (:compile-toplevel :load-toplevel :execute) |
|---|
| 17 |
(asdf:operate 'asdf:load-op :trivial-sockets) |
|---|
| 18 |
(asdf:operate 'asdf:load-op :bordeaux-threads)) |
|---|
| 19 |
|
|---|
| 20 |
(defparameter *tests* '()) |
|---|
| 21 |
|
|---|
| 22 |
(defvar *sockets* '()) |
|---|
| 23 |
(defvar *sockets-lock* (bordeaux-threads:make-lock)) |
|---|
| 24 |
|
|---|
| 25 |
(defun record-socket (socket) |
|---|
| 26 |
(unless (integerp socket) |
|---|
| 27 |
(bordeaux-threads:with-lock-held (*sockets-lock*) |
|---|
| 28 |
(push socket *sockets*))) |
|---|
| 29 |
socket) |
|---|
| 30 |
|
|---|
| 31 |
(defun close-socket (socket &key abort) |
|---|
| 32 |
(if (streamp socket) |
|---|
| 33 |
(close socket :abort abort) |
|---|
| 34 |
(trivial-sockets:close-server socket))) |
|---|
| 35 |
|
|---|
| 36 |
(defun check-sockets () |
|---|
| 37 |
(let ((failures nil)) |
|---|
| 38 |
(bordeaux-threads:with-lock-held (*sockets-lock*) |
|---|
| 39 |
(dolist (socket *sockets*) |
|---|
| 40 |
(when (close-socket socket :abort t) |
|---|
| 41 |
(push socket failures))) |
|---|
| 42 |
(setf *sockets* nil)) |
|---|
| 43 |
#-sbcl ;fixme |
|---|
| 44 |
(when failures |
|---|
| 45 |
(error "failed to close sockets properly:~{ ~A~%~}" failures)))) |
|---|
| 46 |
|
|---|
| 47 |
(defmacro deftest (name &body body) |
|---|
| 48 |
`(progn |
|---|
| 49 |
(defun ,name () |
|---|
| 50 |
(format t "~%----- ~A ----------------------------~%" ',name) |
|---|
| 51 |
(handler-case |
|---|
| 52 |
(progn |
|---|
| 53 |
,@body |
|---|
| 54 |
(check-sockets) |
|---|
| 55 |
(format t "===== [OK] ~A ====================~%" ',name) |
|---|
| 56 |
t) |
|---|
| 57 |
(error (c) |
|---|
| 58 |
(when (typep c 'trivial-sockets:socket-error) |
|---|
| 59 |
(setf c (trivial-sockets:socket-nested-error c))) |
|---|
| 60 |
(format t "~%===== [FAIL] ~A: ~A~%" ',name c) |
|---|
| 61 |
(handler-case |
|---|
| 62 |
(check-sockets) |
|---|
| 63 |
(error (c) |
|---|
| 64 |
(format t "muffling follow-up error ~A~%" c))) |
|---|
| 65 |
nil))) |
|---|
| 66 |
(push ',name *tests*))) |
|---|
| 67 |
|
|---|
| 68 |
(defun run-all-tests () |
|---|
| 69 |
(unless (probe-file *cert*) (error "~A not found" *cert*)) |
|---|
| 70 |
(unless (probe-file *key*) (error "~A not found" *key*)) |
|---|
| 71 |
(let ((n 0) |
|---|
| 72 |
(nok 0)) |
|---|
| 73 |
(dolist (test (reverse *tests*)) |
|---|
| 74 |
(when (funcall test) |
|---|
| 75 |
(incf nok)) |
|---|
| 76 |
(incf n)) |
|---|
| 77 |
(format t "~&passed ~D/~D tests~%" nok n))) |
|---|
| 78 |
|
|---|
| 79 |
(define-condition quit (condition) |
|---|
| 80 |
()) |
|---|
| 81 |
|
|---|
| 82 |
(defparameter *please-quit* t) |
|---|
| 83 |
|
|---|
| 84 |
(defun make-test-thread (name init main &rest args) |
|---|
| 85 |
"Start a thread named NAME, wait until it has funcalled INIT with ARGS |
|---|
| 86 |
as arguments, then continue while the thread concurrently funcalls MAIN |
|---|
| 87 |
with INIT's return values as arguments." |
|---|
| 88 |
(let ((cv (bordeaux-threads:make-condition-variable)) |
|---|
| 89 |
(lock (bordeaux-threads:make-lock name)) |
|---|
| 90 |
;; redirect io manually, because swan's global redirection isn't as |
|---|
| 91 |
;; global as one might hope |
|---|
| 92 |
(out *terminal-io*) |
|---|
| 93 |
(init-ok nil)) |
|---|
| 94 |
(bordeaux-threads:with-lock-held (lock) |
|---|
| 95 |
(setf *please-quit* nil) |
|---|
| 96 |
(prog1 |
|---|
| 97 |
(bordeaux-threads:make-thread |
|---|
| 98 |
(lambda () |
|---|
| 99 |
(flet ((notify () |
|---|
| 100 |
(bordeaux-threads:with-lock-held (lock) |
|---|
| 101 |
(bordeaux-threads:condition-notify cv)))) |
|---|
| 102 |
(let ((*terminal-io* out) |
|---|
| 103 |
(*standard-output* out) |
|---|
| 104 |
(*trace-output* out) |
|---|
| 105 |
(*error-output* out)) |
|---|
| 106 |
(handler-case |
|---|
| 107 |
(let ((values (multiple-value-list (apply init args)))) |
|---|
| 108 |
(setf init-ok t) |
|---|
| 109 |
(notify) |
|---|
| 110 |
(apply main values)) |
|---|
| 111 |
(quit () |
|---|
| 112 |
(notify) |
|---|
| 113 |
t) |
|---|
| 114 |
(error (c) |
|---|
| 115 |
(when (typep c 'trivial-sockets:socket-error) |
|---|
| 116 |
(setf c (trivial-sockets:socket-nested-error c))) |
|---|
| 117 |
(format t "aborting test thread ~A: ~A" name c) |
|---|
| 118 |
(notify) |
|---|
| 119 |
nil))))) |
|---|
| 120 |
:name name) |
|---|
| 121 |
(bordeaux-threads:condition-wait cv lock) |
|---|
| 122 |
(unless init-ok |
|---|
| 123 |
(error "failed to start background thread")))))) |
|---|
| 124 |
|
|---|
| 125 |
(defmacro with-thread ((name init main &rest args) &body body) |
|---|
| 126 |
`(invoke-with-thread (lambda () ,@body) |
|---|
| 127 |
,name |
|---|
| 128 |
,init |
|---|
| 129 |
,main |
|---|
| 130 |
,@args)) |
|---|
| 131 |
|
|---|
| 132 |
(defun invoke-with-thread (body name init main &rest args) |
|---|
| 133 |
(let ((thread (apply #'make-test-thread name init main args))) |
|---|
| 134 |
(unwind-protect |
|---|
| 135 |
(funcall body) |
|---|
| 136 |
(setf *please-quit* t) |
|---|
| 137 |
(loop |
|---|
| 138 |
for delay = 0.0001 then (* delay 2) |
|---|
| 139 |
while (and (< delay 0.5) (bordeaux-threads:thread-alive-p thread)) |
|---|
| 140 |
do |
|---|
| 141 |
(sleep delay)) |
|---|
| 142 |
(when (bordeaux-threads:thread-alive-p thread) |
|---|
| 143 |
(format t "~&thread doesn't want to quit, killing it~%") |
|---|
| 144 |
(force-output) |
|---|
| 145 |
(bordeaux-threads:interrupt-thread thread (lambda () (error 'quit))) |
|---|
| 146 |
(loop |
|---|
| 147 |
for delay = 0.0001 then (* delay 2) |
|---|
| 148 |
while (bordeaux-threads:thread-alive-p thread) |
|---|
| 149 |
do |
|---|
| 150 |
(sleep delay)))))) |
|---|
| 151 |
|
|---|
| 152 |
(defun init-server (&key (unwrap-stream-p t)) |
|---|
| 153 |
(format t "~&SSL server listening on port ~d~%" *port*) |
|---|
| 154 |
(values (record-socket (trivial-sockets:open-server :port *port*)) |
|---|
| 155 |
unwrap-stream-p)) |
|---|
| 156 |
|
|---|
| 157 |
(defun test-server (listening-socket unwrap-stream-p) |
|---|
| 158 |
(format t "~&SSL server accepting...~%") |
|---|
| 159 |
(unwind-protect |
|---|
| 160 |
(let* ((socket (record-socket |
|---|
| 161 |
(trivial-sockets:accept-connection |
|---|
| 162 |
listening-socket |
|---|
| 163 |
:element-type '(unsigned-byte 8)))) |
|---|
| 164 |
(callback nil)) |
|---|
| 165 |
(when (eq unwrap-stream-p :caller) |
|---|
| 166 |
(setf callback (let ((s socket)) (lambda () (close-socket s)))) |
|---|
| 167 |
(setf socket (cl+ssl:stream-fd socket)) |
|---|
| 168 |
(setf unwrap-stream-p nil)) |
|---|
| 169 |
(let ((client (record-socket |
|---|
| 170 |
(cl+ssl:make-ssl-server-stream |
|---|
| 171 |
socket |
|---|
| 172 |
:unwrap-stream-p unwrap-stream-p |
|---|
| 173 |
:close-callback callback |
|---|
| 174 |
:external-format :iso-8859-1 |
|---|
| 175 |
:certificate *cert* |
|---|
| 176 |
:key *key*)))) |
|---|
| 177 |
(unwind-protect |
|---|
| 178 |
(loop |
|---|
| 179 |
for line = (prog2 |
|---|
| 180 |
(when *please-quit* (return)) |
|---|
| 181 |
(read-line client nil) |
|---|
| 182 |
(when *please-quit* (return))) |
|---|
| 183 |
while line |
|---|
| 184 |
do |
|---|
| 185 |
(cond |
|---|
| 186 |
((equal line "freeze") |
|---|
| 187 |
(format t "~&Freezing on client request~%") |
|---|
| 188 |
(loop |
|---|
| 189 |
(sleep 1) |
|---|
| 190 |
(when *please-quit* (return)))) |
|---|
| 191 |
(t |
|---|
| 192 |
(format t "~&Responding to query ~A...~%" line) |
|---|
| 193 |
(format client "(echo ~A)~%" line) |
|---|
| 194 |
(force-output client)))) |
|---|
| 195 |
(close-socket client)))) |
|---|
| 196 |
(close-socket listening-socket))) |
|---|
| 197 |
|
|---|
| 198 |
(defun init-client (&key (unwrap-stream-p t)) |
|---|
| 199 |
(let ((socket (record-socket |
|---|
| 200 |
(trivial-sockets:open-stream |
|---|
| 201 |
"127.0.0.1" |
|---|
| 202 |
*port* |
|---|
| 203 |
:element-type '(unsigned-byte 8)))) |
|---|
| 204 |
(callback nil)) |
|---|
| 205 |
(when (eq unwrap-stream-p :caller) |
|---|
| 206 |
(setf callback (let ((s socket)) (lambda () (close-socket s)))) |
|---|
| 207 |
(setf socket (cl+ssl:stream-fd socket)) |
|---|
| 208 |
(setf unwrap-stream-p nil)) |
|---|
| 209 |
(cl+ssl:make-ssl-client-stream |
|---|
| 210 |
socket |
|---|
| 211 |
:unwrap-stream-p unwrap-stream-p |
|---|
| 212 |
:close-callback callback |
|---|
| 213 |
:external-format :iso-8859-1))) |
|---|
| 214 |
|
|---|
| 215 |
;;; Simple echo-server test. Write a line and check that the result |
|---|
| 216 |
;;; watches, three times in a row. |
|---|
| 217 |
(deftest echo |
|---|
| 218 |
(with-thread ("simple server" #'init-server #'test-server) |
|---|
| 219 |
(with-open-stream (socket (init-client)) |
|---|
| 220 |
(write-line "test" socket) |
|---|
| 221 |
(force-output socket) |
|---|
| 222 |
(assert (equal (read-line socket) "(echo test)")) |
|---|
| 223 |
(write-line "test2" socket) |
|---|
| 224 |
(force-output socket) |
|---|
| 225 |
(assert (equal (read-line socket) "(echo test2)")) |
|---|
| 226 |
(write-line "test3" socket) |
|---|
| 227 |
(force-output socket) |
|---|
| 228 |
(assert (equal (read-line socket) "(echo test3)"))))) |
|---|
| 229 |
|
|---|
| 230 |
;;; Run tests with different BIO setup strategies: |
|---|
| 231 |
;;; - :UNWRAP-STREAMS T |
|---|
| 232 |
;;; In this case, CL+SSL will convert the socket to a file descriptor. |
|---|
| 233 |
;;; - :UNWRAP-STREAMS :CLIENT |
|---|
| 234 |
;;; Convert the socket to a file descriptor manually, and give that |
|---|
| 235 |
;;; to CL+SSL. |
|---|
| 236 |
;;; - :UNWRAP-STREAMS NIL |
|---|
| 237 |
;;; Let CL+SSL write to the stream directly, using the Lisp BIO. |
|---|
| 238 |
(macrolet ((deftests (name (var &rest values) &body body) |
|---|
| 239 |
`(progn |
|---|
| 240 |
,@(loop |
|---|
| 241 |
for value in values |
|---|
| 242 |
collect |
|---|
| 243 |
`(deftest ,(intern (format nil "~A-~A" name value)) |
|---|
| 244 |
(let ((,var ',value)) |
|---|
| 245 |
,@body)))))) |
|---|
| 246 |
|
|---|
| 247 |
(deftests unwrap-strategy (usp nil t :caller) |
|---|
| 248 |
(with-thread ("echo server for strategy test" |
|---|
| 249 |
(lambda () (init-server :unwrap-stream-p usp)) |
|---|
| 250 |
#'test-server) |
|---|
| 251 |
(with-open-stream (socket (init-client :unwrap-stream-p usp)) |
|---|
| 252 |
(write-line "test" socket) |
|---|
| 253 |
(force-output socket) |
|---|
| 254 |
(assert (equal (read-line socket) "(echo test)"))))) |
|---|
| 255 |
|
|---|
| 256 |
#+clozure-common-lisp |
|---|
| 257 |
(deftests read-deadline (usp nil t) |
|---|
| 258 |
(with-thread ("echo server for deadline test" |
|---|
| 259 |
(lambda () (init-server :unwrap-stream-p usp)) |
|---|
| 260 |
#'test-server) |
|---|
| 261 |
(let* ((deadline |
|---|
| 262 |
(+ (get-internal-real-time) |
|---|
| 263 |
(* 3 internal-time-units-per-second))) |
|---|
| 264 |
(low |
|---|
| 265 |
(record-socket |
|---|
| 266 |
(ccl:make-socket |
|---|
| 267 |
:address-family :internet |
|---|
| 268 |
:connect :active |
|---|
| 269 |
:type :stream |
|---|
| 270 |
:remote-host "127.0.0.1" |
|---|
| 271 |
:remote-port *port* |
|---|
| 272 |
:deadline deadline)))) |
|---|
| 273 |
(with-open-stream |
|---|
| 274 |
(socket |
|---|
| 275 |
(cl+ssl:make-ssl-client-stream |
|---|
| 276 |
low |
|---|
| 277 |
:unwrap-stream-p usp |
|---|
| 278 |
:external-format :iso-8859-1)) |
|---|
| 279 |
(write-line "test" socket) |
|---|
| 280 |
(force-output socket) |
|---|
| 281 |
(assert (equal (read-line socket) "(echo test)")) |
|---|
| 282 |
(handler-case |
|---|
| 283 |
(progn |
|---|
| 284 |
(read-char socket) |
|---|
| 285 |
(error "unexpected data")) |
|---|
| 286 |
(ccl::communication-deadline-expired ())))))) |
|---|
| 287 |
|
|---|
| 288 |
#+sbcl |
|---|
| 289 |
(deftests read-deadline (usp nil t :caller) |
|---|
| 290 |
(with-thread ("echo server for deadline test" |
|---|
| 291 |
(lambda () (init-server :unwrap-stream-p usp)) |
|---|
| 292 |
#'test-server) |
|---|
| 293 |
(sb-sys:with-deadline (:seconds 3) |
|---|
| 294 |
(with-open-stream (socket (init-client :unwrap-stream-p usp)) |
|---|
| 295 |
(write-line "test" socket) |
|---|
| 296 |
(force-output socket) |
|---|
| 297 |
(assert (equal (read-line socket) "(echo test)")) |
|---|
| 298 |
(handler-case |
|---|
| 299 |
(progn |
|---|
| 300 |
(read-char socket) |
|---|
| 301 |
(error "unexpected data")) |
|---|
| 302 |
(sb-sys:deadline-timeout ())))))) |
|---|
| 303 |
|
|---|
| 304 |
#+clozure-common-lisp |
|---|
| 305 |
(deftests write-deadline (usp nil t) |
|---|
| 306 |
(with-thread ("echo server for deadline test" |
|---|
| 307 |
(lambda () (init-server :unwrap-stream-p usp)) |
|---|
| 308 |
#'test-server) |
|---|
| 309 |
(let* ((deadline |
|---|
| 310 |
(+ (get-internal-real-time) |
|---|
| 311 |
(* 3 internal-time-units-per-second))) |
|---|
| 312 |
(low |
|---|
| 313 |
(record-socket |
|---|
| 314 |
(ccl:make-socket |
|---|
| 315 |
:address-family :internet |
|---|
| 316 |
:connect :active |
|---|
| 317 |
:type :stream |
|---|
| 318 |
:remote-host "127.0.0.1" |
|---|
| 319 |
:remote-port *port* |
|---|
| 320 |
:deadline deadline))) |
|---|
| 321 |
(socket |
|---|
| 322 |
(cl+ssl:make-ssl-client-stream |
|---|
| 323 |
low |
|---|
| 324 |
:unwrap-stream-p usp |
|---|
| 325 |
:external-format :iso-8859-1))) |
|---|
| 326 |
(unwind-protect |
|---|
| 327 |
(progn |
|---|
| 328 |
(write-line "test" socket) |
|---|
| 329 |
(force-output socket) |
|---|
| 330 |
(assert (equal (read-line socket) "(echo test)")) |
|---|
| 331 |
(write-line "freeze" socket) |
|---|
| 332 |
(force-output socket) |
|---|
| 333 |
(let ((n 0)) |
|---|
| 334 |
(handler-case |
|---|
| 335 |
(loop |
|---|
| 336 |
(write-line "deadbeef" socket) |
|---|
| 337 |
(incf n)) |
|---|
| 338 |
(ccl::communication-deadline-expired ())) |
|---|
| 339 |
;; should have written a couple of lines before the deadline: |
|---|
| 340 |
(assert (> n 100)))) |
|---|
| 341 |
(handler-case |
|---|
| 342 |
(close-socket socket :abort t) |
|---|
| 343 |
(ccl::communication-deadline-expired ())))))) |
|---|
| 344 |
|
|---|
| 345 |
#+sbcl |
|---|
| 346 |
(deftests write-deadline (usp nil t) |
|---|
| 347 |
(with-thread ("echo server for deadline test" |
|---|
| 348 |
(lambda () (init-server :unwrap-stream-p usp)) |
|---|
| 349 |
#'test-server) |
|---|
| 350 |
(with-open-stream (socket (init-client :unwrap-stream-p usp)) |
|---|
| 351 |
(unwind-protect |
|---|
| 352 |
(sb-sys:with-deadline (:seconds 3) |
|---|
| 353 |
(write-line "test" socket) |
|---|
| 354 |
(force-output socket) |
|---|
| 355 |
(assert (equal (read-line socket) "(echo test)")) |
|---|
| 356 |
(write-line "freeze" socket) |
|---|
| 357 |
(force-output socket) |
|---|
| 358 |
(let ((n 0)) |
|---|
| 359 |
(handler-case |
|---|
| 360 |
(loop |
|---|
| 361 |
(write-line "deadbeef" socket) |
|---|
| 362 |
(incf n)) |
|---|
| 363 |
(sb-sys:deadline-timeout ())) |
|---|
| 364 |
;; should have written a couple of lines before the deadline: |
|---|
| 365 |
(assert (> n 100)))) |
|---|
| 366 |
(handler-case |
|---|
| 367 |
(close-socket socket :abort t) |
|---|
| 368 |
(sb-sys:deadline-timeout ()))))))) |
|---|
| 369 |
|
|---|
| 370 |
#+(or) |
|---|
| 371 |
(run-all-tests) |
|---|