[cl-plus-ssl-devel] (and :cl+ssl :clisp :windows)
Pixel // pinterface
pinterface at gmail.com
Sat Mar 24 19:49:56 UTC 2007
Attempting to use cl+ssl (via drakma) with clisp on Win32 quickly resulted
in errors about streams ending, rather than encrypted connections (I didn't
keep a copy of the error around, so I can't tell you exactly; sorry). A
little digging revealed cl+ssl::stream-read-byte was returning nil rather
than a character.
;; from cl+ssl/streams.lisp
(defmethod stream-read-byte ((stream ssl-stream))
(or (ssl-stream-peeked-byte stream)
(let ((buf (ssl-stream-input-buffer stream)))
(handler-case
(cffi-sys::with-pointer-to-vector-data (ptr buf)
(ensure-ssl-funcall (ssl-stream-socket stream)
(ssl-stream-handle stream)
#'ssl-read
5.5
(ssl-stream-handle stream)
ptr
1)
(elt buf 0))
(ssl-error-zero-return () ;SSL_read returns 0 on end-of-file
:eof)))))
Sure doesn't look like it returns nil, does it?
A little more digging revealed this is caused by CFFI.
with-pointer-to-vector-data, on clisp, does a copy-in, copy-out of the
vector. Arguably, it's CFFI's fault for not ensuring the return value of
<body> is returned by with-pointer-to-vector-data. That's easy enough to
change (additions in [square brackets]):
;; from cffi/src/cffi-clisp.lisp
(defmacro with-pointer-to-vector-data ((ptr-var vector) &body body)
"Bind PTR-VAR to a foreign pointer to the data in VECTOR."
(with-unique-names (vector-var size-var)
`(let ((,vector-var ,vector))
(check-type ,vector-var shareable-byte-vector)
(with-foreign-pointer (,ptr-var (length ,vector-var) ,size-var)
[(prog2]
;; copy-in
(loop for i below ,size-var do
(%mem-set (aref ,vector-var i) ,ptr-var :unsigned-char i))
[(progn] , at body[)]
;; copy-out
(loop for i below ,size-var do
(setf (aref ,vector-var i)
(%mem-ref ,ptr-var :unsigned-char i)))[)]))))
But it doesn't actually solve the problem of broken abstraction: That (elt
buf 0) in stream-read-byte looks at buf before data is copied out, so rather
than returning the first line of an HTTP response #(#\H #\T #\T #\P ...), it
returns the first line of that HTTP response one character late #(#\Null #\H
#\T #\T ...), which makes drakma unhappy. This is still arguably CFFI's
fault for having a broken abstraction, however it /is/ an experimental API
and I don't see how CFFI could plug this particular abstraction leak (which
appears to have been discussed before[1]); meaning it's up to cl+ssl to work
around the limitation.
By moving (elt buf 0) out of the with-pointer-to-vector-data call like so
(changes in [square brackets] again), stream-read-byte returns the right
characters at the right time on clisp.
;; in cl+ssl/streams.lisp
(defmethod stream-read-byte ((stream ssl-stream))
(or (ssl-stream-peeked-byte stream)
(let ((buf (ssl-stream-input-buffer stream)))
(handler-case
[(progn]
(cffi-sys::with-pointer-to-vector-data (ptr buf)
(ensure-ssl-funcall
(ssl-stream-socket stream)
(ssl-stream-handle stream)
#'ssl-read
5.5
(ssl-stream-handle stream)
ptr
1)[) #| ends w/pointer, not the new progn |#]
(elt buf 0))
(ssl-error-zero-return () ;SSL_read returns 0 on end-of-file
:eof)))))
This obviates the need for the above changes to CFFI and I /think/ retains
correct behavior on other lisp implementations--buf is still around, so I
don't see why it wouldn't--but that's not a theory I can test.
[1] http://common-lisp.net/pipermail/cffi-devel/2006-January/000495.html
-pixie If you think I'm nuts, feel free to call me on it.
More information about the cl-plus-ssl-devel
mailing list