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