blob: 8cc5ee7727090ae4dbf1d9ede174be1919a59338 [file] [log] [blame]
Holger Hans Peter Freyther43b09092010-06-15 11:52:51 +08001/* main MSC management code... */
2
3/*
Holger Hans Peter Freythere7bd8632013-06-30 15:30:47 +02004 * (C) 2010,2013 by Holger Hans Peter Freyther <zecke@selfish.org>
Holger Hans Peter Freyther85531cc2010-10-06 20:37:09 +08005 * (C) 2010 by On-Waves
Holger Hans Peter Freyther43b09092010-06-15 11:52:51 +08006 *
7 * All Rights Reserved
8 *
9 * This program is free software; you can redistribute it and/or modify
Harald Welte9af6ddf2011-01-01 15:25:50 +010010 * it under the terms of the GNU Affero General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
Holger Hans Peter Freyther43b09092010-06-15 11:52:51 +080012 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Harald Welte9af6ddf2011-01-01 15:25:50 +010017 * GNU Affero General Public License for more details.
Holger Hans Peter Freyther43b09092010-06-15 11:52:51 +080018 *
Harald Welte9af6ddf2011-01-01 15:25:50 +010019 * You should have received a copy of the GNU Affero General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
Holger Hans Peter Freyther43b09092010-06-15 11:52:51 +080021 *
22 */
23
Neels Hofmeyr90843962017-09-04 15:04:35 +020024#include <osmocom/msc/debug.h>
25#include <osmocom/msc/transaction.h>
26#include <osmocom/msc/db.h>
27#include <osmocom/msc/vlr.h>
Neels Hofmeyr90843962017-09-04 15:04:35 +020028#include <osmocom/msc/a_iface.h>
Max43b01b02017-09-15 11:22:30 +020029#include <osmocom/msc/gsm_04_08.h>
Neels Hofmeyr90843962017-09-04 15:04:35 +020030#include <osmocom/msc/gsm_04_11.h>
Neels Hofmeyr212c0c92018-12-07 14:47:34 +010031#include <osmocom/msc/msc_mgcp.h>
Holger Hans Peter Freyther6a3d7652010-06-15 12:03:10 +080032
Neels Hofmeyr00e82d62017-07-05 15:19:52 +020033#include "../../bscconfig.h"
34#ifdef BUILD_IU
35#include <osmocom/ranap/iu_client.h>
36#else
Neels Hofmeyr90843962017-09-04 15:04:35 +020037#include <osmocom/msc/iu_dummy.h>
Neels Hofmeyr00e82d62017-07-05 15:19:52 +020038#endif
39
Neels Hofmeyr55014d02018-03-22 16:43:40 +010040struct gsm_network *gsm_network_init(void *ctx, mncc_recv_cb_t mncc_recv)
41{
42 struct gsm_network *net;
43
44 net = talloc_zero(ctx, struct gsm_network);
45 if (!net)
46 return NULL;
47
48 net->plmn = (struct osmo_plmn_id){ .mcc=1, .mnc=1 };
49
50 /* Permit a compile-time default of A5/3 and A5/1 */
51 net->a5_encryption_mask = (1 << 3) | (1 << 1);
52
53 /* Use 30 min periodic update interval as sane default */
54 net->t3212 = 5;
55
Philipp Maier9ca7b312018-10-10 17:00:49 +020056 net->mncc_guard_timeout = 180;
57
Neels Hofmeyr55014d02018-03-22 16:43:40 +010058 net->paging_response_timer = MSC_PAGING_RESPONSE_TIMER_DEFAULT;
59
60 INIT_LLIST_HEAD(&net->trans_list);
61 INIT_LLIST_HEAD(&net->upqueue);
Neels Hofmeyrc036b792018-11-29 22:37:51 +010062 INIT_LLIST_HEAD(&net->ran_conns);
Neels Hofmeyr55014d02018-03-22 16:43:40 +010063
64 /* init statistics */
65 net->msc_ctrs = rate_ctr_group_alloc(net, &msc_ctrg_desc, 0);
66 if (!net->msc_ctrs) {
67 talloc_free(net);
68 return NULL;
69 }
70 net->active_calls = osmo_counter_alloc("msc.active_calls");
Vadim Yanitskiyad64e2a2018-06-26 18:27:25 +070071 net->active_nc_ss = osmo_counter_alloc("msc.active_nc_ss");
Neels Hofmeyr55014d02018-03-22 16:43:40 +010072
73 net->mncc_recv = mncc_recv;
74
75 INIT_LLIST_HEAD(&net->a.bscs);
76
77 return net;
78}
79
Neels Hofmeyr80447eb2018-12-05 01:11:28 +010080void gsm_network_set_mncc_sock_path(struct gsm_network *net, const char *mncc_sock_path)
81{
82 if (net->mncc_sock_path)
83 talloc_free(net->mncc_sock_path);
84 net->mncc_sock_path = mncc_sock_path ? talloc_strdup(net, mncc_sock_path) : NULL;
85}
86
Harald Welte2483f1b2016-06-19 18:06:02 +020087/* Receive a SAPI-N-REJECT from BSC */
Neels Hofmeyr3c20a5e2018-11-30 01:08:36 +010088void ran_conn_sapi_n_reject(struct ran_conn *conn, int dlci)
Holger Hans Peter Freyther43b09092010-06-15 11:52:51 +080089{
Holger Hans Peter Freyther6a3d7652010-06-15 12:03:10 +080090 int sapi = dlci & 0x7;
91
92 if (sapi == UM_SAPI_SMS)
93 gsm411_sapi_n_reject(conn);
Holger Hans Peter Freyther43b09092010-06-15 11:52:51 +080094}
95
Neels Hofmeyrd03e7282018-11-30 01:20:32 +010096/* receive a Level 3 Complete message.
97 * Ownership of the conn is completely passed to the conn FSM, i.e. for both acceptance and rejection,
98 * the conn FSM shall decide when to release this conn. It may already be discarded before this exits. */
Neels Hofmeyr3c20a5e2018-11-30 01:08:36 +010099void ran_conn_compl_l3(struct ran_conn *conn,
100 struct msgb *msg, uint16_t chosen_channel)
Holger Hans Peter Freyther97643312010-06-17 16:41:25 +0800101{
Neels Hofmeyr3c20a5e2018-11-30 01:08:36 +0100102 ran_conn_get(conn, RAN_CONN_USE_COMPL_L3);
Holger Hans Peter Freyther97643312010-06-17 16:41:25 +0800103 gsm0408_dispatch(conn, msg);
Neels Hofmeyr3c20a5e2018-11-30 01:08:36 +0100104 ran_conn_put(conn, RAN_CONN_USE_COMPL_L3);
Holger Hans Peter Freyther97643312010-06-17 16:41:25 +0800105}
106
Harald Welte2483f1b2016-06-19 18:06:02 +0200107/* Receive a DTAP message from BSC */
Neels Hofmeyr3c20a5e2018-11-30 01:08:36 +0100108void ran_conn_dtap(struct ran_conn *conn, struct msgb *msg)
Holger Hans Peter Freyther97643312010-06-17 16:41:25 +0800109{
Neels Hofmeyr3c20a5e2018-11-30 01:08:36 +0100110 ran_conn_get(conn, RAN_CONN_USE_DTAP);
Holger Hans Peter Freyther97643312010-06-17 16:41:25 +0800111 gsm0408_dispatch(conn, msg);
Harald Welte2483f1b2016-06-19 18:06:02 +0200112
Neels Hofmeyr3c20a5e2018-11-30 01:08:36 +0100113 ran_conn_put(conn, RAN_CONN_USE_DTAP);
Holger Hans Peter Freyther97643312010-06-17 16:41:25 +0800114}
115
Harald Welte2483f1b2016-06-19 18:06:02 +0200116/* Receive an ASSIGNMENT COMPLETE from BSC */
Neels Hofmeyr212c0c92018-12-07 14:47:34 +0100117void ran_conn_assign_compl(struct ran_conn *conn, const struct gsm0808_speech_codec *speech_codec_chosen,
118 const struct sockaddr_storage *aoip_transport_addr)
Holger Hans Peter Freyther40aac3f2011-12-27 12:31:02 +0100119{
Neels Hofmeyr212c0c92018-12-07 14:47:34 +0100120 msc_mgcp_ass_complete(conn, speech_codec_chosen, aoip_transport_addr);
121 /* FIXME: tear down conn upon failure */
Holger Hans Peter Freyther40aac3f2011-12-27 12:31:02 +0100122}
123
Harald Welte2483f1b2016-06-19 18:06:02 +0200124/* Receive an ASSIGNMENT FAILURE from BSC */
Neels Hofmeyr3c20a5e2018-11-30 01:08:36 +0100125void ran_conn_assign_fail(struct ran_conn *conn, uint8_t cause, uint8_t *rr_cause)
Holger Hans Peter Freyther40aac3f2011-12-27 12:31:02 +0100126{
Jacob Erlbeck8e68b562014-01-30 21:01:12 +0100127 LOGP(DRR, LOGL_DEBUG, "MSC assign failure (do nothing).\n");
Neels Hofmeyr212c0c92018-12-07 14:47:34 +0100128 /* FIXME: tear down conn upon failure */
Holger Hans Peter Freyther40aac3f2011-12-27 12:31:02 +0100129}
130
Harald Welte2483f1b2016-06-19 18:06:02 +0200131/* Receive a CLASSMARK CHANGE from BSC */
Neels Hofmeyr3c20a5e2018-11-30 01:08:36 +0100132void ran_conn_classmark_chg(struct ran_conn *conn,
133 const uint8_t *cm2, uint8_t cm2_len,
134 const uint8_t *cm3, uint8_t cm3_len)
Harald Welte95e862c2012-01-23 10:28:35 +0100135{
Neels Hofmeyr68cf9572018-09-18 15:52:58 +0200136 struct gsm_classmark *cm;
137
138 if (!conn->vsub)
139 cm = &conn->temporary_classmark;
140 else
141 cm = &conn->vsub->classmark;
Neels Hofmeyr986fe7e2018-09-13 03:05:52 +0200142
Harald Welte2483f1b2016-06-19 18:06:02 +0200143 if (cm2 && cm2_len) {
Neels Hofmeyr986fe7e2018-09-13 03:05:52 +0200144 if (cm2_len > sizeof(cm->classmark2)) {
Harald Welte2483f1b2016-06-19 18:06:02 +0200145 LOGP(DRR, LOGL_NOTICE, "%s: classmark2 is %u bytes, truncating at %zu bytes\n",
Neels Hofmeyr986fe7e2018-09-13 03:05:52 +0200146 vlr_subscr_name(conn->vsub), cm2_len, sizeof(cm->classmark2));
147 cm2_len = sizeof(cm->classmark2);
Harald Welte95e862c2012-01-23 10:28:35 +0100148 }
Neels Hofmeyr986fe7e2018-09-13 03:05:52 +0200149 cm->classmark2_len = cm2_len;
150 memcpy(cm->classmark2, cm2, cm2_len);
Harald Welte2483f1b2016-06-19 18:06:02 +0200151 }
152 if (cm3 && cm3_len) {
Neels Hofmeyr986fe7e2018-09-13 03:05:52 +0200153 if (cm3_len > sizeof(cm->classmark3)) {
Harald Welte2483f1b2016-06-19 18:06:02 +0200154 LOGP(DRR, LOGL_NOTICE, "%s: classmark3 is %u bytes, truncating at %zu bytes\n",
Neels Hofmeyr986fe7e2018-09-13 03:05:52 +0200155 vlr_subscr_name(conn->vsub), cm3_len, sizeof(cm->classmark3));
156 cm3_len = sizeof(cm->classmark3);
Harald Welte2483f1b2016-06-19 18:06:02 +0200157 }
Neels Hofmeyr986fe7e2018-09-13 03:05:52 +0200158 cm->classmark3_len = cm3_len;
159 memcpy(cm->classmark3, cm3, cm3_len);
Harald Welte95e862c2012-01-23 10:28:35 +0100160 }
Neels Hofmeyr3117b702018-09-13 03:23:07 +0200161
162 /* bump subscr conn FSM in case it is waiting for a Classmark Update */
Neels Hofmeyrc036b792018-11-29 22:37:51 +0100163 if (conn->fi->state == RAN_CONN_S_WAIT_CLASSMARK_UPDATE)
164 osmo_fsm_inst_dispatch(conn->fi, RAN_CONN_E_CLASSMARK_UPDATE, NULL);
Harald Welte95e862c2012-01-23 10:28:35 +0100165}
166
Harald Welte2483f1b2016-06-19 18:06:02 +0200167/* Receive a CIPHERING MODE COMPLETE from BSC */
Neels Hofmeyr3c20a5e2018-11-30 01:08:36 +0100168void ran_conn_cipher_mode_compl(struct ran_conn *conn, struct msgb *msg, uint8_t alg_id)
Harald Weltecf149ee2012-01-23 16:40:24 +0100169{
Harald Welte2483f1b2016-06-19 18:06:02 +0200170 struct vlr_ciph_result ciph_res = { .cause = VLR_CIPH_REJECT };
Harald Weltecf149ee2012-01-23 16:40:24 +0100171
Harald Welte2483f1b2016-06-19 18:06:02 +0200172 if (!conn) {
Harald Welte284c39a2018-01-24 22:38:06 +0100173 LOGP(DRR, LOGL_ERROR, "invalid: rx Ciphering Mode Complete on NULL conn\n");
Harald Welte2483f1b2016-06-19 18:06:02 +0200174 return;
175 }
176 if (!conn->vsub) {
Harald Welte284c39a2018-01-24 22:38:06 +0100177 LOGP(DRR, LOGL_ERROR, "invalid: rx Ciphering Mode Complete for NULL subscr\n");
Harald Welte2483f1b2016-06-19 18:06:02 +0200178 return;
Harald Weltecf149ee2012-01-23 16:40:24 +0100179 }
180
Harald Welte284c39a2018-01-24 22:38:06 +0100181 DEBUGP(DRR, "%s: CIPHERING MODE COMPLETE\n", vlr_subscr_name(conn->vsub));
Harald Welte2483f1b2016-06-19 18:06:02 +0200182
Harald Welte284c39a2018-01-24 22:38:06 +0100183 if (msg) {
184 struct gsm48_hdr *gh = msgb_l3(msg);
185 unsigned int payload_len = msgb_l3len(msg) - sizeof(*gh);
186 struct tlv_parsed tp;
187 uint8_t mi_type;
Harald Welte2483f1b2016-06-19 18:06:02 +0200188
Harald Welte284c39a2018-01-24 22:38:06 +0100189 if (!gh) {
190 LOGP(DRR, LOGL_ERROR, "invalid: msgb without l3 header\n");
191 return;
192 }
193
194 tlv_parse(&tp, &gsm48_att_tlvdef, gh->data, payload_len, 0, 0);
195
196 /* bearer capability */
197 if (TLVP_PRESENT(&tp, GSM48_IE_MOBILE_ID)) {
198 mi_type = TLVP_VAL(&tp, GSM48_IE_MOBILE_ID)[0] & GSM_MI_TYPE_MASK;
199 if (mi_type == GSM_MI_TYPE_IMEISV
200 && TLVP_LEN(&tp, GSM48_IE_MOBILE_ID) > 0) {
Neels Hofmeyrfa10eda2018-03-13 01:22:01 +0100201 gsm48_mi_to_string(ciph_res.imeisv, sizeof(ciph_res.imeisv),
Harald Welte284c39a2018-01-24 22:38:06 +0100202 TLVP_VAL(&tp, GSM48_IE_MOBILE_ID),
203 TLVP_LEN(&tp, GSM48_IE_MOBILE_ID));
Harald Welte284c39a2018-01-24 22:38:06 +0100204 }
Harald Welte2483f1b2016-06-19 18:06:02 +0200205 }
206 }
207
Neels Hofmeyrf41658d2018-11-30 04:35:50 +0100208 conn->geran_encr.alg_id = alg_id;
Neels Hofmeyrb0779bb2018-11-29 23:37:19 +0100209
Harald Welte2483f1b2016-06-19 18:06:02 +0200210 ciph_res.cause = VLR_CIPH_COMPL;
211 vlr_subscr_rx_ciph_res(conn->vsub, &ciph_res);
Harald Weltecf149ee2012-01-23 16:40:24 +0100212}
213
Harald Welte2483f1b2016-06-19 18:06:02 +0200214/* Receive a CLEAR REQUEST from BSC */
Neels Hofmeyr3c20a5e2018-11-30 01:08:36 +0100215int ran_conn_clear_request(struct ran_conn *conn, uint32_t cause)
Harald Welte2483f1b2016-06-19 18:06:02 +0200216{
Neels Hofmeyrc036b792018-11-29 22:37:51 +0100217 ran_conn_close(conn, cause);
Harald Welte2483f1b2016-06-19 18:06:02 +0200218 return 1;
219}
220
Neels Hofmeyrc036b792018-11-29 22:37:51 +0100221static const char *used_ref_counts_str(struct ran_conn *conn)
Neels Hofmeyr99a8d232018-04-09 20:44:56 +0200222{
223 static char buf[256];
224 int bit_nr;
225 char *pos = buf;
226 *pos = '\0';
227
228 if (conn->use_tokens < 0)
229 return "invalid";
230
231#define APPEND_STR(fmt, args...) do { \
232 int remain = sizeof(buf) - (pos - buf) - 1; \
233 int l = -1; \
234 if (remain > 0) \
235 l = snprintf(pos, remain, "%s" fmt, (pos == buf? "" : ","), ##args); \
236 if (l < 0 || l > remain) { \
237 buf[sizeof(buf) - 1] = '\0'; \
238 return buf; \
239 } \
240 pos += l; \
241 } while(0)
242
243 for (bit_nr = 0; (1 << bit_nr) <= conn->use_tokens; bit_nr++) {
244 if (conn->use_tokens & (1 << bit_nr)) {
Neels Hofmeyrc036b792018-11-29 22:37:51 +0100245 APPEND_STR("%s", get_value_string(ran_conn_use_names, bit_nr));
Neels Hofmeyr99a8d232018-04-09 20:44:56 +0200246 }
247 }
248 return buf;
249#undef APPEND_STR
250}
251
Harald Welte2483f1b2016-06-19 18:06:02 +0200252/* increment the ref-count. Needs to be called by every user */
Neels Hofmeyrc036b792018-11-29 22:37:51 +0100253struct ran_conn *_ran_conn_get(struct ran_conn *conn, enum ran_conn_use balance_token,
254 const char *file, int line)
Harald Welte2483f1b2016-06-19 18:06:02 +0200255{
256 OSMO_ASSERT(conn);
Holger Hans Peter Freythere7bd8632013-06-30 15:30:47 +0200257
Neels Hofmeyr3c20a5e2018-11-30 01:08:36 +0100258 if (balance_token != RAN_CONN_USE_UNTRACKED) {
Neels Hofmeyr6166f292017-11-22 14:33:12 +0100259 uint32_t flag = 1 << balance_token;
260 OSMO_ASSERT(balance_token < 32);
261 if (conn->use_tokens & flag)
262 LOGPSRC(DREF, LOGL_ERROR, file, line,
263 "%s: MSC conn use error: using an already used token: %s\n",
264 vlr_subscr_name(conn->vsub),
Neels Hofmeyrc036b792018-11-29 22:37:51 +0100265 ran_conn_use_name(balance_token));
Neels Hofmeyr6166f292017-11-22 14:33:12 +0100266 conn->use_tokens |= flag;
267 }
268
Harald Welte2483f1b2016-06-19 18:06:02 +0200269 conn->use_count++;
270 LOGPSRC(DREF, LOGL_DEBUG, file, line,
Neels Hofmeyr99a8d232018-04-09 20:44:56 +0200271 "%s: MSC conn use + %s == %u (0x%x: %s)\n",
Neels Hofmeyrc036b792018-11-29 22:37:51 +0100272 vlr_subscr_name(conn->vsub), ran_conn_use_name(balance_token),
Neels Hofmeyr99a8d232018-04-09 20:44:56 +0200273 conn->use_count, conn->use_tokens, used_ref_counts_str(conn));
Harald Welte2483f1b2016-06-19 18:06:02 +0200274
275 return conn;
276}
277
278/* decrement the ref-count. Once it reaches zero, we release */
Neels Hofmeyr3c20a5e2018-11-30 01:08:36 +0100279void _ran_conn_put(struct ran_conn *conn, enum ran_conn_use balance_token,
280 const char *file, int line)
Harald Welte2483f1b2016-06-19 18:06:02 +0200281{
282 OSMO_ASSERT(conn);
283
Neels Hofmeyr3c20a5e2018-11-30 01:08:36 +0100284 if (balance_token != RAN_CONN_USE_UNTRACKED) {
Neels Hofmeyr6166f292017-11-22 14:33:12 +0100285 uint32_t flag = 1 << balance_token;
286 OSMO_ASSERT(balance_token < 32);
287 if (!(conn->use_tokens & flag))
288 LOGPSRC(DREF, LOGL_ERROR, file, line,
289 "%s: MSC conn use error: freeing an unused token: %s\n",
290 vlr_subscr_name(conn->vsub),
Neels Hofmeyrc036b792018-11-29 22:37:51 +0100291 ran_conn_use_name(balance_token));
Neels Hofmeyr6166f292017-11-22 14:33:12 +0100292 conn->use_tokens &= ~flag;
293 }
294
Harald Welte2483f1b2016-06-19 18:06:02 +0200295 if (conn->use_count == 0) {
296 LOGPSRC(DREF, LOGL_ERROR, file, line,
Neels Hofmeyr6166f292017-11-22 14:33:12 +0100297 "%s: MSC conn use - %s failed: is already 0\n",
298 vlr_subscr_name(conn->vsub),
Neels Hofmeyrc036b792018-11-29 22:37:51 +0100299 ran_conn_use_name(balance_token));
Harald Welte2483f1b2016-06-19 18:06:02 +0200300 return;
301 }
302
303 conn->use_count--;
304 LOGPSRC(DREF, LOGL_DEBUG, file, line,
Neels Hofmeyr99a8d232018-04-09 20:44:56 +0200305 "%s: MSC conn use - %s == %u (0x%x: %s)\n",
Neels Hofmeyrc036b792018-11-29 22:37:51 +0100306 vlr_subscr_name(conn->vsub), ran_conn_use_name(balance_token),
Neels Hofmeyr99a8d232018-04-09 20:44:56 +0200307 conn->use_count, conn->use_tokens, used_ref_counts_str(conn));
Harald Welte2483f1b2016-06-19 18:06:02 +0200308
Neels Hofmeyr84da6b12016-05-20 21:59:55 +0200309 if (conn->use_count == 0)
Neels Hofmeyrc036b792018-11-29 22:37:51 +0100310 osmo_fsm_inst_dispatch(conn->fi, RAN_CONN_E_UNUSED, NULL);
Neels Hofmeyr84da6b12016-05-20 21:59:55 +0200311}
312
Neels Hofmeyrc036b792018-11-29 22:37:51 +0100313bool ran_conn_used_by(struct ran_conn *conn, enum ran_conn_use token)
Neels Hofmeyr4068ab22018-04-01 20:55:54 +0200314{
315 return conn && (conn->use_tokens & (1 << token));
316}
317
Neels Hofmeyrc036b792018-11-29 22:37:51 +0100318const struct value_string ran_conn_use_names[] = {
Neels Hofmeyr3c20a5e2018-11-30 01:08:36 +0100319 {RAN_CONN_USE_UNTRACKED, "UNTRACKED"},
320 {RAN_CONN_USE_COMPL_L3, "compl_l3"},
321 {RAN_CONN_USE_DTAP, "dtap"},
322 {RAN_CONN_USE_AUTH_CIPH, "auth+ciph"},
323 {RAN_CONN_USE_CM_SERVICE, "cm_service"},
324 {RAN_CONN_USE_TRANS_CC, "trans_cc"},
325 {RAN_CONN_USE_TRANS_SMS, "trans_sms"},
326 {RAN_CONN_USE_TRANS_NC_SS, "trans_nc_ss"},
327 {RAN_CONN_USE_SILENT_CALL, "silent_call"},
328 {RAN_CONN_USE_RELEASE, "release"},
Neels Hofmeyr6166f292017-11-22 14:33:12 +0100329 {0, NULL},
330};
331
Neels Hofmeyr84da6b12016-05-20 21:59:55 +0200332void msc_stop_paging(struct vlr_subscr *vsub)
333{
334 DEBUGP(DPAG, "Paging can stop for %s\n", vlr_subscr_name(vsub));
335 /* tell BSCs and RNCs to stop paging? How? */
Holger Hans Peter Freyther40494552010-06-28 17:09:29 +0800336}