]>
Commit | Line | Data |
---|---|---|
562c96c0 BM |
1 | <?php |
2 | ||
50d6d455 | 3 | namespace UnrealIRCd; |
562c96c0 | 4 | |
8d5a112f | 5 | use Exception; |
562c96c0 BM |
6 | use WebSocket; |
7 | ||
8 | class Connection | |
9 | { | |
50d6d455 D |
10 | protected WebSocket\Client $connection; |
11 | ||
1a3fda20 BM |
12 | public $errno = 0; |
13 | public $error = NULL; | |
14 | ||
a18591f9 | 15 | public function __construct(string $uri, string $api_login, array $options = null) |
50d6d455 D |
16 | { |
17 | $context = $options["context"] ?? stream_context_create(); | |
18 | ||
8d5a112f | 19 | if (isset($options["tls_verify"]) && !$options["tls_verify"]) { |
50d6d455 D |
20 | stream_context_set_option($context, 'ssl', 'verify_peer', false); |
21 | stream_context_set_option($context, 'ssl', 'verify_peer_name', false); | |
22 | } | |
23 | ||
24 | $this->connection = new WebSocket\Client($uri, [ | |
25 | 'context' => $context, | |
26 | 'headers' => [ | |
27 | 'Authorization' => sprintf('Basic %s', base64_encode($api_login)), | |
28 | ], | |
29 | 'timeout' => 10, | |
30 | ]); | |
31 | ||
57c1d592 | 32 | /* Start the connection now */ |
02da13a9 | 33 | if (isset($options["issuer"])) |
fbdccdf5 BM |
34 | { |
35 | /* Set issuer and don't wait for the reply (async) */ | |
36 | $this->query('rpc.set_issuer', ['name' => $options["issuer"]], true); | |
37 | } else { | |
38 | /* Ping-pong */ | |
02da13a9 | 39 | $this->connection->ping(); |
fbdccdf5 | 40 | } |
50d6d455 D |
41 | } |
42 | ||
8d5a112f D |
43 | /** |
44 | * Encode and send a query to the RPC server. | |
45 | * | |
46 | * @note I'm not sure on the response type except that it may be either an object or array. | |
47 | * | |
48 | * @param string $method | |
a18591f9 | 49 | * @param array|null $params |
fbdccdf5 | 50 | * @param bool $no_wait |
a18591f9 | 51 | * |
8d5a112f D |
52 | * @return object|array|bool |
53 | * @throws Exception | |
54 | */ | |
fbdccdf5 | 55 | public function query(string $method, array|null $params = null, $no_wait = false): object|array|bool |
50d6d455 | 56 | { |
8d5a112f D |
57 | $id = random_int(1, 99999); |
58 | ||
50d6d455 D |
59 | $rpc = [ |
60 | "jsonrpc" => "2.0", | |
61 | "method" => $method, | |
62 | "params" => $params, | |
8d5a112f | 63 | "id" => $id |
50d6d455 D |
64 | ]; |
65 | ||
66 | $json_rpc = json_encode($rpc); | |
50d6d455 | 67 | $this->connection->text($json_rpc); |
a3518a0a | 68 | |
fbdccdf5 BM |
69 | if ($no_wait) |
70 | return true; | |
71 | ||
21768a11 | 72 | $starttime = time(); |
a3518a0a BM |
73 | do { |
74 | $reply = $this->connection->receive(); | |
75 | ||
76 | $reply = json_decode($reply); | |
77 | ||
21667b50 BM |
78 | if (property_exists($reply, 'id') && ($id !== $reply->id)) |
79 | { | |
80 | /* This is not our request. Perhaps we are streaming log events | |
81 | * or this is an asynchronous response to like set_issuer. | |
82 | * We don't care about that, continue. | |
83 | * NOTE: This does mean that this event info is "lost" | |
a3518a0a | 84 | */ |
21667b50 BM |
85 | continue; |
86 | } | |
87 | ||
88 | if (property_exists($reply, 'result')) { | |
a3518a0a BM |
89 | $this->errno = 0; |
90 | $this->error = NULL; | |
91 | return $reply->result; | |
92 | } | |
93 | ||
94 | if (property_exists($reply, 'error')) { | |
95 | $this->errno = $reply->error->code; | |
96 | $this->error = $reply->error->message; | |
97 | return false; | |
98 | } | |
21768a11 BM |
99 | if (time() - $starttime > 10) |
100 | throw new Exception('RPC request timed out'); | |
fbdccdf5 | 101 | } while(1); // wait for the reply to OUR request |
a3518a0a BM |
102 | |
103 | /* This should never happen */ | |
104 | throw new Exception('Invalid JSON-RPC response from UnrealIRCd: not an error and not a result.'); | |
105 | } | |
106 | ||
107 | /** | |
108 | * Grab and/or wait for next event. Used for log streaming. | |
109 | * @note This function will return NULL after a 10 second timeout, | |
110 | * this so the function is not entirely blocking. You can safely | |
111 | * retry the operation if the return value === NULL. | |
112 | * | |
113 | * @return object|array|bool|null | |
114 | * @throws Exception | |
115 | */ | |
116 | public function eventloop(): object|array|bool|null | |
117 | { | |
94e1459d | 118 | $this->connection->setTimeout(2); /* lower timeout for socket loop */ |
49faf2cf | 119 | $starttime = microtime(true); |
a3518a0a BM |
120 | try { |
121 | $reply = $this->connection->receive(); | |
49faf2cf | 122 | } catch (WebSocket\TimeoutException $e) { |
101a298c | 123 | if (microtime(true) - $starttime < 1) |
49faf2cf BM |
124 | { |
125 | /* There's some bug in the library: if we | |
126 | * caught the timeout exception once (so | |
127 | * harmless) and then later the server gets | |
128 | * killed or closes the connection otherwise, | |
129 | * then it will again throw WebSocket\TimeoutException | |
130 | * even though it has nothing to do with timeouts. | |
131 | * We detect this by checking if the timeout | |
132 | * took less than 1 second, then we know for sure | |
133 | * that it wasn't really a timeout (since the | |
134 | * timeout is normally 10 seconds). | |
135 | */ | |
136 | throw $e; | |
137 | } | |
a3518a0a BM |
138 | return NULL; |
139 | } | |
50d6d455 | 140 | |
94e1459d BM |
141 | $this->connection->setTimeout(10); /* set timeout back again */ |
142 | ||
50d6d455 D |
143 | $reply = json_decode($reply); |
144 | ||
65cc0295 | 145 | if (property_exists($reply, 'result')) { |
1a3fda20 BM |
146 | $this->errno = 0; |
147 | $this->error = NULL; | |
657dad63 | 148 | return $reply->result; |
50d6d455 | 149 | } |
bea99f3a | 150 | |
a3518a0a | 151 | /* This would be weird */ |
1a3fda20 BM |
152 | if (property_exists($reply, 'error')) { |
153 | $this->errno = $reply->error->code; | |
154 | $this->error = $reply->error->message; | |
155 | return false; | |
bea99f3a D |
156 | } |
157 | ||
1a3fda20 | 158 | /* This should never happen */ |
a3518a0a | 159 | throw new Exception('Invalid JSON-RPC data from UnrealIRCd: not an error and not a result.'); |
50d6d455 | 160 | } |
7c7017a2 | 161 | |
c6e7dd34 BM |
162 | public function rpc(): Rpc |
163 | { | |
164 | return new Rpc($this); | |
165 | } | |
166 | ||
1ef2e09f BM |
167 | public function stats(): Stats |
168 | { | |
169 | return new Stats($this); | |
170 | } | |
171 | ||
7c7017a2 BM |
172 | public function user(): User |
173 | { | |
174 | return new User($this); | |
175 | } | |
176 | ||
177 | public function channel(): Channel | |
178 | { | |
179 | return new Channel($this); | |
180 | } | |
181 | ||
e4de690d | 182 | public function serverban(): ServerBan |
7c7017a2 | 183 | { |
e4de690d | 184 | return new ServerBan($this); |
7c7017a2 BM |
185 | } |
186 | ||
187 | public function spamfilter(): Spamfilter | |
188 | { | |
189 | return new Spamfilter($this); | |
190 | } | |
1ef2e09f | 191 | |
8fd8402e VP |
192 | public function nameban(): NameBan |
193 | { | |
194 | return new NameBan($this); | |
195 | } | |
1ef2e09f | 196 | |
1d1d6c90 VP |
197 | public function server(): Server |
198 | { | |
199 | return new Server($this); | |
200 | } | |
1ef2e09f | 201 | |
65b6ddcb | 202 | public function serverbanexception(): ServerBanException |
9d9157c2 | 203 | { |
9ff6e559 | 204 | return new ServerBanException($this); |
9d9157c2 | 205 | } |
1ef2e09f | 206 | |
a3518a0a BM |
207 | public function log(): Log |
208 | { | |
209 | return new Log($this); | |
210 | } | |
562c96c0 | 211 | } |