|
1 | 1 | (ns clj-libssh2.socket
|
2 |
| - (:require [net.n01se.clojure-jna :as jna])) |
| 2 | + (:require [net.n01se.clojure-jna :as jna] |
| 3 | + [clj-libssh2.error :refer [handle-errors]] |
| 4 | + [clj-libssh2.libssh2 :as libssh2] |
| 5 | + [clj-libssh2.libssh2.keepalive :as libssh2-keepalive] |
| 6 | + [clj-libssh2.libssh2.session :as libssh2-session]) |
| 7 | + (:import [com.sun.jna.ptr IntByReference])) |
| 8 | + |
| 9 | +;; Constants from libsimplesocket.h |
| 10 | + |
| 11 | +(def SIMPLE_SOCKET_BAD_ADDRESS -1) |
| 12 | +(def SIMPLE_SOCKET_SOCKET_FAILED -2) |
| 13 | +(def SIMPLE_SOCKET_CONNECT_FAILED -3) |
| 14 | + |
| 15 | +(def SIMPLE_SOCKET_SELECT_READ 1) |
| 16 | +(def SIMPLE_SOCKET_SELECT_WRITE 2) |
| 17 | +(def SIMPLE_SOCKET_SELECT_ERROR 4) |
3 | 18 |
|
4 | 19 | (def close
|
5 | 20 | "Close a socket"
|
|
14 | 29 | port)]
|
15 | 30 | (when (> 0 socket)
|
16 | 31 | ;; Magic numbers are from libsimplesocket.h
|
17 |
| - (throw (Exception. (case socket |
18 |
| - -1 (format "%s is not a valid IP address" address) |
19 |
| - -2 "Failed to create a TCP socket" |
20 |
| - -3 (format "Failed to connect to %s:%d" address port) |
| 32 | + (throw (Exception. (condp = socket |
| 33 | + SIMPLE_SOCKET_BAD_ADDRESS |
| 34 | + (format "%s is not a valid IP address" address) |
| 35 | + |
| 36 | + SIMPLE_SOCKET_SOCKET_FAILED |
| 37 | + "Failed to create a TCP socket" |
| 38 | + |
| 39 | + SIMPLE_SOCKET_CONNECT_FAILED |
| 40 | + (format "Failed to connect to %s:%d" address port) |
| 41 | + |
21 | 42 | "An unknown error occurred"))))
|
22 | 43 | socket))
|
| 44 | + |
| 45 | +(defn select |
| 46 | + "Call select on a socket from a clj-libssh2 Session." |
| 47 | + [session select-read select-write timeout] |
| 48 | + (jna/invoke Integer |
| 49 | + simplesocket/simple_socket_select |
| 50 | + (:socket session) |
| 51 | + (bit-or |
| 52 | + (if select-read SIMPLE_SOCKET_SELECT_READ 0) |
| 53 | + (if select-write SIMPLE_SOCKET_SELECT_WRITE 0)) |
| 54 | + timeout)) |
| 55 | + |
| 56 | +(defn send-keepalive |
| 57 | + "Send a keepalive message and return the number of seconds until the next |
| 58 | + time we should send a keepalive." |
| 59 | + [session] |
| 60 | + (let [seconds-to-wait (IntByReference.)] |
| 61 | + (handle-errors session |
| 62 | + (libssh2-keepalive/send (:session session) seconds-to-wait)) |
| 63 | + (.getValue seconds-to-wait))) |
| 64 | + |
| 65 | +(defn wait |
| 66 | + "Roughly equivalent to _libssh2_wait_socket in libssh2. Will raise an error |
| 67 | + on timeout or just block until it's time to try again." |
| 68 | + ([session] |
| 69 | + (wait session (System/currentTimeMillis))) |
| 70 | + ([session start-time] |
| 71 | + (let [ms-until-next-keepalive (* (send-keepalive session) 1000) |
| 72 | + block-directions (libssh2-session/block-directions (:session session)) |
| 73 | + block-for-read (boolean (bit-and block-directions libssh2/SESSION_BLOCK_INBOUND)) |
| 74 | + block-for-write (boolean (bit-and block-directions libssh2/SESSION_BLOCK_OUTBOUND)) |
| 75 | + libssh2-timeout-ms (libssh2-session/get-timeout (:session session)) |
| 76 | + select-until (partial select session block-for-read block-for-write) |
| 77 | + select-result (cond (and (< 0 libssh2-timeout-ms) |
| 78 | + (or (= 0 ms-until-next-keepalive) |
| 79 | + (> ms-until-next-keepalive libssh2-timeout-ms))) |
| 80 | + (let [elapsed (- (System/currentTimeMillis) start-time)] |
| 81 | + (when (> elapsed libssh2-timeout-ms) |
| 82 | + (handle-errors session libssh2/ERROR_TIMEOUT)) |
| 83 | + (select-until (- libssh2-timeout-ms elapsed))) |
| 84 | + |
| 85 | + (< 0 ms-until-next-keepalive) |
| 86 | + (select-until ms-until-next-keepalive) |
| 87 | + |
| 88 | + :else |
| 89 | + (select-until 0))] |
| 90 | + (when (>= 0 select-result) |
| 91 | + (handle-errors session libssh2/ERROR_TIMEOUT))))) |
| 92 | + |
| 93 | +(defmacro block |
| 94 | + "Turn a non-blocking call that returns EAGAIN into a blocking one." |
| 95 | + [session & body] |
| 96 | + `(let [start-time# (-> (System/currentTimeMillis) (/ 1000.0) Math/round)] |
| 97 | + (while (= libssh2/ERROR_EAGAIN (do ~@body)) |
| 98 | + (handle-errors ~session |
| 99 | + (wait ~session start-time#))))) |
| 100 | + |
| 101 | +(defmacro block-return |
| 102 | + "Similar to block, but for functions that return a pointer" |
| 103 | + [session & body] |
| 104 | + `(let [start-time# (-> (System/currentTimeMillis) (/ 1000.0) Math/round)] |
| 105 | + (loop [result# (do ~@body)] |
| 106 | + (if (nil? result#) |
| 107 | + (let [errno# (libssh2-session/last-errno (:session ~session))] |
| 108 | + (handle-errors ~session errno#) |
| 109 | + (when (= libssh2/ERROR_EAGAIN errno#) |
| 110 | + (wait ~session start-time#)) |
| 111 | + (recur (do ~@body))) |
| 112 | + result#)))) |
0 commit comments