blob: 27a027234c364c304b7662562c908f3179a14a57 [file] [log] [blame]
Harald Weltef5a0fa32019-03-03 15:44:18 +01001#include <stdint.h>
2#include <string.h>
3#include <unistd.h>
4#include <pthread.h>
5
6#include <osmocom/core/linuxlist.h>
7#include <osmocom/core/select.h>
8#include <osmocom/core/fsm.h>
9#include <osmocom/core/logging.h>
10#include <osmocom/core/socket.h>
11#include <osmocom/gsm/protocol/ipaccess.h>
12#include <osmocom/abis/ipa.h>
13
14#include <osmocom/rspro/RsproPDU.h>
15
16#include "debug.h"
17#include "rspro_util.h"
18#include "rspro_server.h"
19
20#define S(x) (1 << (x))
21
22static void client_slot2rspro(ClientSlot_t *out, const struct client_slot *in)
23{
24 out->clientId = in->client_id;
25 out->slotNr = in->slot_nr;
26}
27
28static void bank_slot2rspro(BankSlot_t *out, const struct bank_slot *in)
29{
30 out->bankId = in->bank_id;
31 out->slotNr = in->slot_nr;
32}
33
34static RsproPDU_t *slotmap2CreateMappingReq(const struct slot_mapping *slotmap)
35{
36 ClientSlot_t clslot;
37 BankSlot_t bslot;
38
39 client_slot2rspro(&clslot, &slotmap->client);
40 bank_slot2rspro(&bslot, &slotmap->bank);
41
42 return rspro_gen_CreateMappingReq(&clslot, &bslot);
43}
44
45static RsproPDU_t *slotmap2RemoveMappingReq(const struct slot_mapping *slotmap)
46{
47 ClientSlot_t clslot;
48 BankSlot_t bslot;
49
50 client_slot2rspro(&clslot, &slotmap->client);
51 bank_slot2rspro(&bslot, &slotmap->bank);
52
53 return rspro_gen_RemoveMappingReq(&clslot, &bslot);
54}
55
56
57static void client_conn_send(struct rspro_client_conn *conn, RsproPDU_t *pdu)
58{
59 struct msgb *msg_tx = rspro_enc_msg(pdu);
60 if (!msg_tx) {
61 ASN_STRUCT_FREE(asn_DEF_RsproPDU, pdu);
62 return;
63 }
64 ipa_prepend_header_ext(msg_tx, IPAC_PROTO_EXT_RSPRO);
65 ipa_msg_push_header(msg_tx, IPAC_PROTO_OSMO);
66 ipa_server_conn_send(conn->peer, msg_tx);
67}
68
69
70/***********************************************************************
71 * per-client connection FSM
72 ***********************************************************************/
73
74static void rspro_client_conn_destroy(struct rspro_client_conn *conn);
75
76enum remsim_server_client_fsm_state {
77 CLNTC_ST_INIT,
78 CLNTC_ST_ESTABLISHED,
79 CLNTC_ST_CONNECTED,
80};
81
82enum remsim_server_client_event {
83 CLNTC_E_TCP_UP,
84 CLNTC_E_CLIENT_CONN, /* Connect{Client,Bank}Req received */
85 CLNTC_E_BANK_CONN,
86 CLNTC_E_TCP_DOWN,
87 CLNTC_E_CREATE_MAP_RES, /* CreateMappingRes received */
88 CLNTC_E_REMOVE_MAP_RES, /* RemoveMappingRes received */
89 CLNTC_E_PUSH, /* drain maps_new or maps_delreq */
90};
91
92static const struct value_string server_client_event_names[] = {
93 OSMO_VALUE_STRING(CLNTC_E_TCP_UP),
94 OSMO_VALUE_STRING(CLNTC_E_CLIENT_CONN),
95 OSMO_VALUE_STRING(CLNTC_E_BANK_CONN),
96 OSMO_VALUE_STRING(CLNTC_E_TCP_DOWN),
97 OSMO_VALUE_STRING(CLNTC_E_CREATE_MAP_RES),
98 OSMO_VALUE_STRING(CLNTC_E_REMOVE_MAP_RES),
99 OSMO_VALUE_STRING(CLNTC_E_PUSH),
100 { 0, NULL }
101};
102
103static void clnt_st_init(struct osmo_fsm_inst *fi, uint32_t event, void *data)
104{
105 switch (event) {
106 case CLNTC_E_TCP_UP:
107 osmo_fsm_inst_state_chg(fi, CLNTC_ST_ESTABLISHED, 10, 1);
108 break;
109 default:
110 OSMO_ASSERT(0);
111 }
112}
113
114static void clnt_st_established(struct osmo_fsm_inst *fi, uint32_t event, void *data)
115{
116 struct rspro_client_conn *conn = fi->priv;
117 const RsproPDU_t *pdu = data;
118 const ConnectClientReq_t *cclreq = NULL;
119 const ConnectBankReq_t *cbreq = NULL;
120 RsproPDU_t *resp = NULL;
121
122 switch (event) {
123 case CLNTC_E_CLIENT_CONN:
124 cclreq = &pdu->msg.choice.connectClientReq;
125 /* save the [remote] component identity in 'conn' */
126 rspro_comp_id_retrieve(&conn->comp_id, &cclreq->identity);
127 if (conn->comp_id.type != ComponentType_remsimClient) {
128 LOGPFSM(fi, "ConnectClientReq from identity != Client ?!?\n");
129 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
130 }
131 /* FIXME: determine client ID */
132 //osmo_fsm_inst_update_id_f(fi, "C%u:%u", conn->bank.bank_id);
133
134 /* reparent us from srv->connections to srv->clients */
135 pthread_rwlock_wrlock(&conn->srv->rwlock);
136 llist_del(&conn->list);
137 llist_add_tail(&conn->list, &conn->srv->clients);
138 pthread_rwlock_unlock(&conn->srv->rwlock);
139
140 osmo_fsm_inst_state_chg(fi, CLNTC_ST_CONNECTED, 0, 0);
141
142 resp = rspro_gen_ConnectClientRes(&conn->srv->comp_id, ResultCode_ok);
143 client_conn_send(conn, resp);
144 break;
145 case CLNTC_E_BANK_CONN:
146 cbreq = &pdu->msg.choice.connectBankReq;
147 /* save the [remote] component identity in 'conn' */
148 rspro_comp_id_retrieve(&conn->comp_id, &cbreq->identity);
149 if (conn->comp_id.type != ComponentType_remsimBankd) {
150 LOGPFSM(fi, "ConnectBankReq from identity != Bank ?!?\n");
151 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_ERROR, NULL);
152 }
153 conn->bank.bank_id = cbreq->bankId;
154 conn->bank.num_slots = cbreq->numberOfSlots;
155 osmo_fsm_inst_update_id_f(fi, "B%u", conn->bank.bank_id);
156
157 /* reparent us from srv->connections to srv->banks */
158 pthread_rwlock_wrlock(&conn->srv->rwlock);
159 llist_del(&conn->list);
160 llist_add_tail(&conn->list, &conn->srv->banks);
161 pthread_rwlock_unlock(&conn->srv->rwlock);
162
163 /* send response to bank first */
164 resp = rspro_gen_ConnectBankRes(&conn->srv->comp_id, ResultCode_ok);
165 client_conn_send(conn, resp);
166
167 /* the state change will associate any pre-existing slotmaps */
168 osmo_fsm_inst_state_chg(fi, CLNTC_ST_CONNECTED, 0, 0);
169
170 osmo_fsm_inst_dispatch(fi, CLNTC_E_PUSH, NULL);
171 break;
172 default:
173 OSMO_ASSERT(0);
174 }
175}
176
177
178static void clnt_st_connected_cl_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
179{
180#if 0
181 struct rspro_client_conn *conn = fi->priv;
182 ClientSlot_t clslot;
183 RsproPDU_t *pdu;
184
185 /* send configuration to this new client */
186 client_slot2rspro(&clslot, FIXME);
187 pdu = rspro_gen_ConfigClientReq(&clslot, bankd_ip, bankd_port);
188 client_conn_send(conn, pdu);
189#endif
190}
191
192static void clnt_st_connected_bk_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
193{
194 struct rspro_client_conn *conn = fi->priv;
195 struct slotmaps *slotmaps = conn->srv->slotmaps;
196 struct slot_mapping *map;
197
198 LOGPFSM(fi, "Associating pre-existing slotmaps (if any)\n");
199 /* Link all known mappings to this new bank */
200 slotmaps_wrlock(slotmaps);
201 llist_for_each_entry(map, &slotmaps->mappings, list) {
202 if (map->bank.bank_id == conn->bank.bank_id)
203 _slotmap_state_change(map, SLMAP_S_NEW, &conn->bank.maps_new);
204 }
205 slotmaps_unlock(slotmaps);
206}
207
208static void clnt_st_connected_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
209{
210 struct rspro_client_conn *conn = fi->priv;
211 switch (conn->comp_id.type) {
212 case ComponentType_remsimClient:
213 clnt_st_connected_cl_onenter(fi, prev_state);
214 break;
215 case ComponentType_remsimBankd:
216 clnt_st_connected_bk_onenter(fi, prev_state);
217 break;
218 default:
219 OSMO_ASSERT(0);
220 }
221}
222
223static void clnt_st_connected(struct osmo_fsm_inst *fi, uint32_t event, void *data)
224{
225 struct rspro_client_conn *conn = fi->priv;
226 struct slotmaps *slotmaps = conn->srv->slotmaps;
227 const struct RsproPDU_t *rx = NULL;
228 struct slot_mapping *map, *map2;
229
230 switch (event) {
231 case CLNTC_E_CREATE_MAP_RES:
232 rx = data;
233 slotmaps_wrlock(slotmaps);
234 /* FIXME: resolve map by pdu->tag */
235 /* as hack use first element of conn->maps_unack */
236 map = llist_first_entry(&conn->bank.maps_unack, struct slot_mapping, bank_list);
237 if (!map) {
238 slotmaps_unlock(slotmaps);
239 LOGPFSM(fi, "CreateMapRes but no unacknowledged map");
240 break;
241 }
242 _slotmap_state_change(map, SLMAP_S_ACTIVE, &conn->bank.maps_active);
243 slotmaps_unlock(slotmaps);
244 break;
245 case CLNTC_E_REMOVE_MAP_RES:
246 rx = data;
247 slotmaps_wrlock(slotmaps);
248 /* FIXME: resolve map by pdu->tag */
249 /* as hack use first element of conn->maps_deleting */
250 map = llist_first_entry(&conn->bank.maps_deleting, struct slot_mapping, bank_list);
251 if (!map) {
252 slotmaps_unlock(slotmaps);
253 LOGPFSM(fi, "RemoveMapRes but no unacknowledged map");
254 break;
255 }
256 slotmaps_unlock(slotmaps);
257 /* slotmap_del() will remove it from both global and bank list */
258 slotmap_del(map->maps, map);
259 break;
260 case CLNTC_E_PUSH:
261 slotmaps_wrlock(slotmaps);
262 /* send any pending create requests */
263 llist_for_each_entry_safe(map, map2, &conn->bank.maps_new, bank_list) {
264 RsproPDU_t *pdu = slotmap2CreateMappingReq(map);
265 client_conn_send(conn, pdu);
266 _slotmap_state_change(map, SLMAP_S_UNACKNOWLEDGED, &conn->bank.maps_unack);
267 }
268 /* send any pending delete requests */
269 llist_for_each_entry_safe(map, map2, &conn->bank.maps_delreq, bank_list) {
270 RsproPDU_t *pdu = slotmap2RemoveMappingReq(map);
271 client_conn_send(conn, pdu);
272 _slotmap_state_change(map, SLMAP_S_DELETING, &conn->bank.maps_deleting);
273 }
274 slotmaps_unlock(slotmaps);
275 break;
276 default:
277 OSMO_ASSERT(0);
278 }
279}
280
281static void clnt_allstate_action(struct osmo_fsm_inst *fi, uint32_t event, void *data)
282{
283 struct rspro_client_conn *conn = fi->priv;
284
285 switch (event) {
286 case CLNTC_E_TCP_DOWN:
287 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
288 break;
289 default:
290 OSMO_ASSERT(0);
291 }
292}
293
294static int server_client_fsm_timer_cb(struct osmo_fsm_inst *fi)
295{
296 struct rspro_client_conn *conn = fi->priv;
297
298 switch (fi->T) {
299 case 1:
300 /* No ClientConnectReq received:disconnect */
301 return 1; /* ask core to terminate FSM */
302 default:
303 OSMO_ASSERT(0);
304 }
305 return 0;
306}
307
308static void server_client_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
309{
310 struct rspro_client_conn *conn = fi->priv;
311 /* this call will destroy the IPA connection, which will in turn call closed_cb()
312 * which will try to deliver a E_TCP_DOWN event. Clear conn->fi to avoid that loop */
313 conn->fi = NULL;
314 rspro_client_conn_destroy(conn);
315}
316
317static const struct osmo_fsm_state server_client_fsm_states[] = {
318 [CLNTC_ST_INIT] = {
319 .name = "INIT",
320 .in_event_mask = S(CLNTC_E_TCP_UP),
321 .out_state_mask = S(CLNTC_ST_ESTABLISHED),
322 .action = clnt_st_init,
323 },
324 [CLNTC_ST_ESTABLISHED] = {
325 .name = "ESTABLISHED",
326 .in_event_mask = S(CLNTC_E_CLIENT_CONN) | S(CLNTC_E_BANK_CONN),
327 .out_state_mask = S(CLNTC_ST_CONNECTED),
328 .action = clnt_st_established,
329 },
330 [CLNTC_ST_CONNECTED] = {
331 .name = "CONNECTED",
332 .in_event_mask = S(CLNTC_E_CREATE_MAP_RES) | S(CLNTC_E_REMOVE_MAP_RES) |
333 S(CLNTC_E_PUSH),
334 .action = clnt_st_connected,
335 .onenter = clnt_st_connected_onenter,
336 },
337};
338
339static struct osmo_fsm remsim_server_client_fsm = {
340 .name = "SERVER_CONN",
341 .states = server_client_fsm_states,
342 .num_states = ARRAY_SIZE(server_client_fsm_states),
343 .allstate_event_mask = S(CLNTC_E_TCP_DOWN),
344 .allstate_action = clnt_allstate_action,
345 .cleanup = server_client_cleanup,
346 .timer_cb = server_client_fsm_timer_cb,
347 .log_subsys = DMAIN,
348 .event_names = server_client_event_names,
349};
350
351struct osmo_fsm_inst *server_client_fsm_alloc(void *ctx, struct rspro_client_conn *conn)
352{
353 //const char *id = osmo_sock_get_name2(conn->peer->ofd.fd);
354 return osmo_fsm_inst_alloc(&remsim_server_client_fsm, ctx, conn, LOGL_DEBUG, NULL);
355}
356
357
358static __attribute__((constructor)) void on_dso_load(void)
359{
360 osmo_fsm_register(&remsim_server_client_fsm);
361}
362
363
364/***********************************************************************
365 * IPA RSPRO Server
366 ***********************************************************************/
367
368struct rspro_client_conn *_bankd_conn_by_id(struct rspro_server *srv, uint16_t bank_id)
369{
370 struct rspro_client_conn *conn;
371 llist_for_each_entry(conn, &srv->banks, list) {
372 if (conn->bank.bank_id == bank_id)
373 return conn;
374 }
375 return NULL;
376}
377struct rspro_client_conn *bankd_conn_by_id(struct rspro_server *srv, uint16_t bank_id)
378{
379 struct rspro_client_conn *conn;
380 pthread_rwlock_rdlock(&srv->rwlock);
381 conn = _bankd_conn_by_id(srv, bank_id);
382 pthread_rwlock_unlock(&srv->rwlock);
383 return conn;
384}
385
386static int handle_rx_rspro(struct rspro_client_conn *conn, const RsproPDU_t *pdu)
387{
388 switch (pdu->msg.present) {
389 case RsproPDUchoice_PR_connectClientReq:
390 osmo_fsm_inst_dispatch(conn->fi, CLNTC_E_CLIENT_CONN, (void *)pdu);
391 break;
392 case RsproPDUchoice_PR_connectBankReq:
393 osmo_fsm_inst_dispatch(conn->fi, CLNTC_E_BANK_CONN, (void *)pdu);
394 break;
395 case RsproPDUchoice_PR_createMappingRes:
396 osmo_fsm_inst_dispatch(conn->fi, CLNTC_E_CREATE_MAP_RES, (void *)pdu);
397 break;
398 case RsproPDUchoice_PR_removeMappingRes:
399 osmo_fsm_inst_dispatch(conn->fi, CLNTC_E_REMOVE_MAP_RES, (void *)pdu);
400 break;
401 default:
402 LOGPFSML(conn->fi, LOGL_ERROR, "Received unknown/unimplemented RSPRO msg_type %d\n",
403 pdu->msg.present);
404 return -1;
405 }
406 return 0;
407}
408
409/* data was received from one of the client connections to the RSPRO socket */
410static int sock_read_cb(struct ipa_server_conn *peer, struct msgb *msg)
411{
412 struct ipaccess_head *hh = (struct ipaccess_head *) msg->data;
413 struct ipaccess_head_ext *he = (struct ipaccess_head_ext *) msgb_l2(msg);
414 struct rspro_client_conn *conn = peer->data;
415 RsproPDU_t *pdu;
416 int rc;
417
418 if (msgb_length(msg) < sizeof(*hh))
419 goto invalid;
420 msg->l2h = &hh->data[0];
421 if (hh->proto != IPAC_PROTO_OSMO)
422 goto invalid;
423 if (!he || msgb_l2len(msg)< sizeof(*he))
424 goto invalid;
425 msg->l2h = &he->data[0];
426
427 if (he->proto!= IPAC_PROTO_EXT_RSPRO)
428 goto invalid;
429
430 pdu = rspro_dec_msg(msg);
431 if (!pdu)
432 goto invalid;
433
434 rc = handle_rx_rspro(conn, pdu);
435 ASN_STRUCT_FREE(asn_DEF_RsproPDU, pdu);
436 return rc;
437
438invalid:
439 msgb_free(msg);
440 return -1;
441}
442
443static int sock_closed_cb(struct ipa_server_conn *peer)
444{
445 struct rspro_client_conn *conn = peer->data;
446 if (conn->fi)
447 osmo_fsm_inst_dispatch(conn->fi, CLNTC_E_TCP_DOWN, NULL);
448 /* FIXME: who cleans up conn? */
449 /* ipa server code relases 'peer' just after this */
450 return 0;
451}
452
453/* a new TCP connection was accepted on the RSPRO server socket */
454static int accept_cb(struct ipa_server_link *link, int fd)
455{
456 struct rspro_server *srv = link->data;
457 struct rspro_client_conn *conn;
458
459 conn = talloc_zero(srv, struct rspro_client_conn);
460 OSMO_ASSERT(conn);
461
462 conn->srv = srv;
463 /* don't allocate peer under 'conn', as it must survive 'conn' during teardown */
464 conn->peer = ipa_server_conn_create(link, link, fd, sock_read_cb, sock_closed_cb, conn);
465 if (!conn->peer)
466 goto out_err;
467
468 /* don't allocate 'fi' as slave from 'conn', as 'fi' needs to survive 'conn' during
469 * teardown */
470 conn->fi = server_client_fsm_alloc(srv, conn);
471 if (!conn->fi)
472 goto out_err_conn;
473
474 INIT_LLIST_HEAD(&conn->bank.maps_new);
475 INIT_LLIST_HEAD(&conn->bank.maps_unack);
476 INIT_LLIST_HEAD(&conn->bank.maps_active);
477 INIT_LLIST_HEAD(&conn->bank.maps_delreq);
478 INIT_LLIST_HEAD(&conn->bank.maps_deleting);
479
480 pthread_rwlock_wrlock(&conn->srv->rwlock);
481 llist_add_tail(&conn->list, &srv->connections);
482 pthread_rwlock_unlock(&conn->srv->rwlock);
483
484 osmo_fsm_inst_dispatch(conn->fi, CLNTC_E_TCP_UP, NULL);
485 return 0;
486
487out_err_conn:
488 ipa_server_conn_destroy(conn->peer);
489 /* the above will free 'conn' down the chain */
490 return -1;
491out_err:
492 talloc_free(conn);
493 return -1;
494}
495
496/* call-back if we were triggered by a rest_api thread */
497int event_fd_cb(struct osmo_fd *ofd, unsigned int what)
498{
499 struct rspro_server *srv = ofd->data;
500 struct rspro_client_conn *conn;
501 bool non_empty_new, non_empty_del;
502 uint64_t value;
503 int rc;
504
505 /* read from the socket to "confirm" the event and make it non-readable again */
506 rc = read(ofd->fd, &value, 8);
507 if (rc < 8) {
508 fprintf(stderr, "Error reading eventfd: %d\n", rc);
509 return rc;
510 }
511
512 printf("rspro_server: Event FD arrived, checking for any pending work\n");
513
514 pthread_rwlock_rdlock(&srv->rwlock);
515 llist_for_each_entry(conn, &srv->banks, list) {
516 slotmaps_rdlock(srv->slotmaps);
517 non_empty_new = llist_empty(&conn->bank.maps_new);
518 non_empty_del = llist_empty(&conn->bank.maps_delreq);
519 slotmaps_unlock(srv->slotmaps);
520
521 /* trigger FSM to send any pending new/deleted maps */
522 if (non_empty_new || non_empty_del)
523 osmo_fsm_inst_dispatch(conn->fi, CLNTC_E_PUSH, NULL);
524 }
525 pthread_rwlock_unlock(&srv->rwlock);
526
527 return 0;
528}
529
530/* unlink all slotmaps from any of the lists of this conn->bank.maps_* */
531static void _unlink_all_slotmaps(struct rspro_client_conn *conn)
532{
533 struct slot_mapping *smap, *smap2;
534
535 llist_for_each_entry_safe(smap, smap2, &conn->bank.maps_new, bank_list) {
536 /* unlink from list and keep in state NEW */
537 _slotmap_state_change(smap, SLMAP_S_NEW, NULL);
538 }
539 llist_for_each_entry_safe(smap, smap2, &conn->bank.maps_unack, bank_list) {
540 /* unlink from list and change to state NEW */
541 _slotmap_state_change(smap, SLMAP_S_NEW, NULL);
542 }
543 llist_for_each_entry_safe(smap, smap2, &conn->bank.maps_active, bank_list) {
544 /* unlink from list and change to state NEW */
545 _slotmap_state_change(smap, SLMAP_S_NEW, NULL);
546 }
547 llist_for_each_entry_safe(smap, smap2, &conn->bank.maps_delreq, bank_list) {
548 /* unlink from list and delete */
549 _slotmap_del(smap->maps, smap);
550 }
551 llist_for_each_entry_safe(smap, smap2, &conn->bank.maps_deleting, bank_list) {
552 /* unlink from list and delete */
553 _slotmap_del(smap->maps, smap);
554 }
555}
556
557/* only to be used by the FSM cleanup. */
558static void rspro_client_conn_destroy(struct rspro_client_conn *conn)
559{
560 /* this will internally call closed_cb() which will dispatch a TCP_DOWN event */
561 ipa_server_conn_destroy(conn->peer);
562 conn->peer = NULL;
563
564 /* ensure all slotmaps are unlinked + returned to NEW or deleted */
565 slotmaps_wrlock(conn->srv->slotmaps);
566 _unlink_all_slotmaps(conn);
567 slotmaps_unlock(conn->srv->slotmaps);
568
569 pthread_rwlock_wrlock(&conn->srv->rwlock);
570 llist_del(&conn->list);
571 pthread_rwlock_unlock(&conn->srv->rwlock);
572
573 talloc_free(conn);
574}
575
576
577struct rspro_server *rspro_server_create(void *ctx, const char *host, uint16_t port)
578
579{
580 struct rspro_server *srv = talloc_zero(ctx, struct rspro_server);
581 OSMO_ASSERT(srv);
582
583 pthread_rwlock_init(&srv->rwlock, NULL);
584 pthread_rwlock_wrlock(&srv->rwlock);
585 INIT_LLIST_HEAD(&srv->connections);
586 INIT_LLIST_HEAD(&srv->clients);
587 INIT_LLIST_HEAD(&srv->banks);
588 pthread_rwlock_unlock(&srv->rwlock);
589
590 srv->link = ipa_server_link_create(ctx, NULL, host, port, accept_cb, srv);
591 ipa_server_link_open(srv->link);
592
593 return srv;
594}
595
596void rspro_server_destroy(struct rspro_server *srv)
597{
598 /* FIXME: clear all lists */
599
600 ipa_server_link_destroy(srv->link);
601 srv->link = NULL;
602 talloc_free(srv);
603}