diff options
-rw-r--r-- | examples/Makefile.am | 20 | ||||
-rw-r--r-- | examples/lapd-over-datagram-network.c (renamed from examples/lapd-over-stream-server.c) | 80 | ||||
-rw-r--r-- | examples/lapd-over-datagram-user.c (renamed from examples/lapd-over-stream-client.c) | 45 | ||||
-rw-r--r-- | include/osmocom/netif/Makefile.am | 3 | ||||
-rw-r--r-- | include/osmocom/netif/datagram.h | 47 | ||||
-rw-r--r-- | src/Makefile.am | 3 | ||||
-rw-r--r-- | src/datagram.c | 354 |
7 files changed, 469 insertions, 83 deletions
diff --git a/examples/Makefile.am b/examples/Makefile.am index 981128b..c1759e2 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -2,15 +2,15 @@ INCLUDES = $(all_includes) -I$(top_srcdir)/include AM_CFLAGS=-Wall -g $(LIBOSMOCORE_CFLAGS) $(LIBOSMOGSM_CFLAGS) $(LIBOSMOABIS_CFLAGS) $(COVERAGE_CFLAGS) AM_LDFLAGS = $(COVERAGE_LDFLAGS) -noinst_PROGRAMS = lapd-over-stream-client \ - lapd-over-stream-server +noinst_PROGRAMS = lapd-over-datagram-user \ + lapd-over-datagram-network -lapd_over_stream_client_SOURCES = lapd-over-stream-client.c -lapd_over_stream_client_LDADD = $(top_builddir)/src/libosmonetif.la \ - $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) \ - $(LIBOSMOABIS_LIBS) +lapd_over_datagram_user_SOURCES = lapd-over-datagram-user.c +lapd_over_datagram_user_LDADD = $(top_builddir)/src/libosmonetif.la \ + $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOABIS_LIBS) -lapd_over_stream_server_SOURCES = lapd-over-stream-server.c -lapd_over_stream_server_LDADD = $(top_builddir)/src/libosmonetif.la \ - $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) \ - $(LIBOSMOABIS_LIBS) +lapd_over_datagram_network_SOURCES = lapd-over-datagram-network.c +lapd_over_datagram_network_LDADD = $(top_builddir)/src/libosmonetif.la \ + $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOABIS_LIBS) diff --git a/examples/lapd-over-stream-server.c b/examples/lapd-over-datagram-network.c index 8d06f62..a0c8ac9 100644 --- a/examples/lapd-over-stream-server.c +++ b/examples/lapd-over-datagram-network.c @@ -1,4 +1,4 @@ -/* LAPD over stream (network-mode/server) example. */ +/* LAPD over datagram network-mode example. */ #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -8,10 +8,11 @@ #include <osmocom/core/msgb.h> #include <osmocom/core/logging.h> #include <osmocom/core/application.h> +#include <osmocom/core/select.h> #include <osmocom/abis/lapd.h> -#include <osmocom/netif/stream.h> +#include <osmocom/netif/datagram.h> static void *tall_test; @@ -32,7 +33,7 @@ const struct log_info lapd_test_log_info = { .num_cat = ARRAY_SIZE(lapd_test_cat), }; -static struct stream_server_link *server; +static struct datagram_conn *conn; static struct lapd_instance *lapd; static int sapi = 63, tei = 0; @@ -43,11 +44,11 @@ void sighandler(int foo) exit(EXIT_SUCCESS); } -int read_cb(struct stream_server_conn *conn, struct msgb *msg) +int read_cb(struct datagram_server_conn *conn, struct msgb *msg) { int error; - LOGP(DLINP, LOGL_NOTICE, "received message from stream\n"); + LOGP(DLINP, LOGL_NOTICE, "received message from datagram\n"); if (lapd_receive(lapd, msg, &error) < 0) { LOGP(DLINP, LOGL_ERROR, "lapd_receive returned error!\n"); @@ -58,10 +59,10 @@ int read_cb(struct stream_server_conn *conn, struct msgb *msg) void lapd_tx_cb(struct msgb *msg, void *cbdata) { - struct stream_server_conn *conn = cbdata; + struct datagram_conn *conn = cbdata; - LOGP(DLINP, LOGL_NOTICE, "sending message over stream\n"); - stream_server_conn_send(conn, msg); + LOGP(DLINP, LOGL_NOTICE, "sending message over datagram\n"); + datagram_conn_send(conn, msg); } void lapd_rx_cb(struct osmo_dlsap_prim *dp, uint8_t tei, uint8_t sapi, @@ -95,37 +96,6 @@ void lapd_rx_cb(struct osmo_dlsap_prim *dp, uint8_t tei, uint8_t sapi, } } -static int accept_cb(struct stream_server_link *server, int fd) -{ - struct stream_server_conn *conn; - int teip; - - conn = stream_server_conn_create(tall_test, server, fd, read_cb, - NULL, NULL); - if (conn == NULL) { - LOGP(DLINP, LOGL_ERROR, "error in lapd_receive\n"); - return -1; - } - - /* - * initialize LAPD stuff. - */ - - lapd = lapd_instance_alloc(1, lapd_tx_cb, conn, lapd_rx_cb, conn, - &lapd_profile_sat); - if (lapd == NULL) { - LOGP(DLINP, LOGL_ERROR, "cannot allocate instance\n"); - exit(EXIT_FAILURE); - } - - teip = lapd_tei_alloc(lapd, tei); - if (teip == 0) { - LOGP(DLINP, LOGL_ERROR, "cannot assign TEI\n"); - exit(EXIT_FAILURE); - } - return 0; -} - static int kbd_cb(struct osmo_fd *fd, unsigned int what) { char buf[1024]; @@ -154,6 +124,7 @@ static int kbd_cb(struct osmo_fd *fd, unsigned int what) int main(void) { struct osmo_fd *kbd_ofd; + int teip; tall_test = talloc_named_const(NULL, 1, "lapd_test"); @@ -161,19 +132,34 @@ int main(void) log_set_log_level(osmo_stderr_target, 1); /* - * initialize stream server. + * initialize datagram server. */ - server = stream_server_link_create(tall_test); - if (server == NULL) { + conn = datagram_conn_create(tall_test); + if (conn == NULL) { fprintf(stderr, "cannot create client\n"); exit(EXIT_FAILURE); } - stream_server_link_set_addr(server, "127.0.0.1"); - stream_server_link_set_port(server, 10000); - stream_server_link_set_accept_cb(server, accept_cb); + datagram_conn_set_local_addr(conn, "127.0.0.1"); + datagram_conn_set_local_port(conn, 10001); + datagram_conn_set_remote_addr(conn, "127.0.0.1"); + datagram_conn_set_remote_port(conn, 10000); + datagram_conn_set_read_cb(conn, read_cb); + + lapd = lapd_instance_alloc(1, lapd_tx_cb, conn, lapd_rx_cb, conn, + &lapd_profile_sat); + if (lapd == NULL) { + LOGP(DLINP, LOGL_ERROR, "cannot allocate instance\n"); + exit(EXIT_FAILURE); + } + + teip = lapd_tei_alloc(lapd, tei); + if (teip == 0) { + LOGP(DLINP, LOGL_ERROR, "cannot assign TEI\n"); + exit(EXIT_FAILURE); + } - if (stream_server_link_open(server) < 0) { + if (datagram_conn_open(conn) < 0) { fprintf(stderr, "cannot open client\n"); exit(EXIT_FAILURE); } @@ -185,7 +171,7 @@ int main(void) } kbd_ofd->fd = STDIN_FILENO; kbd_ofd->when = BSC_FD_READ; - kbd_ofd->data = server; + kbd_ofd->data = conn; kbd_ofd->cb = kbd_cb; osmo_fd_register(kbd_ofd); diff --git a/examples/lapd-over-stream-client.c b/examples/lapd-over-datagram-user.c index 1525b7c..2e6e02e 100644 --- a/examples/lapd-over-stream-client.c +++ b/examples/lapd-over-datagram-user.c @@ -1,4 +1,4 @@ -/* LAPD over stream (user-mode/client) example. */ +/* LAPD over datagram user-mode example. */ #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -8,10 +8,11 @@ #include <osmocom/core/msgb.h> #include <osmocom/core/logging.h> #include <osmocom/core/application.h> +#include <osmocom/core/select.h> #include <osmocom/abis/lapd.h> -#include <osmocom/netif/stream.h> +#include <osmocom/netif/datagram.h> #define DLAPDTEST 0 @@ -30,7 +31,7 @@ const struct log_info lapd_test_log_info = { .num_cat = ARRAY_SIZE(lapd_test_cat), }; -static struct stream_client_conn *conn; +static struct datagram_conn *conn; static struct lapd_instance *lapd; static int sapi = 63, tei = 0; @@ -42,21 +43,11 @@ void sighandler(int foo) exit(EXIT_SUCCESS); } -static int connect_cb(struct stream_client_conn *conn) -{ - LOGP(DLINP, LOGL_NOTICE, "connected\n"); - if (lapd_sap_start(lapd, tei, sapi) < 0) { - LOGP(DLINP, LOGL_ERROR, "cannot start user-side LAPD\n"); - exit(EXIT_FAILURE); - } - return 0; -} - -static int read_cb(struct stream_client_conn *conn, struct msgb *msg) +static int read_cb(struct datagram_server_conn *conn, struct msgb *msg) { int error; - LOGP(DLINP, LOGL_NOTICE, "received message from stream\n"); + LOGP(DLINP, LOGL_NOTICE, "received message from datagram\n"); if (lapd_receive(lapd, msg, &error) < 0) { LOGP(DLINP, LOGL_ERROR, "lapd_receive returned error!\n"); @@ -69,8 +60,8 @@ static void *tall_test; void lapd_tx_cb(struct msgb *msg, void *cbdata) { - LOGP(DLINP, LOGL_NOTICE, "sending message over stream\n"); - stream_client_conn_send(conn, msg); + LOGP(DLINP, LOGL_NOTICE, "sending message over datagram\n"); + datagram_conn_send(conn, msg); } void lapd_rx_cb(struct osmo_dlsap_prim *dp, uint8_t tei, uint8_t sapi, @@ -150,24 +141,30 @@ int main(void) } /* - * initialize stream client. + * initialize datagram socket. */ - conn = stream_client_conn_create(tall_test); + conn = datagram_conn_create(tall_test); if (conn == NULL) { fprintf(stderr, "cannot create client\n"); exit(EXIT_FAILURE); } - stream_client_conn_set_addr(conn, "127.0.0.1"); - stream_client_conn_set_port(conn, 10000); - stream_client_conn_set_connect_cb(conn, connect_cb); - stream_client_conn_set_read_cb(conn, read_cb); + datagram_conn_set_local_addr(conn, "127.0.0.1"); + datagram_conn_set_local_port(conn, 10000); + datagram_conn_set_remote_addr(conn, "127.0.0.1"); + datagram_conn_set_remote_port(conn, 10001); + datagram_conn_set_read_cb(conn, read_cb); - if (stream_client_conn_open(conn) < 0) { + if (datagram_conn_open(conn) < 0) { fprintf(stderr, "cannot open client\n"); exit(EXIT_FAILURE); } + if (lapd_sap_start(lapd, tei, sapi) < 0) { + LOGP(DLINP, LOGL_ERROR, "cannot start user-side LAPD\n"); + exit(EXIT_FAILURE); + } + kbd_ofd = talloc_zero(tall_test, struct osmo_fd); if (!kbd_ofd) { LOGP(DLAPDTEST, LOGL_ERROR, "OOM\n"); diff --git a/include/osmocom/netif/Makefile.am b/include/osmocom/netif/Makefile.am index 8b68362..afab204 100644 --- a/include/osmocom/netif/Makefile.am +++ b/include/osmocom/netif/Makefile.am @@ -1,3 +1,4 @@ -osmonetif_HEADERS = stream.h +osmonetif_HEADERS = datagram.h \ + stream.h osmonetifdir = $(includedir)/osmocom/netif diff --git a/include/osmocom/netif/datagram.h b/include/osmocom/netif/datagram.h new file mode 100644 index 0000000..abda79b --- /dev/null +++ b/include/osmocom/netif/datagram.h @@ -0,0 +1,47 @@ +#ifndef _OSMO_DGRAM_H_ +#define _OSMO_DGRAM_H_ + +struct datagram_client_conn; + +struct datagram_client_conn *datagram_client_conn_create(void *ctx); +void datagram_client_conn_destroy(struct datagram_client_conn *conn); + +void datagram_client_conn_set_addr(struct datagram_client_conn *conn, const char *addr); +void datagram_client_conn_set_port(struct datagram_client_conn *conn, uint16_t port); +void datagram_client_conn_set_data(struct datagram_client_conn *conn, void *data); + +int datagram_client_conn_open(struct datagram_client_conn *conn); +void datagram_client_conn_close(struct datagram_client_conn *conn); + +void datagram_client_conn_send(struct datagram_client_conn *conn, struct msgb *msg); + +struct datagram_server_conn; + +struct datagram_server_conn *datagram_server_conn_create(void *ctx); + +void datagram_server_conn_set_addr(struct datagram_server_conn *conn, const char *addr); +void datagram_server_conn_set_port(struct datagram_server_conn *conn, uint16_t port); +void datagram_server_conn_set_read_cb(struct datagram_server_conn *conn, int (*read_cb)(struct datagram_server_conn *conn, struct msgb *msg)); +void datagram_server_conn_destroy(struct datagram_server_conn *conn); + +int datagram_server_conn_open(struct datagram_server_conn *conn); +void datagram_server_conn_close(struct datagram_server_conn *conn); + +struct datagram_conn; + +struct datagram_conn *datagram_conn_create(void *ctx); +void datagram_conn_destroy(struct datagram_conn *conn); + +int datagram_conn_open(struct datagram_conn *conn); +void datagram_conn_close(struct datagram_conn *conn); + +void datagram_conn_set_local_addr(struct datagram_conn *conn, const char *addr); +void datagram_conn_set_remote_addr(struct datagram_conn *conn, const char *addr); +void datagram_conn_set_local_port(struct datagram_conn *conn, uint16_t port); +void datagram_conn_set_remote_port(struct datagram_conn *conn, uint16_t port); +void datagram_conn_set_read_cb(struct datagram_conn *conn, int (*read_cb)(struct datagram_server_conn *conn, struct msgb *msg)); +void datagram_conn_set_data(struct datagram_client_conn *conn, void *data); + +void datagram_conn_send(struct datagram_conn *conn, struct msgb *msg); + +#endif diff --git a/src/Makefile.am b/src/Makefile.am index 30e1e72..bf2d60a 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,4 +8,5 @@ AM_LDFLAGS = $(LIBOSMOCORE_LIBS) $(LIBOSMOGSM_LIBS) $(LIBOSMOVTY_LIBS) $(COVERAG lib_LTLIBRARIES = libosmonetif.la -libosmonetif_la_SOURCES = stream.c +libosmonetif_la_SOURCES = datagram.c \ + stream.c diff --git a/src/datagram.c b/src/datagram.c new file mode 100644 index 0000000..cd09025 --- /dev/null +++ b/src/datagram.c @@ -0,0 +1,354 @@ +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <time.h> +#include <sys/fcntl.h> +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <arpa/inet.h> + +#include <osmocom/core/linuxlist.h> +#include <osmocom/core/select.h> +#include <osmocom/gsm/tlv.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/logging.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/socket.h> + +#include <osmocom/netif/datagram.h> + +/* + * Client side. + */ + +struct datagram_client_conn { + struct osmo_fd ofd; + struct llist_head tx_queue; + const char *addr; + uint16_t port; + int (*write_cb)(struct datagram_client_conn *conn); + void *data; +}; + +void datagram_client_conn_close(struct datagram_client_conn *conn) +{ + osmo_fd_unregister(&conn->ofd); + close(conn->ofd.fd); +} + +static int datagram_client_write(struct datagram_client_conn *conn) +{ + struct msgb *msg; + struct llist_head *lh; + int ret; + + LOGP(DLINP, LOGL_DEBUG, "sending data\n"); + + if (llist_empty(&conn->tx_queue)) { + conn->ofd.when &= ~BSC_FD_WRITE; + return 0; + } + lh = conn->tx_queue.next; + llist_del(lh); + msg = llist_entry(lh, struct msgb, list); + + ret = send(conn->ofd.fd, msg->data, msg->len, 0); + if (ret < 0) { + LOGP(DLINP, LOGL_ERROR, "error to send (%s)\n", + strerror(errno)); + } + msgb_free(msg); + return 0; +} + +static int datagram_client_fd_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct datagram_client_conn *conn = ofd->data; + + if (what & BSC_FD_WRITE) { + LOGP(DLINP, LOGL_DEBUG, "connected write\n"); + datagram_client_write(conn); + } + return 0; +} + +struct datagram_client_conn *datagram_client_conn_create(void *ctx) +{ + struct datagram_client_conn *conn; + + conn = talloc_zero(ctx, struct datagram_client_conn); + if (!conn) + return NULL; + + conn->ofd.when |= BSC_FD_READ; + conn->ofd.priv_nr = 0; /* XXX */ + conn->ofd.cb = datagram_client_fd_cb; + conn->ofd.data = conn; + INIT_LLIST_HEAD(&conn->tx_queue); + + return conn; +} + +void +datagram_client_conn_set_addr(struct datagram_client_conn *conn, const char *addr) +{ + conn->addr = talloc_strdup(conn, addr); +} + +void +datagram_client_conn_set_port(struct datagram_client_conn *conn, uint16_t port) +{ + conn->port = port; +} + +void +datagram_client_conn_set_data(struct datagram_client_conn *conn, void *data) +{ + conn->data = data; +} + +void datagram_client_conn_destroy(struct datagram_client_conn *conn) +{ + talloc_free(conn); +} + +int datagram_client_conn_open(struct datagram_client_conn *conn) +{ + int ret; + + ret = osmo_sock_init(AF_INET, SOCK_DGRAM, IPPROTO_UDP, + conn->addr, conn->port, + OSMO_SOCK_F_CONNECT|OSMO_SOCK_F_NONBLOCK); + if (ret < 0) { + if (errno != EINPROGRESS) + return ret; + } + conn->ofd.fd = ret; + if (osmo_fd_register(&conn->ofd) < 0) { + close(ret); + return -EIO; + } + return 0; +} + +void datagram_client_conn_send(struct datagram_client_conn *conn, struct msgb *msg) +{ + msgb_enqueue(&conn->tx_queue, msg); + conn->ofd.when |= BSC_FD_WRITE; +} + +/* + * Server side. + */ + +struct datagram_server_conn { + struct osmo_fd ofd; + const char *addr; + uint16_t port; + int (*cb)(struct datagram_server_conn *conn, struct msgb *msg); + void *data; +}; + +static void datagram_server_conn_read(struct datagram_server_conn *conn) +{ + struct msgb *msg; + int ret; + + LOGP(DLINP, LOGL_DEBUG, "message received\n"); + + msg = msgb_alloc(1200, "LAPD/client"); + if (!msg) { + LOGP(DLINP, LOGL_ERROR, "cannot allocate room for message\n"); + return; + } + ret = recv(conn->ofd.fd, msg->data, msg->data_len, 0); + if (ret < 0) { + if (errno == EPIPE || errno == ECONNRESET) { + LOGP(DLINP, LOGL_ERROR, "lost connection with server\n"); + } + datagram_server_conn_destroy(conn); + return; + } else if (ret == 0) { + LOGP(DLINP, LOGL_ERROR, "connection closed with server\n"); + datagram_server_conn_destroy(conn); + return; + } + msgb_put(msg, ret); + LOGP(DLINP, LOGL_NOTICE, "received %d bytes from client\n", ret); + if (conn->cb) + conn->cb(conn, msg); + + return; +} + +static int datagram_server_conn_cb(struct osmo_fd *ofd, unsigned int what) +{ + struct datagram_server_conn *conn = ofd->data; + + LOGP(DLINP, LOGL_DEBUG, "connected read/write\n"); + if (what & BSC_FD_READ) + datagram_server_conn_read(conn); + + return 0; +} + +struct datagram_server_conn *datagram_server_conn_create(void *ctx) +{ + struct datagram_server_conn *conn; + + conn = talloc_zero(ctx, struct datagram_server_conn); + if (!conn) + return NULL; + + conn->ofd.when |= BSC_FD_READ; + conn->ofd.cb = datagram_server_conn_cb; + conn->ofd.data = conn; + + return conn; +} + +void datagram_server_conn_set_addr(struct datagram_server_conn *conn, const char *addr) +{ + conn->addr = talloc_strdup(conn, addr); +} + +void datagram_server_conn_set_port(struct datagram_server_conn *conn, uint16_t port) +{ + conn->port = port; +} + +void datagram_server_conn_set_read_cb(struct datagram_server_conn *conn, + int (*read_cb)(struct datagram_server_conn *conn, struct msgb *msg)) +{ + conn->cb = read_cb; +} + +void datagram_server_conn_destroy(struct datagram_server_conn *conn) +{ + talloc_free(conn); +} + +int datagram_server_conn_open(struct datagram_server_conn *conn) +{ + int ret; + + ret = osmo_sock_init(AF_INET, SOCK_DGRAM, IPPROTO_UDP, + conn->addr, conn->port, OSMO_SOCK_F_BIND); + if (ret < 0) + return ret; + + conn->ofd.fd = ret; + if (osmo_fd_register(&conn->ofd) < 0) { + close(ret); + return -EIO; + } + return 0; +} + +void datagram_server_conn_close(struct datagram_server_conn *conn) +{ + osmo_fd_unregister(&conn->ofd); + close(conn->ofd.fd); +} + +/* + * Client+Server (bidirectional communications). + */ + +struct datagram_conn { + struct datagram_server_conn *server; + struct datagram_client_conn *client; + void *data; +}; + +struct datagram_conn *datagram_conn_create(void *ctx) +{ + struct datagram_conn *conn; + + conn = talloc_zero(ctx, struct datagram_conn); + if (!conn) + return NULL; + + conn->server = datagram_server_conn_create(ctx); + if (conn->server == NULL) + return NULL; + + conn->client = datagram_client_conn_create(ctx); + if (conn->client == NULL) { + datagram_server_conn_destroy(conn->server); + return NULL; + } + + return conn; +} + +void datagram_conn_destroy(struct datagram_conn *conn) +{ + datagram_server_conn_destroy(conn->server); + datagram_client_conn_destroy(conn->client); +} + +void +datagram_conn_set_local_addr(struct datagram_conn *conn, const char *addr) +{ + datagram_server_conn_set_addr(conn->server, addr); +} + +void +datagram_conn_set_remote_addr(struct datagram_conn *conn, const char *addr) +{ + datagram_client_conn_set_addr(conn->client, addr); +} + +void +datagram_conn_set_local_port(struct datagram_conn *conn, uint16_t port) +{ + datagram_server_conn_set_port(conn->server, port); +} + +void +datagram_conn_set_remote_port(struct datagram_conn *conn, uint16_t port) +{ + datagram_client_conn_set_port(conn->client, port); +} + +void datagram_conn_set_read_cb(struct datagram_conn *conn, + int (*read_cb)(struct datagram_server_conn *conn, struct msgb *msg)) +{ + conn->server->cb = read_cb; +} + +void +datagram_conn_set_data(struct datagram_client_conn *conn, void *data) +{ + conn->data = data; +} + +int datagram_conn_open(struct datagram_conn *conn) +{ + int ret; + + ret = datagram_server_conn_open(conn->server); + if (ret < 0) + return ret; + + ret = datagram_client_conn_open(conn->client); + if (ret < 0) { + datagram_server_conn_close(conn->server); + return ret; + } + return ret; +} + +void datagram_conn_close(struct datagram_conn *conn) +{ + datagram_server_conn_close(conn->server); + datagram_client_conn_close(conn->client); +} + +void datagram_conn_send(struct datagram_conn *conn, struct msgb *msg) +{ + datagram_client_conn_send(conn->client, msg); +} |