Introduce e_backend_is_destination_reachable()
[platform/upstream/evolution-data-server.git] / libebackend / e-backend.c
1 /*
2  * e-backend.c
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) version 3.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with the program; if not, see <http://www.gnu.org/licenses/>
16  *
17  */
18
19 /**
20  * SECTION: e-backend
21  * @include: libebackend/libebackend.h
22  * @short_description: An abstract base class for backends
23  *
24  * An #EBackend is paired with an #ESource to facilitate performing
25  * actions on the local or remote resource described by the #ESource.
26  *
27  * In other words, whereas a certain backend type knows how to talk to a
28  * certain type of server or data store, the #ESource fills in configuration
29  * details such as host name, user name, resource path, etc.
30  *
31  * All #EBackend instances are created by an #EBackendFactory.
32  **/
33
34 #include <config.h>
35 #include <glib/gi18n-lib.h>
36
37 #include <gio/gio.h>
38
39 #include <libedataserver/libedataserver.h>
40
41 #include "e-backend.h"
42 #include "e-user-prompter.h"
43
44 #define E_BACKEND_GET_PRIVATE(obj) \
45         (G_TYPE_INSTANCE_GET_PRIVATE \
46         ((obj), E_TYPE_BACKEND, EBackendPrivate))
47
48 typedef struct _AsyncContext AsyncContext;
49
50 struct _EBackendPrivate {
51         ESource *source;
52         EUserPrompter *prompter;
53         gboolean online;
54 };
55
56 struct _AsyncContext {
57         ESourceAuthenticator *auth;
58 };
59
60 enum {
61         PROP_0,
62         PROP_ONLINE,
63         PROP_SOURCE,
64         PROP_USER_PROMPTER
65 };
66
67 G_DEFINE_ABSTRACT_TYPE (EBackend, e_backend, G_TYPE_OBJECT)
68
69 static void
70 async_context_free (AsyncContext *async_context)
71 {
72         if (async_context->auth != NULL)
73                 g_object_unref (async_context->auth);
74
75         g_slice_free (AsyncContext, async_context);
76 }
77
78 static void
79 backend_set_source (EBackend *backend,
80                     ESource *source)
81 {
82         g_return_if_fail (E_IS_SOURCE (source));
83         g_return_if_fail (backend->priv->source == NULL);
84
85         backend->priv->source = g_object_ref (source);
86 }
87
88 static void
89 backend_set_property (GObject *object,
90                       guint property_id,
91                       const GValue *value,
92                       GParamSpec *pspec)
93 {
94         switch (property_id) {
95                 case PROP_ONLINE:
96                         e_backend_set_online (
97                                 E_BACKEND (object),
98                                 g_value_get_boolean (value));
99                         return;
100
101                 case PROP_SOURCE:
102                         backend_set_source (
103                                 E_BACKEND (object),
104                                 g_value_get_object (value));
105                         return;
106         }
107
108         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
109 }
110
111 static void
112 backend_get_property (GObject *object,
113                       guint property_id,
114                       GValue *value,
115                       GParamSpec *pspec)
116 {
117         switch (property_id) {
118                 case PROP_ONLINE:
119                         g_value_set_boolean (
120                                 value, e_backend_get_online (
121                                 E_BACKEND (object)));
122                         return;
123
124                 case PROP_SOURCE:
125                         g_value_set_object (
126                                 value, e_backend_get_source (
127                                 E_BACKEND (object)));
128                         return;
129
130                 case PROP_USER_PROMPTER:
131                         g_value_set_object (
132                                 value, e_backend_get_user_prompter (
133                                 E_BACKEND (object)));
134                         return;
135         }
136
137         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
138 }
139
140 static void
141 backend_dispose (GObject *object)
142 {
143         EBackendPrivate *priv;
144
145         priv = E_BACKEND_GET_PRIVATE (object);
146
147         if (priv->source != NULL) {
148                 g_object_unref (priv->source);
149                 priv->source = NULL;
150         }
151
152         if (priv->prompter) {
153                 g_object_unref (priv->prompter);
154                 priv->prompter = NULL;
155         }
156
157         /* Chain up to parent's dispose() method. */
158         G_OBJECT_CLASS (e_backend_parent_class)->dispose (object);
159 }
160
161 static void
162 backend_constructed (GObject *object)
163 {
164         GNetworkMonitor *monitor;
165
166         /* Chain up to parent's constructed() method. */
167         G_OBJECT_CLASS (e_backend_parent_class)->constructed (object);
168
169         /* Synchronize network monitoring. */
170
171         monitor = g_network_monitor_get_default ();
172
173         g_object_bind_property (
174                 monitor, "network-available",
175                 object, "online",
176                 G_BINDING_SYNC_CREATE);
177 }
178
179 static void
180 backend_authenticate_thread (GSimpleAsyncResult *simple,
181                              GObject *object,
182                              GCancellable *cancellable)
183 {
184         AsyncContext *async_context;
185         GError *error = NULL;
186
187         async_context = g_simple_async_result_get_op_res_gpointer (simple);
188
189         e_backend_authenticate_sync (
190                 E_BACKEND (object),
191                 async_context->auth,
192                 cancellable, &error);
193
194         if (error != NULL)
195                 g_simple_async_result_take_error (simple, error);
196 }
197
198 static gboolean
199 backend_authenticate_sync (EBackend *backend,
200                            ESourceAuthenticator *auth,
201                            GCancellable *cancellable,
202                            GError **error)
203 {
204         g_set_error (
205                 error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
206                 _("%s does not support authentication"),
207                 G_OBJECT_TYPE_NAME (backend));
208
209         return FALSE;
210 }
211
212 static void
213 backend_authenticate (EBackend *backend,
214                       ESourceAuthenticator *auth,
215                       GCancellable *cancellable,
216                       GAsyncReadyCallback callback,
217                       gpointer user_data)
218 {
219         GSimpleAsyncResult *simple;
220         AsyncContext *async_context;
221
222         async_context = g_slice_new0 (AsyncContext);
223         async_context->auth = g_object_ref (auth);
224
225         simple = g_simple_async_result_new (
226                 G_OBJECT (backend), callback,
227                 user_data, backend_authenticate);
228
229         g_simple_async_result_set_check_cancellable (simple, cancellable);
230
231         g_simple_async_result_set_op_res_gpointer (
232                 simple, async_context, (GDestroyNotify) async_context_free);
233
234         g_simple_async_result_run_in_thread (
235                 simple, backend_authenticate_thread,
236                 G_PRIORITY_DEFAULT, cancellable);
237
238         g_object_unref (simple);
239 }
240
241 static gboolean
242 backend_authenticate_finish (EBackend *backend,
243                              GAsyncResult *result,
244                              GError **error)
245 {
246         GSimpleAsyncResult *simple;
247
248         g_return_val_if_fail (
249                 g_simple_async_result_is_valid (
250                 result, G_OBJECT (backend),
251                 backend_authenticate), FALSE);
252
253         simple = G_SIMPLE_ASYNC_RESULT (result);
254
255         /* Assume success unless a GError is set. */
256         return !g_simple_async_result_propagate_error (simple, error);
257 }
258
259 static gboolean
260 backend_get_destination_address (EBackend *backend,
261                                  gchar **host,
262                                  guint16 *port)
263 {
264         /* default implementation returns FALSE, indicating
265            no remote destination being used for this backend */
266         return FALSE;
267 }
268
269 static void
270 e_backend_class_init (EBackendClass *class)
271 {
272         GObjectClass *object_class;
273
274         g_type_class_add_private (class, sizeof (EBackendPrivate));
275
276         object_class = G_OBJECT_CLASS (class);
277         object_class->set_property = backend_set_property;
278         object_class->get_property = backend_get_property;
279         object_class->dispose = backend_dispose;
280         object_class->constructed = backend_constructed;
281
282         class->authenticate_sync = backend_authenticate_sync;
283         class->authenticate = backend_authenticate;
284         class->authenticate_finish = backend_authenticate_finish;
285         class->get_destination_address = backend_get_destination_address;
286
287         g_object_class_install_property (
288                 object_class,
289                 PROP_ONLINE,
290                 g_param_spec_boolean (
291                         "online",
292                         "Online",
293                         "Whether the backend is online",
294                         TRUE,
295                         G_PARAM_READWRITE |
296                         G_PARAM_CONSTRUCT |
297                         G_PARAM_STATIC_STRINGS));
298
299         g_object_class_install_property (
300                 object_class,
301                 PROP_SOURCE,
302                 g_param_spec_object (
303                         "source",
304                         "Source",
305                         "The data source being acted upon",
306                         E_TYPE_SOURCE,
307                         G_PARAM_READWRITE |
308                         G_PARAM_CONSTRUCT_ONLY |
309                         G_PARAM_STATIC_STRINGS));
310
311         g_object_class_install_property (
312                 object_class,
313                 PROP_USER_PROMPTER,
314                 g_param_spec_object (
315                         "user-prompter",
316                         "User Prompter",
317                         "User prompter instance",
318                         E_TYPE_USER_PROMPTER,
319                         G_PARAM_READABLE |
320                         G_PARAM_STATIC_STRINGS));
321 }
322
323 static void
324 e_backend_init (EBackend *backend)
325 {
326         backend->priv = E_BACKEND_GET_PRIVATE (backend);
327         backend->priv->prompter = e_user_prompter_new ();
328 }
329
330 /**
331  * e_backend_get_online:
332  * @backend: an #EBackend
333  *
334  * Returns the online state of @backend: %TRUE if @backend is online,
335  * %FALSE if offline.  The online state of each backend is bound to the
336  * online state of the #EDataFactory that created it.
337  *
338  * Returns: the online state
339  *
340  * Since: 3.4
341  **/
342 gboolean
343 e_backend_get_online (EBackend *backend)
344 {
345         g_return_val_if_fail (E_IS_BACKEND (backend), FALSE);
346
347         return backend->priv->online;
348 }
349
350 /**
351  * e_backend_set_online:
352  * @backend: an #EBackend
353  * @online: the online state
354  *
355  * Sets the online state of @backend: %TRUE if @backend is online,
356  * @FALSE if offline.  The online state of each backend is bound to
357  * the online state of the #EDataFactory that created it.
358  *
359  * Since: 3.4
360  **/
361 void
362 e_backend_set_online (EBackend *backend,
363                       gboolean online)
364 {
365         g_return_if_fail (E_IS_BACKEND (backend));
366
367         /* Avoid unnecessary "notify" signals. */
368         if (backend->priv->online == online)
369                 return;
370
371         backend->priv->online = online;
372
373         g_object_notify (G_OBJECT (backend), "online");
374 }
375
376 /**
377  * e_backend_get_source:
378  * @backend: an #EBackend
379  *
380  * Returns the #ESource to which @backend is paired.
381  *
382  * Returns: the #ESource to which @backend is paired
383  *
384  * Since: 3.4
385  **/
386 ESource *
387 e_backend_get_source (EBackend *backend)
388 {
389         g_return_val_if_fail (E_IS_BACKEND (backend), NULL);
390
391         return backend->priv->source;
392 }
393
394 /**
395  * e_backend_authenticate_sync:
396  * @backend: an #EBackend
397  * @auth: an #ESourceAuthenticator
398  * @cancellable: optional #GCancellable object, or %NULL
399  * @error: return location for a #GError, or %NULL
400  *
401  * Convenience function providing a consistent authentication interface
402  * for backends running in either the registry service itself or a client
403  * process communicating with the registry service over D-Bus.
404  *
405  * Authenticates @backend's #EBackend:source, using @auth to handle
406  * authentication attempts.  The @backend and @auth arguments may be one
407  * and the same if @backend implements the #ESourceAuthenticator interface.
408  * The operation loops until authentication is successful or the user aborts
409  * further authentication attempts.  If an error occurs, the function will
410  * set @error and return %FALSE.
411  *
412  * Returns: %TRUE on success, %FALSE on failure
413  *
414  * Since: 3.6
415  **/
416 gboolean
417 e_backend_authenticate_sync (EBackend *backend,
418                              ESourceAuthenticator *auth,
419                              GCancellable *cancellable,
420                              GError **error)
421 {
422         EBackendClass *class;
423
424         g_return_val_if_fail (E_IS_BACKEND (backend), FALSE);
425         g_return_val_if_fail (E_IS_SOURCE_AUTHENTICATOR (auth), FALSE);
426
427         class = E_BACKEND_GET_CLASS (backend);
428         g_return_val_if_fail (class->authenticate_sync != NULL, FALSE);
429
430         return class->authenticate_sync (backend, auth, cancellable, error);
431 }
432
433 /**
434  * e_backend_authenticate:
435  * @backend: an #EBackend
436  * @auth: an #ESourceAuthenticator
437  * @cancellable: optional #GCancellable object, or %NULL
438  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
439  * @user_data: data to pass to the callback function
440  *
441  * Convenience function providing a consistent authentication interface
442  * for backends running in either the registry service itself or a client
443  * process communicating with the registry service over D-Bus.
444  *
445  * Asynchronously authenticates @backend's #EBackend:source, using @auth
446  * to handle authentication attempts.  The @backend and @auth arguments may
447  * be one and the same if @backend implements the #ESourceAuthenticator
448  * interface.  The operation loops until authentication is succesful or the
449  * user aborts further authentication attempts.
450  *
451  * When the operation is finished, @callback will be called.  You can then
452  * call e_backend_authenticate_finish() to get the result of the operation.
453  *
454  * Since: 3.6
455  **/
456 void
457 e_backend_authenticate (EBackend *backend,
458                         ESourceAuthenticator *auth,
459                         GCancellable *cancellable,
460                         GAsyncReadyCallback callback,
461                         gpointer user_data)
462 {
463         EBackendClass *class;
464
465         g_return_if_fail (E_IS_BACKEND (backend));
466         g_return_if_fail (E_IS_SOURCE_AUTHENTICATOR (auth));
467
468         class = E_BACKEND_GET_CLASS (backend);
469         g_return_if_fail (class->authenticate != NULL);
470
471         class->authenticate (backend, auth, cancellable, callback, user_data);
472 }
473
474 /**
475  * e_backend_authenticate_finish:
476  * @backend: an #EBackend
477  * @result: a #GAsyncResult
478  * @error: return location for a #GError, or %NULL
479  *
480  * Finishes the operation started with e_backend_authenticate().  If
481  * an error occurred, the function will set @error and return %FALSE.
482  *
483  * Returns: %TRUE on success, %FALSE on failure
484  *
485  * Since: 3.6
486  **/
487 gboolean
488 e_backend_authenticate_finish (EBackend *backend,
489                                GAsyncResult *result,
490                                GError **error)
491 {
492         EBackendClass *class;
493
494         g_return_val_if_fail (E_IS_BACKEND (backend), FALSE);
495         g_return_val_if_fail (G_IS_ASYNC_RESULT (result), FALSE);
496
497         class = E_BACKEND_GET_CLASS (backend);
498         g_return_val_if_fail (class->authenticate_finish != NULL, FALSE);
499
500         return class->authenticate_finish (backend, result, error);
501 }
502
503 /**
504  * e_backend_get_user_prompter:
505  * @backend: an #EBackend
506  *
507  * Gets an instance of #EUserPrompter, associated with this @backend.
508  * The instance is owned by the @backend.
509  *
510  * Returns: (transfer-none): an #EUserPrompter instance
511  *
512  * Since: 3.8
513  **/
514 EUserPrompter *
515 e_backend_get_user_prompter (EBackend *backend)
516 {
517         g_return_val_if_fail (E_IS_BACKEND (backend), NULL);
518
519         return backend->priv->prompter;
520 }
521
522 /**
523  * e_backend_trust_prompt_sync:
524  * @backend: an #EBackend
525  * @parameters: an #ENamedParameters with values for the trust prompt
526  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
527  * @error: return location for a #GError, or %NULL
528  *
529  * Asks a user a trust prompt with given @parameters, and returns what
530  * user responded. This blocks until the response is delivered.
531  *
532  * Returns: an #ETrustPromptResponse what user responded
533  *
534  * Note: The function can return also %E_TRUST_PROMPT_RESPONSE_UNKNOWN,
535  *    it's on error or if user closes the trust prompt dialog with other
536  *    than the offered buttons. Usual behaviour in such case is to treat
537  *    it as a temporary reject.
538  *
539  * Since: 3.8
540  **/
541 ETrustPromptResponse
542 e_backend_trust_prompt_sync (EBackend *backend,
543                              const ENamedParameters *parameters,
544                              GCancellable *cancellable,
545                              GError **error)
546 {
547         EUserPrompter *prompter;
548         gint response;
549
550         g_return_val_if_fail (
551                 E_IS_BACKEND (backend), E_TRUST_PROMPT_RESPONSE_UNKNOWN);
552         g_return_val_if_fail (
553                 parameters != NULL, E_TRUST_PROMPT_RESPONSE_UNKNOWN);
554
555         prompter = e_backend_get_user_prompter (backend);
556         g_return_val_if_fail (
557                 prompter != NULL, E_TRUST_PROMPT_RESPONSE_UNKNOWN);
558
559         response = e_user_prompter_extension_prompt_sync (
560                 prompter, "ETrustPrompt::trust-prompt",
561                 parameters, NULL, cancellable, error);
562
563         if (response == 0)
564                 return E_TRUST_PROMPT_RESPONSE_REJECT;
565         if (response == 1)
566                 return E_TRUST_PROMPT_RESPONSE_ACCEPT;
567         if (response == 2)
568                 return E_TRUST_PROMPT_RESPONSE_ACCEPT_TEMPORARILY;
569         if (response == -1)
570                 return E_TRUST_PROMPT_RESPONSE_REJECT_TEMPORARILY;
571
572         return E_TRUST_PROMPT_RESPONSE_UNKNOWN;
573 }
574
575 /**
576  * e_backend_trust_prompt:
577  * @backend: an #EBackend
578  * @parameters: an #ENamedParameters with values for the trust prompt
579  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
580  * @callback: a #GAsyncReadyCallback to call when the request is satisfied
581  * @user_data: data to pass to the callback function
582  *
583  * Initiates a user trust prompt with given @parameters.
584  *
585  * When the operation is finished, @callback will be called. You can then
586  * call e_backend_trust_prompt_finish() to get the result of the operation.
587  *
588  * Since: 3.8
589  **/
590 void
591 e_backend_trust_prompt (EBackend *backend,
592                         const ENamedParameters *parameters,
593                         GCancellable *cancellable,
594                         GAsyncReadyCallback callback,
595                         gpointer user_data)
596 {
597         EUserPrompter *prompter;
598
599         g_return_if_fail (E_IS_BACKEND (backend));
600         g_return_if_fail (parameters != NULL);
601
602         prompter = e_backend_get_user_prompter (backend);
603         g_return_if_fail (prompter != NULL);
604
605         e_user_prompter_extension_prompt (
606                 prompter, "ETrustPrompt::trust-prompt",
607                 parameters, cancellable, callback, user_data);
608 }
609
610 /**
611  * e_backend_trust_prompt_finish:
612  * @backend: an #EBackend
613  * @result: a #GAsyncResult
614  * @error: return location for a #GError, or %NULL
615  *
616  * Finishes the operation started with e_backend_trust_prompt().
617  * If an error occurred, the function will set @error and return
618  * %E_TRUST_PROMPT_RESPONSE_UNKNOWN.
619  *
620  * Returns: an #ETrustPromptResponse what user responded
621  *
622  * Note: The function can return also %E_TRUST_PROMPT_RESPONSE_UNKNOWN,
623  *    it's on error or if user closes the trust prompt dialog with other
624  *    than the offered buttons. Usual behaviour in such case is to treat
625  *    it as a temporary reject.
626  *
627  * Since: 3.8
628  **/
629 ETrustPromptResponse
630 e_backend_trust_prompt_finish (EBackend *backend,
631                                GAsyncResult *result,
632                                GError **error)
633 {
634         EUserPrompter *prompter;
635         gint response;
636
637         g_return_val_if_fail (
638                 E_IS_BACKEND (backend), E_TRUST_PROMPT_RESPONSE_UNKNOWN);
639
640         prompter = e_backend_get_user_prompter (backend);
641         g_return_val_if_fail (
642                 prompter != NULL, E_TRUST_PROMPT_RESPONSE_UNKNOWN);
643
644         response = e_user_prompter_extension_prompt_finish (
645                 prompter, result, NULL, error);
646
647         if (response == 0)
648                 return E_TRUST_PROMPT_RESPONSE_REJECT;
649         if (response == 1)
650                 return E_TRUST_PROMPT_RESPONSE_ACCEPT;
651         if (response == 2)
652                 return E_TRUST_PROMPT_RESPONSE_ACCEPT_TEMPORARILY;
653         if (response == -1)
654                 return E_TRUST_PROMPT_RESPONSE_REJECT_TEMPORARILY;
655
656         return E_TRUST_PROMPT_RESPONSE_UNKNOWN;
657 }
658
659 /**
660  * e_backend_get_destination_address:
661  * @backend: an #EBackend instance
662  * @host: (out): destination server host name
663  * @port: (out): destination server port
664  *
665  * Provides destination server host name and port to which
666  * the backend connects. This is used to determine required
667  * connection point for e_backend_destination_is_reachable().
668  * The @host is a newly allocated string, which will be freed
669  * with g_free(). When @backend sets both @host and @port, then
670  * it should return %TRUE, indicating it's a remote backend.
671  * Default implementation returns %FALSE, which is treated
672  * like the backend is local, no checking for server reachability
673  * is possible.
674  *
675  * Returns: %TRUE, when it's a remote backend and provides both
676  *   @host and @port; %FALSE otherwise.
677  *
678  * Since: 3.8
679  **/
680 gboolean
681 e_backend_get_destination_address (EBackend *backend,
682                                    gchar **host,
683                                    guint16 *port)
684 {
685         EBackendClass *klass;
686
687         g_return_val_if_fail (E_IS_BACKEND (backend), FALSE);
688         g_return_val_if_fail (host != NULL, FALSE);
689         g_return_val_if_fail (port != NULL, FALSE);
690
691         klass = E_BACKEND_GET_CLASS (backend);
692         g_return_val_if_fail (klass->get_destination_address != NULL, FALSE);
693
694         return klass->get_destination_address (backend, host, port);
695 }
696
697 /**
698  * e_backend_is_destination_reachable:
699  * @backend: an #EBackend instance
700  * @cancellable: a #GCancellable instance, or %NULL
701  * @error: a #GError for errors, or %NULL
702  *
703  * Checks whether the @backend<!-- -->'s destination server, as returned
704  * by e_backend_get_destination_address(), is reachable.
705  * If the e_backend_get_destination_address() returns %FALSE, this function
706  * returns %TRUE, meaning the destination is always reachable.
707  * This uses #GNetworkMonitor<!-- -->'s g_network_monitor_can_reach()
708  * for reachability tests.
709  *
710  * Returns: %TRUE, when destination server address is reachable or
711  *    the backend doesn't provide destination address; %FALSE if
712  *    the backend destination server cannot be reached currently.
713  *
714  * Since: 3.8
715  **/
716 gboolean
717 e_backend_is_destination_reachable (EBackend *backend,
718                                     GCancellable *cancellable,
719                                     GError **error)
720 {
721         gboolean reachable = TRUE;
722         gchar *host = NULL;
723         guint16 port = 0;
724
725         g_return_val_if_fail (E_IS_BACKEND (backend), FALSE);
726
727         if (e_backend_get_destination_address (backend, &host, &port)) {
728                 g_warn_if_fail (host != NULL);
729
730                 if (host) {
731                         GNetworkMonitor *network_monitor;
732                         GSocketConnectable *connectable;
733
734                         network_monitor = g_network_monitor_get_default ();
735
736                         connectable = g_network_address_new (host, port);
737                         if (connectable) {
738                                 reachable = g_network_monitor_can_reach (network_monitor, connectable, cancellable, error);
739                                 g_object_unref (connectable);
740                         } else {
741                                 reachable = FALSE;
742                         }
743                 }
744         }
745
746         g_free (host);
747
748         return reachable;
749 }