diff options
Diffstat (limited to 'drivers/isdn/mISDN/socket.c')
-rw-r--r-- | drivers/isdn/mISDN/socket.c | 833 |
1 files changed, 833 insertions, 0 deletions
diff --git a/drivers/isdn/mISDN/socket.c b/drivers/isdn/mISDN/socket.c new file mode 100644 index 000000000..8dc729008 --- /dev/null +++ b/drivers/isdn/mISDN/socket.c @@ -0,0 +1,833 @@ +/* + * + * Author Karsten Keil <kkeil@novell.com> + * + * Copyright 2008 by Karsten Keil <kkeil@novell.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/mISDNif.h> +#include <linux/slab.h> +#include <linux/export.h> +#include "core.h" + +static u_int *debug; + +static struct proto mISDN_proto = { + .name = "misdn", + .owner = THIS_MODULE, + .obj_size = sizeof(struct mISDN_sock) +}; + +#define _pms(sk) ((struct mISDN_sock *)sk) + +static struct mISDN_sock_list data_sockets = { + .lock = __RW_LOCK_UNLOCKED(data_sockets.lock) +}; + +static struct mISDN_sock_list base_sockets = { + .lock = __RW_LOCK_UNLOCKED(base_sockets.lock) +}; + +#define L2_HEADER_LEN 4 + +static inline struct sk_buff * +_l2_alloc_skb(unsigned int len, gfp_t gfp_mask) +{ + struct sk_buff *skb; + + skb = alloc_skb(len + L2_HEADER_LEN, gfp_mask); + if (likely(skb)) + skb_reserve(skb, L2_HEADER_LEN); + return skb; +} + +static void +mISDN_sock_link(struct mISDN_sock_list *l, struct sock *sk) +{ + write_lock_bh(&l->lock); + sk_add_node(sk, &l->head); + write_unlock_bh(&l->lock); +} + +static void mISDN_sock_unlink(struct mISDN_sock_list *l, struct sock *sk) +{ + write_lock_bh(&l->lock); + sk_del_node_init(sk); + write_unlock_bh(&l->lock); +} + +static int +mISDN_send(struct mISDNchannel *ch, struct sk_buff *skb) +{ + struct mISDN_sock *msk; + int err; + + msk = container_of(ch, struct mISDN_sock, ch); + if (*debug & DEBUG_SOCKET) + printk(KERN_DEBUG "%s len %d %p\n", __func__, skb->len, skb); + if (msk->sk.sk_state == MISDN_CLOSED) + return -EUNATCH; + __net_timestamp(skb); + err = sock_queue_rcv_skb(&msk->sk, skb); + if (err) + printk(KERN_WARNING "%s: error %d\n", __func__, err); + return err; +} + +static int +mISDN_ctrl(struct mISDNchannel *ch, u_int cmd, void *arg) +{ + struct mISDN_sock *msk; + + msk = container_of(ch, struct mISDN_sock, ch); + if (*debug & DEBUG_SOCKET) + printk(KERN_DEBUG "%s(%p, %x, %p)\n", __func__, ch, cmd, arg); + switch (cmd) { + case CLOSE_CHANNEL: + msk->sk.sk_state = MISDN_CLOSED; + break; + } + return 0; +} + +static inline void +mISDN_sock_cmsg(struct sock *sk, struct msghdr *msg, struct sk_buff *skb) +{ + struct timeval tv; + + if (_pms(sk)->cmask & MISDN_TIME_STAMP) { + skb_get_timestamp(skb, &tv); + put_cmsg(msg, SOL_MISDN, MISDN_TIME_STAMP, sizeof(tv), &tv); + } +} + +static int +mISDN_sock_recvmsg(struct socket *sock, struct msghdr *msg, size_t len, + int flags) +{ + struct sk_buff *skb; + struct sock *sk = sock->sk; + + int copied, err; + + if (*debug & DEBUG_SOCKET) + printk(KERN_DEBUG "%s: len %d, flags %x ch.nr %d, proto %x\n", + __func__, (int)len, flags, _pms(sk)->ch.nr, + sk->sk_protocol); + if (flags & (MSG_OOB)) + return -EOPNOTSUPP; + + if (sk->sk_state == MISDN_CLOSED) + return 0; + + skb = skb_recv_datagram(sk, flags, flags & MSG_DONTWAIT, &err); + if (!skb) + return err; + + if (msg->msg_name) { + DECLARE_SOCKADDR(struct sockaddr_mISDN *, maddr, msg->msg_name); + + maddr->family = AF_ISDN; + maddr->dev = _pms(sk)->dev->id; + if ((sk->sk_protocol == ISDN_P_LAPD_TE) || + (sk->sk_protocol == ISDN_P_LAPD_NT)) { + maddr->channel = (mISDN_HEAD_ID(skb) >> 16) & 0xff; + maddr->tei = (mISDN_HEAD_ID(skb) >> 8) & 0xff; + maddr->sapi = mISDN_HEAD_ID(skb) & 0xff; + } else { + maddr->channel = _pms(sk)->ch.nr; + maddr->sapi = _pms(sk)->ch.addr & 0xFF; + maddr->tei = (_pms(sk)->ch.addr >> 8) & 0xFF; + } + msg->msg_namelen = sizeof(*maddr); + } + + copied = skb->len + MISDN_HEADER_LEN; + if (len < copied) { + if (flags & MSG_PEEK) + atomic_dec(&skb->users); + else + skb_queue_head(&sk->sk_receive_queue, skb); + return -ENOSPC; + } + memcpy(skb_push(skb, MISDN_HEADER_LEN), mISDN_HEAD_P(skb), + MISDN_HEADER_LEN); + + err = skb_copy_datagram_msg(skb, 0, msg, copied); + + mISDN_sock_cmsg(sk, msg, skb); + + skb_free_datagram(sk, skb); + + return err ? : copied; +} + +static int +mISDN_sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) +{ + struct sock *sk = sock->sk; + struct sk_buff *skb; + int err = -ENOMEM; + + if (*debug & DEBUG_SOCKET) + printk(KERN_DEBUG "%s: len %d flags %x ch %d proto %x\n", + __func__, (int)len, msg->msg_flags, _pms(sk)->ch.nr, + sk->sk_protocol); + + if (msg->msg_flags & MSG_OOB) + return -EOPNOTSUPP; + + if (msg->msg_flags & ~(MSG_DONTWAIT | MSG_NOSIGNAL | MSG_ERRQUEUE)) + return -EINVAL; + + if (len < MISDN_HEADER_LEN) + return -EINVAL; + + if (sk->sk_state != MISDN_BOUND) + return -EBADFD; + + lock_sock(sk); + + skb = _l2_alloc_skb(len, GFP_KERNEL); + if (!skb) + goto done; + + if (memcpy_from_msg(skb_put(skb, len), msg, len)) { + err = -EFAULT; + goto done; + } + + memcpy(mISDN_HEAD_P(skb), skb->data, MISDN_HEADER_LEN); + skb_pull(skb, MISDN_HEADER_LEN); + + if (msg->msg_namelen >= sizeof(struct sockaddr_mISDN)) { + /* if we have a address, we use it */ + DECLARE_SOCKADDR(struct sockaddr_mISDN *, maddr, msg->msg_name); + mISDN_HEAD_ID(skb) = maddr->channel; + } else { /* use default for L2 messages */ + if ((sk->sk_protocol == ISDN_P_LAPD_TE) || + (sk->sk_protocol == ISDN_P_LAPD_NT)) + mISDN_HEAD_ID(skb) = _pms(sk)->ch.nr; + } + + if (*debug & DEBUG_SOCKET) + printk(KERN_DEBUG "%s: ID:%x\n", + __func__, mISDN_HEAD_ID(skb)); + + err = -ENODEV; + if (!_pms(sk)->ch.peer) + goto done; + err = _pms(sk)->ch.recv(_pms(sk)->ch.peer, skb); + if (err) + goto done; + else { + skb = NULL; + err = len; + } + +done: + if (skb) + kfree_skb(skb); + release_sock(sk); + return err; +} + +static int +data_sock_release(struct socket *sock) +{ + struct sock *sk = sock->sk; + + if (*debug & DEBUG_SOCKET) + printk(KERN_DEBUG "%s(%p) sk=%p\n", __func__, sock, sk); + if (!sk) + return 0; + switch (sk->sk_protocol) { + case ISDN_P_TE_S0: + case ISDN_P_NT_S0: + case ISDN_P_TE_E1: + case ISDN_P_NT_E1: + if (sk->sk_state == MISDN_BOUND) + delete_channel(&_pms(sk)->ch); + else + mISDN_sock_unlink(&data_sockets, sk); + break; + case ISDN_P_LAPD_TE: + case ISDN_P_LAPD_NT: + case ISDN_P_B_RAW: + case ISDN_P_B_HDLC: + case ISDN_P_B_X75SLP: + case ISDN_P_B_L2DTMF: + case ISDN_P_B_L2DSP: + case ISDN_P_B_L2DSPHDLC: + delete_channel(&_pms(sk)->ch); + mISDN_sock_unlink(&data_sockets, sk); + break; + } + + lock_sock(sk); + + sock_orphan(sk); + skb_queue_purge(&sk->sk_receive_queue); + + release_sock(sk); + sock_put(sk); + + return 0; +} + +static int +data_sock_ioctl_bound(struct sock *sk, unsigned int cmd, void __user *p) +{ + struct mISDN_ctrl_req cq; + int err = -EINVAL, val[2]; + struct mISDNchannel *bchan, *next; + + lock_sock(sk); + if (!_pms(sk)->dev) { + err = -ENODEV; + goto done; + } + switch (cmd) { + case IMCTRLREQ: + if (copy_from_user(&cq, p, sizeof(cq))) { + err = -EFAULT; + break; + } + if ((sk->sk_protocol & ~ISDN_P_B_MASK) == ISDN_P_B_START) { + list_for_each_entry_safe(bchan, next, + &_pms(sk)->dev->bchannels, list) { + if (bchan->nr == cq.channel) { + err = bchan->ctrl(bchan, + CONTROL_CHANNEL, &cq); + break; + } + } + } else + err = _pms(sk)->dev->D.ctrl(&_pms(sk)->dev->D, + CONTROL_CHANNEL, &cq); + if (err) + break; + if (copy_to_user(p, &cq, sizeof(cq))) + err = -EFAULT; + break; + case IMCLEAR_L2: + if (sk->sk_protocol != ISDN_P_LAPD_NT) { + err = -EINVAL; + break; + } + val[0] = cmd; + if (get_user(val[1], (int __user *)p)) { + err = -EFAULT; + break; + } + err = _pms(sk)->dev->teimgr->ctrl(_pms(sk)->dev->teimgr, + CONTROL_CHANNEL, val); + break; + case IMHOLD_L1: + if (sk->sk_protocol != ISDN_P_LAPD_NT + && sk->sk_protocol != ISDN_P_LAPD_TE) { + err = -EINVAL; + break; + } + val[0] = cmd; + if (get_user(val[1], (int __user *)p)) { + err = -EFAULT; + break; + } + err = _pms(sk)->dev->teimgr->ctrl(_pms(sk)->dev->teimgr, + CONTROL_CHANNEL, val); + break; + default: + err = -EINVAL; + break; + } +done: + release_sock(sk); + return err; +} + +static int +data_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) +{ + int err = 0, id; + struct sock *sk = sock->sk; + struct mISDNdevice *dev; + struct mISDNversion ver; + + switch (cmd) { + case IMGETVERSION: + ver.major = MISDN_MAJOR_VERSION; + ver.minor = MISDN_MINOR_VERSION; + ver.release = MISDN_RELEASE; + if (copy_to_user((void __user *)arg, &ver, sizeof(ver))) + err = -EFAULT; + break; + case IMGETCOUNT: + id = get_mdevice_count(); + if (put_user(id, (int __user *)arg)) + err = -EFAULT; + break; + case IMGETDEVINFO: + if (get_user(id, (int __user *)arg)) { + err = -EFAULT; + break; + } + dev = get_mdevice(id); + if (dev) { + struct mISDN_devinfo di; + + memset(&di, 0, sizeof(di)); + di.id = dev->id; + di.Dprotocols = dev->Dprotocols; + di.Bprotocols = dev->Bprotocols | get_all_Bprotocols(); + di.protocol = dev->D.protocol; + memcpy(di.channelmap, dev->channelmap, + sizeof(di.channelmap)); + di.nrbchan = dev->nrbchan; + strcpy(di.name, dev_name(&dev->dev)); + if (copy_to_user((void __user *)arg, &di, sizeof(di))) + err = -EFAULT; + } else + err = -ENODEV; + break; + default: + if (sk->sk_state == MISDN_BOUND) + err = data_sock_ioctl_bound(sk, cmd, + (void __user *)arg); + else + err = -ENOTCONN; + } + return err; +} + +static int data_sock_setsockopt(struct socket *sock, int level, int optname, + char __user *optval, unsigned int len) +{ + struct sock *sk = sock->sk; + int err = 0, opt = 0; + + if (*debug & DEBUG_SOCKET) + printk(KERN_DEBUG "%s(%p, %d, %x, %p, %d)\n", __func__, sock, + level, optname, optval, len); + + lock_sock(sk); + + switch (optname) { + case MISDN_TIME_STAMP: + if (get_user(opt, (int __user *)optval)) { + err = -EFAULT; + break; + } + + if (opt) + _pms(sk)->cmask |= MISDN_TIME_STAMP; + else + _pms(sk)->cmask &= ~MISDN_TIME_STAMP; + break; + default: + err = -ENOPROTOOPT; + break; + } + release_sock(sk); + return err; +} + +static int data_sock_getsockopt(struct socket *sock, int level, int optname, + char __user *optval, int __user *optlen) +{ + struct sock *sk = sock->sk; + int len, opt; + + if (get_user(len, optlen)) + return -EFAULT; + + if (len != sizeof(char)) + return -EINVAL; + + switch (optname) { + case MISDN_TIME_STAMP: + if (_pms(sk)->cmask & MISDN_TIME_STAMP) + opt = 1; + else + opt = 0; + + if (put_user(opt, optval)) + return -EFAULT; + break; + default: + return -ENOPROTOOPT; + } + + return 0; +} + +static int +data_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_len) +{ + struct sockaddr_mISDN *maddr = (struct sockaddr_mISDN *) addr; + struct sock *sk = sock->sk; + struct sock *csk; + int err = 0; + + if (*debug & DEBUG_SOCKET) + printk(KERN_DEBUG "%s(%p) sk=%p\n", __func__, sock, sk); + if (addr_len != sizeof(struct sockaddr_mISDN)) + return -EINVAL; + if (!maddr || maddr->family != AF_ISDN) + return -EINVAL; + + lock_sock(sk); + + if (_pms(sk)->dev) { + err = -EALREADY; + goto done; + } + _pms(sk)->dev = get_mdevice(maddr->dev); + if (!_pms(sk)->dev) { + err = -ENODEV; + goto done; + } + + if (sk->sk_protocol < ISDN_P_B_START) { + read_lock_bh(&data_sockets.lock); + sk_for_each(csk, &data_sockets.head) { + if (sk == csk) + continue; + if (_pms(csk)->dev != _pms(sk)->dev) + continue; + if (csk->sk_protocol >= ISDN_P_B_START) + continue; + if (IS_ISDN_P_TE(csk->sk_protocol) + == IS_ISDN_P_TE(sk->sk_protocol)) + continue; + read_unlock_bh(&data_sockets.lock); + err = -EBUSY; + goto done; + } + read_unlock_bh(&data_sockets.lock); + } + + _pms(sk)->ch.send = mISDN_send; + _pms(sk)->ch.ctrl = mISDN_ctrl; + + switch (sk->sk_protocol) { + case ISDN_P_TE_S0: + case ISDN_P_NT_S0: + case ISDN_P_TE_E1: + case ISDN_P_NT_E1: + mISDN_sock_unlink(&data_sockets, sk); + err = connect_layer1(_pms(sk)->dev, &_pms(sk)->ch, + sk->sk_protocol, maddr); + if (err) + mISDN_sock_link(&data_sockets, sk); + break; + case ISDN_P_LAPD_TE: + case ISDN_P_LAPD_NT: + err = create_l2entity(_pms(sk)->dev, &_pms(sk)->ch, + sk->sk_protocol, maddr); + break; + case ISDN_P_B_RAW: + case ISDN_P_B_HDLC: + case ISDN_P_B_X75SLP: + case ISDN_P_B_L2DTMF: + case ISDN_P_B_L2DSP: + case ISDN_P_B_L2DSPHDLC: + err = connect_Bstack(_pms(sk)->dev, &_pms(sk)->ch, + sk->sk_protocol, maddr); + break; + default: + err = -EPROTONOSUPPORT; + } + if (err) + goto done; + sk->sk_state = MISDN_BOUND; + _pms(sk)->ch.protocol = sk->sk_protocol; + +done: + release_sock(sk); + return err; +} + +static int +data_sock_getname(struct socket *sock, struct sockaddr *addr, + int *addr_len, int peer) +{ + struct sockaddr_mISDN *maddr = (struct sockaddr_mISDN *) addr; + struct sock *sk = sock->sk; + + if (!_pms(sk)->dev) + return -EBADFD; + + lock_sock(sk); + + *addr_len = sizeof(*maddr); + maddr->family = AF_ISDN; + maddr->dev = _pms(sk)->dev->id; + maddr->channel = _pms(sk)->ch.nr; + maddr->sapi = _pms(sk)->ch.addr & 0xff; + maddr->tei = (_pms(sk)->ch.addr >> 8) & 0xff; + release_sock(sk); + return 0; +} + +static const struct proto_ops data_sock_ops = { + .family = PF_ISDN, + .owner = THIS_MODULE, + .release = data_sock_release, + .ioctl = data_sock_ioctl, + .bind = data_sock_bind, + .getname = data_sock_getname, + .sendmsg = mISDN_sock_sendmsg, + .recvmsg = mISDN_sock_recvmsg, + .poll = datagram_poll, + .listen = sock_no_listen, + .shutdown = sock_no_shutdown, + .setsockopt = data_sock_setsockopt, + .getsockopt = data_sock_getsockopt, + .connect = sock_no_connect, + .socketpair = sock_no_socketpair, + .accept = sock_no_accept, + .mmap = sock_no_mmap +}; + +static int +data_sock_create(struct net *net, struct socket *sock, int protocol) +{ + struct sock *sk; + + if (sock->type != SOCK_DGRAM) + return -ESOCKTNOSUPPORT; + + sk = sk_alloc(net, PF_ISDN, GFP_KERNEL, &mISDN_proto); + if (!sk) + return -ENOMEM; + + sock_init_data(sock, sk); + + sock->ops = &data_sock_ops; + sock->state = SS_UNCONNECTED; + sock_reset_flag(sk, SOCK_ZAPPED); + + sk->sk_protocol = protocol; + sk->sk_state = MISDN_OPEN; + mISDN_sock_link(&data_sockets, sk); + + return 0; +} + +static int +base_sock_release(struct socket *sock) +{ + struct sock *sk = sock->sk; + + printk(KERN_DEBUG "%s(%p) sk=%p\n", __func__, sock, sk); + if (!sk) + return 0; + + mISDN_sock_unlink(&base_sockets, sk); + sock_orphan(sk); + sock_put(sk); + + return 0; +} + +static int +base_sock_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg) +{ + int err = 0, id; + struct mISDNdevice *dev; + struct mISDNversion ver; + + switch (cmd) { + case IMGETVERSION: + ver.major = MISDN_MAJOR_VERSION; + ver.minor = MISDN_MINOR_VERSION; + ver.release = MISDN_RELEASE; + if (copy_to_user((void __user *)arg, &ver, sizeof(ver))) + err = -EFAULT; + break; + case IMGETCOUNT: + id = get_mdevice_count(); + if (put_user(id, (int __user *)arg)) + err = -EFAULT; + break; + case IMGETDEVINFO: + if (get_user(id, (int __user *)arg)) { + err = -EFAULT; + break; + } + dev = get_mdevice(id); + if (dev) { + struct mISDN_devinfo di; + + memset(&di, 0, sizeof(di)); + di.id = dev->id; + di.Dprotocols = dev->Dprotocols; + di.Bprotocols = dev->Bprotocols | get_all_Bprotocols(); + di.protocol = dev->D.protocol; + memcpy(di.channelmap, dev->channelmap, + sizeof(di.channelmap)); + di.nrbchan = dev->nrbchan; + strcpy(di.name, dev_name(&dev->dev)); + if (copy_to_user((void __user *)arg, &di, sizeof(di))) + err = -EFAULT; + } else + err = -ENODEV; + break; + case IMSETDEVNAME: + { + struct mISDN_devrename dn; + if (copy_from_user(&dn, (void __user *)arg, + sizeof(dn))) { + err = -EFAULT; + break; + } + dev = get_mdevice(dn.id); + if (dev) + err = device_rename(&dev->dev, dn.name); + else + err = -ENODEV; + } + break; + default: + err = -EINVAL; + } + return err; +} + +static int +base_sock_bind(struct socket *sock, struct sockaddr *addr, int addr_len) +{ + struct sockaddr_mISDN *maddr = (struct sockaddr_mISDN *) addr; + struct sock *sk = sock->sk; + int err = 0; + + if (!maddr || maddr->family != AF_ISDN) + return -EINVAL; + + lock_sock(sk); + + if (_pms(sk)->dev) { + err = -EALREADY; + goto done; + } + + _pms(sk)->dev = get_mdevice(maddr->dev); + if (!_pms(sk)->dev) { + err = -ENODEV; + goto done; + } + sk->sk_state = MISDN_BOUND; + +done: + release_sock(sk); + return err; +} + +static const struct proto_ops base_sock_ops = { + .family = PF_ISDN, + .owner = THIS_MODULE, + .release = base_sock_release, + .ioctl = base_sock_ioctl, + .bind = base_sock_bind, + .getname = sock_no_getname, + .sendmsg = sock_no_sendmsg, + .recvmsg = sock_no_recvmsg, + .poll = sock_no_poll, + .listen = sock_no_listen, + .shutdown = sock_no_shutdown, + .setsockopt = sock_no_setsockopt, + .getsockopt = sock_no_getsockopt, + .connect = sock_no_connect, + .socketpair = sock_no_socketpair, + .accept = sock_no_accept, + .mmap = sock_no_mmap +}; + + +static int +base_sock_create(struct net *net, struct socket *sock, int protocol) +{ + struct sock *sk; + + if (sock->type != SOCK_RAW) + return -ESOCKTNOSUPPORT; + + sk = sk_alloc(net, PF_ISDN, GFP_KERNEL, &mISDN_proto); + if (!sk) + return -ENOMEM; + + sock_init_data(sock, sk); + sock->ops = &base_sock_ops; + sock->state = SS_UNCONNECTED; + sock_reset_flag(sk, SOCK_ZAPPED); + sk->sk_protocol = protocol; + sk->sk_state = MISDN_OPEN; + mISDN_sock_link(&base_sockets, sk); + + return 0; +} + +static int +mISDN_sock_create(struct net *net, struct socket *sock, int proto, int kern) +{ + int err = -EPROTONOSUPPORT; + + switch (proto) { + case ISDN_P_BASE: + err = base_sock_create(net, sock, proto); + break; + case ISDN_P_TE_S0: + case ISDN_P_NT_S0: + case ISDN_P_TE_E1: + case ISDN_P_NT_E1: + case ISDN_P_LAPD_TE: + case ISDN_P_LAPD_NT: + case ISDN_P_B_RAW: + case ISDN_P_B_HDLC: + case ISDN_P_B_X75SLP: + case ISDN_P_B_L2DTMF: + case ISDN_P_B_L2DSP: + case ISDN_P_B_L2DSPHDLC: + err = data_sock_create(net, sock, proto); + break; + default: + return err; + } + + return err; +} + +static const struct net_proto_family mISDN_sock_family_ops = { + .owner = THIS_MODULE, + .family = PF_ISDN, + .create = mISDN_sock_create, +}; + +int +misdn_sock_init(u_int *deb) +{ + int err; + + debug = deb; + err = sock_register(&mISDN_sock_family_ops); + if (err) + printk(KERN_ERR "%s: error(%d)\n", __func__, err); + return err; +} + +void +misdn_sock_cleanup(void) +{ + sock_unregister(PF_ISDN); +} |