~jan-kneschke/mysql-proxy/packet-tracking-assertions

« back to all changes in this revision

Viewing changes to trunk/src/network-mysqld-proto.c

  • Committer: Kay Roepke
  • Author(s): Jan Kneschke
  • Date: 2008-01-23 22:00:28 UTC
  • Revision ID: kay@mysql.com-20080123220028-hq2xqb69apa75fnx
first round on mysql-shell based on the proxy code

this is mostly a verification if the proxy-code is flexible enough to handle 
all three scenarios of: client, server and forwarding (proxy)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* $%BEGINLICENSE%$
2
 
 Copyright (c) 2007, 2009, Oracle and/or its affiliates. All rights reserved.
3
 
 
4
 
 This program is free software; you can redistribute it and/or
5
 
 modify it under the terms of the GNU General Public License as
6
 
 published by the Free Software Foundation; version 2 of the
7
 
 License.
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
12
 
 GNU General Public License for more details.
13
 
 
14
 
 You should have received a copy of the GNU General Public License
15
 
 along with this program; if not, write to the Free Software
16
 
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17
 
 02110-1301  USA
18
 
 
19
 
 $%ENDLICENSE%$ */
20
 
 
 
1
/* Copyright (C) 2007 MySQL AB
 
2
 
 
3
   This program is free software; you can redistribute it and/or modify
 
4
   it under the terms of the GNU General Public License as published by
 
5
   the Free Software Foundation; version 2 of the License.
 
6
 
 
7
   This program is distributed in the hope that it will be useful,
 
8
   but WITHOUT ANY WARRANTY; without even the implied warranty of
 
9
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
10
   GNU General Public License for more details.
 
11
 
 
12
   You should have received a copy of the GNU General Public License
 
13
   along with this program; if not, write to the Free Software
 
14
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */ 
21
15
 
22
16
#include <string.h>
23
 
#include <stdio.h>
24
 
#include <stdlib.h>
25
 
 
26
17
#include "network-mysqld-proto.h"
27
18
 
28
19
#include "sys-pedantic.h"
29
 
#include "glib-ext.h"
30
20
 
31
21
/** @file
32
22
 *
49
39
#define CRASHME() do { char *_crashme = NULL; *_crashme = 0; } while(0);
50
40
 
51
41
/**
52
 
 * a handy macro for constant strings 
 
42
 * a handy marco for constant strings 
53
43
 */
54
44
#define C(x) x, sizeof(x) - 1
55
 
#define S(x) x->str, x->len
56
45
 
57
46
/** @defgroup proto MySQL Protocol
58
47
 * 
63
52
/*@{*/
64
53
 
65
54
/**
66
 
 * extract the type of the object that is placed where a length-encoded string is expected
67
 
 *
68
 
 * reads a byte from the packet and checks if it either a:
69
 
 * - integer
70
 
 * - NULL
71
 
 * - a ERR packet
72
 
 * - a EOF packet
73
 
 */
74
 
int network_mysqld_proto_peek_lenenc_type(network_packet *packet, network_mysqld_lenenc_type *type) {
75
 
        guint off = packet->offset;
76
 
        unsigned char *bytestream = (unsigned char *)packet->data->str;
77
 
 
78
 
        g_return_val_if_fail(off < packet->data->len, -1);
79
 
 
80
 
        if (bytestream[off] < 251) { /* */
81
 
                *type = NETWORK_MYSQLD_LENENC_TYPE_INT;
82
 
        } else if (bytestream[off] == 251) { /* NULL */
83
 
                *type = NETWORK_MYSQLD_LENENC_TYPE_NULL;
84
 
        } else if (bytestream[off] == 252) { /* 2 byte length*/
85
 
                *type = NETWORK_MYSQLD_LENENC_TYPE_INT;
86
 
        } else if (bytestream[off] == 253) { /* 3 byte */
87
 
                *type = NETWORK_MYSQLD_LENENC_TYPE_INT;
88
 
        } else if (bytestream[off] == 254) { /* 8 byte OR EOF */
89
 
                if (off == 4 && 
90
 
                    packet->data->len - packet->offset < 8) {
91
 
                        *type = NETWORK_MYSQLD_LENENC_TYPE_EOF;
92
 
                } else {
93
 
                        *type = NETWORK_MYSQLD_LENENC_TYPE_INT;
94
 
                }
95
 
        } else {
96
 
                *type = NETWORK_MYSQLD_LENENC_TYPE_ERR;
97
 
        }
98
 
 
99
 
        return 0;
100
 
}
101
 
 
102
 
/**
103
55
 * decode a length-encoded integer from a network packet
104
56
 *
105
57
 * _off is incremented on success 
106
58
 *
107
59
 * @param packet   the MySQL-packet to decode
108
 
 * @param v        destination of the integer
109
 
 * @return 0 on success, non-0 on error 
 
60
 * @param _off     offset in into the packet 
 
61
 * @return the decoded number
110
62
 *
111
63
 */
112
 
int network_mysqld_proto_get_lenenc_int(network_packet *packet, guint64 *v) {
113
 
        guint off = packet->offset;
 
64
guint64 network_mysqld_proto_get_lenenc_int(GString *packet, guint *_off) {
 
65
        guint off = *_off;
114
66
        guint64 ret = 0;
115
 
        unsigned char *bytestream = (unsigned char *)packet->data->str;
 
67
        unsigned char *bytestream = (unsigned char *)packet->str;
116
68
 
117
 
        if (off >= packet->data->len) return -1;
 
69
        g_assert(off < packet->len);
118
70
        
119
71
        if (bytestream[off] < 251) { /* */
120
72
                ret = bytestream[off];
 
73
        } else if (bytestream[off] == 251) { /* NULL in row-data */
 
74
                ret = bytestream[off];
121
75
        } else if (bytestream[off] == 252) { /* 2 byte length*/
122
 
                if (off + 2 >= packet->data->len) return -1;
 
76
                g_assert(off + 2 < packet->len);
123
77
                ret = (bytestream[off + 1] << 0) | 
124
78
                        (bytestream[off + 2] << 8) ;
125
79
                off += 2;
126
80
        } else if (bytestream[off] == 253) { /* 3 byte */
127
 
                if (off + 3 >= packet->data->len) return -1;
 
81
                g_assert(off + 3 < packet->len);
128
82
                ret = (bytestream[off + 1]   <<  0) | 
129
83
                        (bytestream[off + 2] <<  8) |
130
84
                        (bytestream[off + 3] << 16);
131
85
 
132
86
                off += 3;
133
87
        } else if (bytestream[off] == 254) { /* 8 byte */
134
 
                if (off + 8 >= packet->data->len) return -1;
 
88
                g_assert(off + 8 < packet->len);
135
89
                ret = (bytestream[off + 5] << 0) |
136
90
                        (bytestream[off + 6] << 8) |
137
91
                        (bytestream[off + 7] << 16) |
146
100
 
147
101
                off += 8;
148
102
        } else {
149
 
                /* if we hit this place we complete have no idea about the protocol */
150
 
                g_critical("%s: bytestream[%d] is %d", 
151
 
                        G_STRLOC,
 
103
                g_error("%s.%d: bytestream[%d] is %d", 
 
104
                        __FILE__, __LINE__,
152
105
                        off, bytestream[off]);
153
 
 
154
 
                /* either ERR (255) or NULL (251) */
155
 
 
156
 
                return -1;
157
106
        }
158
107
        off += 1;
159
108
 
160
 
        packet->offset = off;
161
 
 
162
 
        *v = ret;
163
 
 
164
 
        return 0;
165
 
}
 
109
        *_off = off;
 
110
 
 
111
        return ret;
 
112
}
 
113
 
 
114
/**
 
115
 * decode a OK packet from the network packet
 
116
 */
 
117
int network_mysqld_proto_get_ok_packet(GString *packet, guint64 *affected, guint64 *insert_id, int *server_status, int *warning_count, char **msg) {
 
118
        guint off = 0;
 
119
        guint64 dest;
 
120
        guint field_count;
 
121
 
 
122
        field_count = network_mysqld_proto_get_int8(packet, &off);
 
123
        g_assert(field_count == 0);
 
124
 
 
125
        dest = network_mysqld_proto_get_lenenc_int(packet, &off); if (affected) *affected = dest;
 
126
        dest = network_mysqld_proto_get_lenenc_int(packet, &off); if (insert_id) *insert_id = dest;
 
127
 
 
128
        dest = network_mysqld_proto_get_int16(packet, &off);      if (server_status) *server_status = dest;
 
129
        dest = network_mysqld_proto_get_int16(packet, &off);      if (warning_count) *warning_count = dest;
 
130
 
 
131
        if (msg) *msg = NULL;
 
132
 
 
133
        return 0;
 
134
}
 
135
 
 
136
int network_mysqld_proto_append_ok_packet(GString *packet, guint64 affected_rows, guint64 insert_id, guint16 server_status, guint16 warnings) {
 
137
        network_mysqld_proto_append_int8(packet, 0); /* no fields */
 
138
        network_mysqld_proto_append_lenenc_int(packet, affected_rows);
 
139
        network_mysqld_proto_append_lenenc_int(packet, insert_id);
 
140
        network_mysqld_proto_append_int16(packet, server_status); /* autocommit */
 
141
        network_mysqld_proto_append_int16(packet, warnings); /* no warnings */
 
142
 
 
143
        return 0;
 
144
}
 
145
 
 
146
/**
 
147
 * create a ERR packet
 
148
 *
 
149
 * @note the sqlstate has to match the SQL standard. If no matching SQL state is known, leave it at NULL
 
150
 *
 
151
 * @param packet      network packet
 
152
 * @param errmsg      the error message
 
153
 * @param errmsg_len  byte-len of the error-message
 
154
 * @param errorcode   mysql error-code we want to send
 
155
 * @param sqlstate    if none-NULL, 5-char SQL state to send, if NULL, default SQL state is used
 
156
 *
 
157
 * @return 0 on success
 
158
 */
 
159
int network_mysqld_proto_append_error_packet(GString *packet, const char *errmsg, gsize errmsg_len, guint errorcode, const gchar *sqlstate) {
 
160
        network_mysqld_proto_append_int8(packet, 0xff); /* ERR */
 
161
        network_mysqld_proto_append_int16(packet, errorcode); /* errorcode */
 
162
        g_string_append_c(packet, '#');
 
163
        if (!sqlstate) {
 
164
                g_string_append_len(packet, C("07000"));
 
165
        } else {
 
166
                g_string_append_len(packet, sqlstate, 5);
 
167
        }
 
168
 
 
169
        if (errmsg_len < 512) {
 
170
                g_string_append_len(packet, errmsg, errmsg_len);
 
171
        } else {
 
172
                /* truncate the err-msg */
 
173
                g_string_append_len(packet, errmsg, 512);
 
174
        }
 
175
 
 
176
        return 0;
 
177
}
 
178
 
166
179
 
167
180
/**
168
181
 * skip bytes in the network packet
170
183
 * a assertion makes sure that we can't skip over the end of the packet 
171
184
 *
172
185
 * @param packet the MySQL network packet
 
186
 * @param _off   offset into the packet
173
187
 * @param size   bytes to skip
174
188
 *
175
189
 */
176
 
int network_mysqld_proto_skip(network_packet *packet, gsize size) {
177
 
        if (packet->offset + size > packet->data->len) return -1;
 
190
void network_mysqld_proto_skip(GString *packet, guint *_off, gsize size) {
 
191
        g_assert(*_off + size <= packet->len);
178
192
        
179
 
        packet->offset += size;
180
 
 
181
 
        return 0;
 
193
        *_off += size;
182
194
}
183
195
 
184
196
/**
185
197
 * get a fixed-length integer from the network packet 
186
198
 *
187
199
 * @param packet the MySQL network packet
188
 
 * @param v      destination of the integer
 
200
 * @param _off   offset into the packet
189
201
 * @param size   byte-len of the integer to decode
190
202
 * @return a the decoded integer
191
203
 */
192
 
int network_mysqld_proto_peek_int_len(network_packet *packet, guint64 *v, gsize size) {
 
204
guint64 network_mysqld_proto_get_int_len(GString *packet, guint *_off, gsize size) {
193
205
        gsize i;
194
206
        int shift;
195
 
        guint32 r_l = 0, r_h = 0;
196
 
        guchar *bytes = (guchar *)packet->data->str + packet->offset;
197
 
 
198
 
        if (packet->offset > packet->data->len) {
199
 
                return -1;
200
 
        }
201
 
        if (packet->offset + size > packet->data->len) {
202
 
                return -1;
203
 
        }
204
 
 
205
 
        /**
206
 
         * for some reason left-shift > 32 leads to negative numbers 
207
 
         */
208
 
        for (i = 0, shift = 0; 
209
 
                        i < size && i < 4; 
210
 
                        i++, shift += 8, bytes++) {
211
 
                r_l |= ((*bytes) << shift);
212
 
        }
213
 
 
214
 
        for (shift = 0;
215
 
                        i < size; 
216
 
                        i++, shift += 8, bytes++) {
217
 
                r_h |= ((*bytes) << shift);
218
 
        }
219
 
 
220
 
        *v = (((guint64)r_h << 32) | r_l);
221
 
 
222
 
        return 0;
223
 
}
224
 
 
225
 
int network_mysqld_proto_get_int_len(network_packet *packet, guint64 *v, gsize size) {
226
 
        int err = 0;
227
 
 
228
 
        err = err || network_mysqld_proto_peek_int_len(packet, v, size);
229
 
 
230
 
        if (err) return -1;
231
 
 
232
 
        packet->offset += size;
233
 
 
234
 
        return 0;
235
 
}
236
 
/**
237
 
 * get a 8-bit integer from the network packet
238
 
 *
239
 
 * @param packet the MySQL network packet
240
 
 * @param v      dest for the number
241
 
 * @return 0 on success, non-0 on error
242
 
 * @see network_mysqld_proto_get_int_len()
243
 
 */
244
 
int network_mysqld_proto_get_int8(network_packet *packet, guint8 *v) {
245
 
        guint64 v64;
246
 
 
247
 
        if (network_mysqld_proto_get_int_len(packet, &v64, 1)) return -1;
248
 
 
249
 
        g_assert_cmpint(v64 & 0xff, ==, v64); /* check that we really only got one byte back */
250
 
 
251
 
        *v = v64 & 0xff;
252
 
 
253
 
        return 0;
254
 
}
255
 
 
256
 
/**
257
 
 * get a 8-bit integer from the network packet
258
 
 *
259
 
 * @param packet the MySQL network packet
260
 
 * @param v      dest for the number
261
 
 * @return 0 on success, non-0 on error
262
 
 * @see network_mysqld_proto_get_int_len()
263
 
 */
264
 
int network_mysqld_proto_peek_int8(network_packet *packet, guint8 *v) {
265
 
        guint64 v64;
266
 
 
267
 
        if (network_mysqld_proto_peek_int_len(packet, &v64, 1)) return -1;
268
 
 
269
 
        g_assert_cmpint(v64 & 0xff, ==, v64); /* check that we really only got one byte back */
270
 
 
271
 
        *v = v64 & 0xff;
272
 
 
273
 
        return 0;
274
 
}
275
 
 
276
 
 
277
 
/**
278
 
 * get a 16-bit integer from the network packet
279
 
 *
280
 
 * @param packet the MySQL network packet
281
 
 * @param v      dest for the number
282
 
 * @return 0 on success, non-0 on error
283
 
 * @see network_mysqld_proto_get_int_len()
284
 
 */
285
 
int network_mysqld_proto_get_int16(network_packet *packet, guint16 *v) {
286
 
        guint64 v64;
287
 
 
288
 
        if (network_mysqld_proto_get_int_len(packet, &v64, 2)) return -1;
289
 
 
290
 
        g_assert_cmpint(v64 & 0xffff, ==, v64); /* check that we really only got two byte back */
291
 
 
292
 
        *v = v64 & 0xffff;
293
 
 
294
 
        return 0;
295
 
}
296
 
 
297
 
/**
298
 
 * get a 16-bit integer from the network packet
299
 
 *
300
 
 * @param packet the MySQL network packet
301
 
 * @param v      dest for the number
302
 
 * @return 0 on success, non-0 on error
303
 
 * @see network_mysqld_proto_get_int_len()
304
 
 */
305
 
int network_mysqld_proto_peek_int16(network_packet *packet, guint16 *v) {
306
 
        guint64 v64;
307
 
 
308
 
        if (network_mysqld_proto_peek_int_len(packet, &v64, 2)) return -1;
309
 
 
310
 
        g_assert_cmpint(v64 & 0xffff, ==, v64); /* check that we really only got two byte back */
311
 
 
312
 
        *v = v64 & 0xffff;
313
 
 
314
 
        return 0;
315
 
}
316
 
 
317
 
 
318
 
/**
319
 
 * get a 24-bit integer from the network packet
320
 
 *
321
 
 * @param packet the MySQL network packet
322
 
 * @param v      dest for the number
323
 
 * @return 0 on success, non-0 on error
324
 
 * @see network_mysqld_proto_get_int_len()
325
 
 */
326
 
int network_mysqld_proto_get_int24(network_packet *packet, guint32 *v) {
327
 
        guint64 v64;
328
 
 
329
 
        if (network_mysqld_proto_get_int_len(packet, &v64, 3)) return -1;
330
 
 
331
 
        g_assert_cmpint(v64 & 0x00ffffff, ==, v64); /* check that we really only got two byte back */
332
 
 
333
 
        *v = v64 & 0x00ffffff;
334
 
 
335
 
        return 0;
336
 
}
337
 
 
 
207
        guint64 r = 0;
 
208
        guint off = *_off;
 
209
 
 
210
        g_assert(*_off < packet->len);
 
211
        if (*_off + size > packet->len) {
 
212
                CRASHME();
 
213
        }
 
214
        g_assert(*_off + size <= packet->len);
 
215
 
 
216
        for (i = 0, shift = 0; i < size; i++, shift += 8) {
 
217
                r += (unsigned char)(packet->str[off + i]) << shift;
 
218
        }
 
219
 
 
220
        *_off += size;
 
221
 
 
222
        return r;
 
223
}
 
224
 
 
225
/**
 
226
 * get a 8-bit integer from the network packet
 
227
 *
 
228
 * @param packet the MySQL network packet
 
229
 * @param _off   offset into the packet
 
230
 * @return a the decoded integer
 
231
 * @see network_mysqld_proto_get_int_len()
 
232
 */
 
233
guint8 network_mysqld_proto_get_int8(GString *packet, guint *_off) {
 
234
        return network_mysqld_proto_get_int_len(packet, _off, 1);
 
235
}
 
236
 
 
237
/**
 
238
 * get a 16-bit integer from the network packet
 
239
 *
 
240
 * @param packet the MySQL network packet
 
241
 * @param _off   offset into the packet
 
242
 * @return a the decoded integer
 
243
 * @see network_mysqld_proto_get_int_len()
 
244
 */
 
245
guint16 network_mysqld_proto_get_int16(GString *packet, guint *_off) {
 
246
        return network_mysqld_proto_get_int_len(packet, _off, 2);
 
247
}
338
248
 
339
249
/**
340
250
 * get a 32-bit integer from the network packet
341
251
 *
342
252
 * @param packet the MySQL network packet
343
 
 * @param v      dest for the number
344
 
 * @return 0 on success, non-0 on error
345
 
 * @see network_mysqld_proto_get_int_len()
346
 
 */
347
 
int network_mysqld_proto_get_int32(network_packet *packet, guint32 *v) {
348
 
        guint64 v64;
349
 
 
350
 
        if (network_mysqld_proto_get_int_len(packet, &v64, 4)) return -1;
351
 
 
352
 
        *v = v64 & 0xffffffff;
353
 
 
354
 
        return 0;
355
 
}
356
 
 
357
 
/**
358
 
 * get a 6-byte integer from the network packet
359
 
 *
360
 
 * @param packet the MySQL network packet
361
 
 * @param v      dest for the number
362
 
 * @return 0 on success, non-0 on error
363
 
 * @see network_mysqld_proto_get_int_len()
364
 
 */
365
 
int network_mysqld_proto_get_int48(network_packet *packet, guint64 *v) {
366
 
        guint64 v64;
367
 
 
368
 
        if (network_mysqld_proto_get_int_len(packet, &v64, 6)) return -1;
369
 
 
370
 
        *v = v64;
371
 
 
372
 
        return 0;
373
 
}
374
 
 
375
 
/**
376
 
 * get a 8-byte integer from the network packet
377
 
 *
378
 
 * @param packet the MySQL network packet
379
 
 * @param v      dest for the number
380
 
 * @return 0 on success, non-0 on error
381
 
 * @see network_mysqld_proto_get_int_len()
382
 
 */
383
 
int network_mysqld_proto_get_int64(network_packet *packet, guint64 *v) {
384
 
        return network_mysqld_proto_get_int_len(packet, v, 8);
385
 
}
386
 
 
387
 
/**
388
 
 * find a 8-bit integer in the network packet
389
 
 *
390
 
 * @param packet the MySQL network packet
391
 
 * @param c      character to find
392
 
 * @param pos    offset into the packet the 'c' was found
 
253
 * @param _off   offset into the packet
393
254
 * @return a the decoded integer
394
255
 * @see network_mysqld_proto_get_int_len()
395
256
 */
396
 
int network_mysqld_proto_find_int8(network_packet *packet, guint8 c, guint *pos) {
397
 
        int err = 0;
398
 
        guint off = packet->offset;
399
 
 
400
 
        while (!err) {
401
 
                guint8 _c;
402
 
 
403
 
                err = err || network_mysqld_proto_get_int8(packet, &_c);
404
 
                if (!err) {
405
 
                        if (c == _c) {
406
 
                                *pos = packet->offset - off;
407
 
                                break;
408
 
                        }
409
 
                }
410
 
        }
411
 
 
412
 
        packet->offset = off;
413
 
 
414
 
        return err;
 
257
guint32 network_mysqld_proto_get_int32(GString *packet, guint *_off) {
 
258
        return network_mysqld_proto_get_int_len(packet, _off, 4);
415
259
}
416
260
 
417
 
 
418
261
/**
419
262
 * get a string from the network packet
420
263
 *
421
264
 * @param packet the MySQL network packet
422
 
 * @param s      dest of the string
 
265
 * @param _off   offset into the packet
423
266
 * @param len    length of the string
424
 
 * @return       0 on success, non-0 otherwise
425
267
 * @return the string (allocated) or NULL of len is 0
426
268
 */
427
 
int network_mysqld_proto_get_string_len(network_packet *packet, gchar **s, gsize len) {
 
269
gchar *network_mysqld_proto_get_string_len(GString *packet, guint *_off, gsize len) {
428
270
        gchar *str;
429
271
 
430
 
        if (len == 0) {
431
 
                *s = NULL;
432
 
                return 0;
433
 
        }
434
 
 
435
 
        if (packet->offset > packet->data->len) {
436
 
                return -1;
437
 
        }
438
 
        if (packet->offset + len > packet->data->len) {
439
 
                g_critical("%s: packet-offset out of range: %u + "F_SIZE_T" > "F_SIZE_T, 
440
 
                                G_STRLOC,
441
 
                                packet->offset, len, packet->data->len);
442
 
 
443
 
                return -1;
444
 
        }
445
 
 
446
 
        if (len) {
447
 
                str = g_malloc(len + 1);
448
 
                memcpy(str, packet->data->str + packet->offset, len);
449
 
                str[len] = '\0';
450
 
        } else {
451
 
                str = NULL;
452
 
        }
453
 
 
454
 
        packet->offset += len;
455
 
 
456
 
        *s = str;
457
 
 
458
 
        return 0;
 
272
        g_assert(*_off < packet->len);
 
273
        if (*_off + len > packet->len) {
 
274
                g_error("packet-offset out of range: %u + "F_SIZE_T" > "F_SIZE_T, *_off, len, packet->len);
 
275
        }
 
276
 
 
277
        str = len ? g_strndup(packet->str + *_off, len) : NULL; 
 
278
 
 
279
        *_off += len;
 
280
 
 
281
        return str;
459
282
}
460
283
 
461
284
/**
464
287
 * variable length strings are prefixed with variable-length integer defining the length of the string
465
288
 *
466
289
 * @param packet the MySQL network packet
467
 
 * @param s      destination of the decoded string
468
 
 * @param _len    destination of the length of the decoded string, if len is non-NULL
469
 
 * @return 0 on success, non-0 on error
 
290
 * @param _off   offset into the packet
 
291
 * @return the string
470
292
 * @see network_mysqld_proto_get_string_len(), network_mysqld_proto_get_lenenc_int()
471
293
 */
472
 
int network_mysqld_proto_get_lenenc_string(network_packet *packet, gchar **s, guint64 *_len) {
 
294
gchar *network_mysqld_proto_get_lenenc_string(GString *packet, guint *_off) {
473
295
        guint64 len;
474
296
 
475
 
        if (packet->offset >= packet->data->len) {
476
 
                g_debug_hexdump(G_STRLOC, S(packet->data));
477
 
                return -1;
478
 
        }       
479
 
        if (packet->offset >= packet->data->len) {
480
 
                return -1;
481
 
        }
482
 
 
483
 
        if (network_mysqld_proto_get_lenenc_int(packet, &len)) return -1;
484
 
        
485
 
        if (packet->offset + len > packet->data->len) return -1;
486
 
 
487
 
        if (_len) *_len = len;
488
 
        
489
 
        return network_mysqld_proto_get_string_len(packet, s, len);
 
297
        len = network_mysqld_proto_get_lenenc_int(packet, _off);
 
298
        
 
299
        g_assert(*_off < packet->len);
 
300
        g_assert(*_off + len <= packet->len);
 
301
        
 
302
        return network_mysqld_proto_get_string_len(packet, _off, len);
490
303
}
491
304
 
492
305
/**
493
306
 * get a NUL-terminated string from the network packet
494
307
 *
495
308
 * @param packet the MySQL network packet
496
 
 * @param s      dest of the string
497
 
 * @return       0 on success, non-0 otherwise
 
309
 * @param _off   offset into the packet
 
310
 * @return       the string
498
311
 * @see network_mysqld_proto_get_string_len()
499
312
 */
500
 
int network_mysqld_proto_get_string(network_packet *packet, gchar **s) {
501
 
        guint64 len;
502
 
        int err = 0;
503
 
 
504
 
        for (len = 0; packet->offset + len < packet->data->len && *(packet->data->str + packet->offset + len); len++);
505
 
 
506
 
        if (*(packet->data->str + packet->offset + len) != '\0') {
507
 
                /* this has to be a \0 */
508
 
                return -1;
509
 
        }
 
313
gchar *network_mysqld_proto_get_string(GString *packet, guint *_off) {
 
314
        guint len;
 
315
        gchar *r = NULL;
 
316
 
 
317
        for (len = 0; *_off + len < packet->len && *(packet->str + *_off + len); len++);
 
318
 
 
319
        g_assert(*(packet->str + *_off + len) == '\0'); /* this has to be a \0 */
510
320
 
511
321
        if (len > 0) {
512
 
                if (packet->offset >= packet->data->len) {
513
 
                        return -1;
514
 
                }
515
 
                if (packet->offset + len > packet->data->len) {
516
 
                        return -1;
517
 
                }
 
322
                g_assert(*_off < packet->len);
 
323
                g_assert(*_off + len <= packet->len);
518
324
 
519
325
                /**
520
326
                 * copy the string w/o the NUL byte 
521
327
                 */
522
 
                err = err || network_mysqld_proto_get_string_len(packet, s, len);
 
328
                r = network_mysqld_proto_get_string_len(packet, _off, len);
523
329
        }
524
330
 
525
 
        err = err || network_mysqld_proto_skip(packet, 1);
 
331
        *_off += 1;
526
332
 
527
 
        return err ? -1 : 0;
 
333
        return r;
528
334
}
529
335
 
530
336
 
532
338
 * get a GString from the network packet
533
339
 *
534
340
 * @param packet the MySQL network packet
 
341
 * @param _off   offset into the packet
535
342
 * @param len    bytes to copy
536
343
 * @param out    a GString which carries the string
537
 
 * @return       0 on success, -1 on error
 
344
 * @return       a pointer to the string in out
538
345
 */
539
 
int network_mysqld_proto_get_gstring_len(network_packet *packet, gsize len, GString *out) {
540
 
        int err = 0;
541
 
 
542
 
        if (!out) return -1;
543
 
 
 
346
gchar *network_mysqld_proto_get_gstring_len(GString *packet, guint *_off, gsize len, GString *out) {
544
347
        g_string_truncate(out, 0);
545
348
 
546
 
        if (!len) return 0; /* nothing to copy */
547
 
 
548
 
        err = err || (packet->offset >= packet->data->len); /* the offset is already too large */
549
 
        err = err || (packet->offset + len > packet->data->len); /* offset would get too large */
550
 
 
551
 
        if (!err) {
552
 
                g_string_append_len(out, packet->data->str + packet->offset, len);
553
 
                packet->offset += len;
 
349
        if (len) {
 
350
                g_assert(*_off < packet->len);
 
351
                if (*_off + len > packet->len) {
 
352
                        g_error("packet-offset out of range: %u + "F_SIZE_T" > "F_SIZE_T, *_off, len, packet->len);
 
353
                }
 
354
 
 
355
                g_string_append_len(out, packet->str + *_off, len);
 
356
                *_off += len;
554
357
        }
555
358
 
556
 
        return err ? -1 : 0;
 
359
        return out->str;
557
360
}
558
361
 
559
362
/**
560
363
 * get a NUL-terminated GString from the network packet
561
364
 *
562
365
 * @param packet the MySQL network packet
 
366
 * @param _off   offset into the packet
563
367
 * @param out    a GString which carries the string
564
368
 * @return       a pointer to the string in out
565
369
 *
566
370
 * @see network_mysqld_proto_get_gstring_len()
567
371
 */
568
 
int network_mysqld_proto_get_gstring(network_packet *packet, GString *out) {
569
 
        guint64 len;
570
 
        int err = 0;
571
 
 
572
 
        for (len = 0; packet->offset + len < packet->data->len && *(packet->data->str + packet->offset + len) != '\0'; len++);
573
 
 
574
 
        if (packet->offset + len == packet->data->len) { /* havn't found a trailing \0 */
575
 
                return -1;
576
 
        }
 
372
gchar *network_mysqld_proto_get_gstring(GString *packet, guint *_off, GString *out) {
 
373
        guint len;
 
374
        gchar *r = NULL;
 
375
 
 
376
        for (len = 0; *_off + len < packet->len && *(packet->str + *_off + len); len++);
 
377
 
 
378
        g_assert(*(packet->str + *_off + len) == '\0'); /* this has to be a \0 */
577
379
 
578
380
        if (len > 0) {
579
 
                g_assert(packet->offset < packet->data->len);
580
 
                g_assert(packet->offset + len <= packet->data->len);
 
381
                g_assert(*_off < packet->len);
 
382
                g_assert(*_off + len <= packet->len);
581
383
 
582
 
                err = err || network_mysqld_proto_get_gstring_len(packet, len, out);
 
384
                r = network_mysqld_proto_get_gstring_len(packet, _off, len, out);
583
385
        }
584
386
 
585
387
        /* skip the \0 */
586
 
        err = err || network_mysqld_proto_skip(packet, 1);
 
388
        *_off += 1;
587
389
 
588
 
        return err ? -1 : 0;
 
390
        return r;
589
391
}
590
392
 
591
393
/**
592
394
 * get a variable-length GString from the network packet
593
395
 *
594
396
 * @param packet the MySQL network packet
 
397
 * @param _off   offset into the packet
595
398
 * @param out    a GString which carries the string
596
 
 * @return       0 on success, non-0 on error
 
399
 * @return       a pointer to the string in out
597
400
 *
598
401
 * @see network_mysqld_proto_get_gstring_len(), network_mysqld_proto_get_lenenc_int()
599
402
 */
600
 
int network_mysqld_proto_get_lenenc_gstring(network_packet *packet, GString *out) {
 
403
gchar *network_mysqld_proto_get_lenenc_gstring(GString *packet, guint *_off, GString *out) {
601
404
        guint64 len;
602
 
        int err = 0;
603
 
 
604
 
        err = err || network_mysqld_proto_get_lenenc_int(packet, &len);
605
 
        err = err || network_mysqld_proto_get_gstring_len(packet, len, out);
606
 
 
607
 
        return err ? -1 : 0;
 
405
 
 
406
        len = network_mysqld_proto_get_lenenc_int(packet, _off);
 
407
 
 
408
        return network_mysqld_proto_get_gstring_len(packet, _off, len, out);
608
409
}
609
410
 
610
411
/**
612
413
 *
613
414
 * @return a empty MYSQL_FIELD
614
415
 */
615
 
MYSQL_FIELD *network_mysqld_proto_fielddef_new() {
 
416
MYSQL_FIELD *network_mysqld_proto_field_init() {
616
417
        MYSQL_FIELD *field;
617
418
        
618
419
        field = g_new0(MYSQL_FIELD, 1);
625
426
 *
626
427
 * @param field  the MYSQL_FIELD to free
627
428
 */
628
 
void network_mysqld_proto_fielddef_free(MYSQL_FIELD *field) {
 
429
void network_mysqld_proto_field_free(MYSQL_FIELD *field) {
629
430
        if (field->catalog) g_free(field->catalog);
630
431
        if (field->db) g_free(field->db);
631
432
        if (field->name) g_free(field->name);
641
442
 *
642
443
 * @return a empty array of MYSQL_FIELD
643
444
 */
644
 
GPtrArray *network_mysqld_proto_fielddefs_new(void) {
 
445
GPtrArray *network_mysqld_proto_fields_init(void) {
645
446
        GPtrArray *fields;
646
447
        
647
448
        fields = g_ptr_array_new();
655
456
 * @param fields  array of MYSQL_FIELD to free
656
457
 * @see network_mysqld_proto_field_free()
657
458
 */
658
 
void network_mysqld_proto_fielddefs_free(GPtrArray *fields) {
 
459
void network_mysqld_proto_fields_free(GPtrArray *fields) {
659
460
        guint i;
660
461
 
661
462
        for (i = 0; i < fields->len; i++) {
662
463
                MYSQL_FIELD *field = fields->pdata[i];
663
464
 
664
 
                if (field) network_mysqld_proto_fielddef_free(field);
 
465
                if (field) network_mysqld_proto_field_free(field);
665
466
        }
666
467
 
667
468
        g_ptr_array_free(fields, TRUE);
684
485
 * @param id      sequence-id of the packet
685
486
 * @return 0
686
487
 */
687
 
int network_mysqld_proto_set_packet_len(GString *_header, guint32 length) {
688
 
        unsigned char *header = (unsigned char *)_header->str;
689
 
 
690
 
        g_assert_cmpint(length, <=, PACKET_LEN_MAX);
 
488
int network_mysqld_proto_set_header(unsigned char *header, size_t length, unsigned char id) {
 
489
        g_assert(length <= PACKET_LEN_MAX);
691
490
 
692
491
        header[0] = (length >>  0) & 0xFF;
693
492
        header[1] = (length >>  8) & 0xFF;
694
493
        header[2] = (length >> 16) & 0xFF;
695
 
        
696
 
        return 0;
697
 
}
698
 
 
699
 
int network_mysqld_proto_set_packet_id(GString *_header, guint8 id) {
700
 
        unsigned char *header = (unsigned char *)_header->str;
701
 
 
702
494
        header[3] = id;
703
495
 
704
496
        return 0;
705
497
}
706
498
 
707
 
int network_mysqld_proto_append_packet_len(GString *_header, guint32 length) {
708
 
        return network_mysqld_proto_append_int24(_header, length);
709
 
}
710
 
 
711
 
int network_mysqld_proto_append_packet_id(GString *_header, guint8 id) {
712
 
        return network_mysqld_proto_append_int8(_header, id);
713
 
}
714
 
 
715
499
/**
716
500
 * decode the packet length from a packet header
717
501
 *
719
503
 * @return the packet length
720
504
 * @see network_mysqld_proto_set_header()
721
505
 */
722
 
guint32 network_mysqld_proto_get_packet_len(GString *_header) {
723
 
        unsigned char *header = (unsigned char *)_header->str;
724
 
 
 
506
size_t network_mysqld_proto_get_header(unsigned char *header) {
725
507
        return header[0] | header[1] << 8 | header[2] << 16;
726
508
}
727
509
 
728
510
/**
729
 
 * decode the packet length from a packet header
730
 
 *
731
 
 * @param header the first 3 bytes of the network packet
732
 
 * @return the packet length
733
 
 * @see network_mysqld_proto_set_header()
734
 
 */
735
 
guint8 network_mysqld_proto_get_packet_id(GString *_header) {
736
 
        unsigned char *header = (unsigned char *)_header->str;
737
 
 
738
 
        return header[3];
739
 
}
740
 
 
741
 
 
742
 
/**
743
511
 * append the variable-length integer to the packet
744
512
 *
745
513
 * @param packet  the MySQL network packet
834
602
 * @see network_mysqld_proto_append_int_len()
835
603
 */
836
604
int network_mysqld_proto_append_int8(GString *packet, guint8 num) {
837
 
        return network_mysqld_proto_append_int_len(packet, num, 1);
 
605
        return network_mysqld_proto_append_int_len(packet, num, sizeof(num));
838
606
}
839
607
 
840
608
/**
846
614
 * @see network_mysqld_proto_append_int_len()
847
615
 */
848
616
int network_mysqld_proto_append_int16(GString *packet, guint16 num) {
849
 
        return network_mysqld_proto_append_int_len(packet, num, 2);
850
 
}
851
 
 
852
 
/**
853
 
 * encode 24-bit integer in to a network packet
854
 
 *
855
 
 * @param packet  the MySQL network packet
856
 
 * @param num     integer to encode
857
 
 *
858
 
 * @see network_mysqld_proto_append_int_len()
859
 
 */
860
 
int network_mysqld_proto_append_int24(GString *packet, guint32 num) {
861
 
        return network_mysqld_proto_append_int_len(packet, num, 3);
862
 
}
863
 
 
 
617
        return network_mysqld_proto_append_int_len(packet, num, sizeof(num));
 
618
}
864
619
 
865
620
/**
866
621
 * encode 32-bit integer in to a network packet
871
626
 * @see network_mysqld_proto_append_int_len()
872
627
 */
873
628
int network_mysqld_proto_append_int32(GString *packet, guint32 num) {
874
 
        return network_mysqld_proto_append_int_len(packet, num, 4);
875
 
}
876
 
 
877
 
/**
878
 
 * encode 48-bit integer in to a network packet
879
 
 *
880
 
 * @param packet  the MySQL network packet
881
 
 * @param num     integer to encode
882
 
 *
883
 
 * @see network_mysqld_proto_append_int_len()
884
 
 */
885
 
int network_mysqld_proto_append_int48(GString *packet, guint64 num) {
886
 
        return network_mysqld_proto_append_int_len(packet, num, 6);
887
 
}
888
 
 
889
 
 
890
 
/**
891
 
 * encode 64-bit integer in to a network packet
892
 
 *
893
 
 * @param packet  the MySQL network packet
894
 
 * @param num     integer to encode
895
 
 *
896
 
 * @see network_mysqld_proto_append_int_len()
897
 
 */
898
 
int network_mysqld_proto_append_int64(GString *packet, guint64 num) {
899
 
        return network_mysqld_proto_append_int_len(packet, num, 8);
900
 
}
901
 
 
902
 
 
903
 
/**
904
 
 * hash the password as MySQL 4.1 and later assume
905
 
 *
906
 
 *   SHA1( password )
907
 
 *
908
 
 * @see network_mysqld_proto_scramble
909
 
 */
910
 
int network_mysqld_proto_password_hash(GString *response, const char *password, gsize password_len) {
911
 
        GChecksum *cs;
912
 
 
913
 
        /* first round: SHA1(password) */
914
 
        cs = g_checksum_new(G_CHECKSUM_SHA1);
915
 
 
916
 
        g_checksum_update(cs, (guchar *)password, password_len);
917
 
 
918
 
        g_string_set_size(response, g_checksum_type_get_length(G_CHECKSUM_SHA1));
919
 
        response->len = response->allocated_len; /* will be overwritten with the right value in the next step */
920
 
        g_checksum_get_digest(cs, (guchar *)response->str, &(response->len));
921
 
 
922
 
        g_checksum_free(cs);
923
 
        
924
 
        return 0;
925
 
}
926
 
 
927
 
/**
928
 
 * scramble the hashed password with the challenge
929
 
 *
930
 
 * @param response         dest 
931
 
 * @param challenge        the challenge string as sent by the mysql-server
932
 
 * @param challenge_len    length of the challenge
933
 
 * @param hashed_password  hashed password
934
 
 * @param hashed_password_len length of the hashed password
935
 
 *
936
 
 * @see network_mysqld_proto_password_hash
937
 
 */
938
 
int network_mysqld_proto_password_scramble(GString *response,
939
 
                const char *challenge, gsize challenge_len,
940
 
                const char *hashed_password, gsize hashed_password_len) {
941
 
        int i;
942
 
        GChecksum *cs;
943
 
        GString *step2;
944
 
 
945
 
        g_return_val_if_fail(NULL != challenge, -1);
946
 
        g_return_val_if_fail(20 == challenge_len, -1);
947
 
        g_return_val_if_fail(NULL != hashed_password, -1);
948
 
        g_return_val_if_fail(20 == hashed_password_len, -1);
949
 
 
950
 
        /**
951
 
         * we have to run
952
 
         *
953
 
         *   XOR( SHA1(password), SHA1(challenge + SHA1(SHA1(password)))
954
 
         *
955
 
         * where SHA1(password) is the hashed_password and
956
 
         *       challenge      is ... challenge
957
 
         *
958
 
         *   XOR( hashed_password, SHA1(challenge + SHA1(hashed_password)))
959
 
         *
960
 
         */
961
 
 
962
 
        /* 1. SHA1(hashed_password) */
963
 
        step2 = g_string_new(NULL);
964
 
        network_mysqld_proto_password_hash(step2, hashed_password, hashed_password_len);
965
 
 
966
 
        /* 2. SHA1(challenge + SHA1(hashed_password) */
967
 
        cs = g_checksum_new(G_CHECKSUM_SHA1);
968
 
        g_checksum_update(cs, (guchar *)challenge, challenge_len);
969
 
        g_checksum_update(cs, (guchar *)step2->str, step2->len);
970
 
        
971
 
        g_string_set_size(response, g_checksum_type_get_length(G_CHECKSUM_SHA1));
972
 
        response->len = response->allocated_len;
973
 
        g_checksum_get_digest(cs, (guchar *)response->str, &(response->len));
974
 
        
975
 
        g_checksum_free(cs);
976
 
 
977
 
        /* XOR the hashed_password with SHA1(challenge + SHA1(hashed_password)) */
978
 
        for (i = 0; i < 20; i++) {
979
 
                response->str[i] = (guchar)response->str[i] ^ (guchar)hashed_password[i];
980
 
        }
981
 
 
982
 
        g_string_free(step2, TRUE);
983
 
 
984
 
        return 0;
985
 
}
986
 
 
987
 
/**
988
 
 * unscramble the auth-response and get the hashed-password
989
 
 *
990
 
 * @param hashed_password  dest of the hashed password
991
 
 * @param challenge        the challenge string as sent by the mysql-server
992
 
 * @param challenge_len    length of the challenge
993
 
 * @param response         auth response as sent by the client 
994
 
 * @param response_len     length of response
995
 
 * @param double_hashed    the double hashed password as stored in the mysql.server table (without * and unhexed)
996
 
 * @param double_hashed_len length of double_hashed
997
 
 *
998
 
 * @see network_mysqld_proto_scramble
999
 
 */
1000
 
int network_mysqld_proto_password_unscramble(
1001
 
                GString *hashed_password,
1002
 
                const char *challenge, gsize challenge_len,
1003
 
                const char *response, gsize response_len,
1004
 
                const char *double_hashed, gsize double_hashed_len) {
1005
 
        int i;
1006
 
        GChecksum *cs;
1007
 
 
1008
 
        g_return_val_if_fail(NULL != response, FALSE);
1009
 
        g_return_val_if_fail(20 == response_len, FALSE);
1010
 
        g_return_val_if_fail(NULL != challenge, FALSE);
1011
 
        g_return_val_if_fail(20 == challenge_len, FALSE);
1012
 
        g_return_val_if_fail(NULL != double_hashed, FALSE);
1013
 
        g_return_val_if_fail(20 == double_hashed_len, FALSE);
1014
 
 
1015
 
        /**
1016
 
         * to check we have to:
1017
 
         *
1018
 
         *   hashed_password = XOR( response, SHA1(challenge + double_hashed))
1019
 
         *   double_hashed == SHA1(hashed_password)
1020
 
         *
1021
 
         * where SHA1(password) is the hashed_password and
1022
 
         *       challenge      is ... challenge
1023
 
         *       response       is the response of the client
1024
 
         *
1025
 
         *   XOR( hashed_password, SHA1(challenge + SHA1(hashed_password)))
1026
 
         *
1027
 
         */
1028
 
 
1029
 
 
1030
 
        /* 1. SHA1(challenge + double_hashed) */
1031
 
        cs = g_checksum_new(G_CHECKSUM_SHA1);
1032
 
        g_checksum_update(cs, (guchar *)challenge, challenge_len);
1033
 
        g_checksum_update(cs, (guchar *)double_hashed, double_hashed_len);
1034
 
        
1035
 
        g_string_set_size(hashed_password, g_checksum_type_get_length(G_CHECKSUM_SHA1));
1036
 
        hashed_password->len = hashed_password->allocated_len;
1037
 
        g_checksum_get_digest(cs, (guchar *)hashed_password->str, &(hashed_password->len));
1038
 
        
1039
 
        g_checksum_free(cs);
1040
 
        
1041
 
        /* 2. XOR the response with SHA1(challenge + SHA1(hashed_password)) */
1042
 
        for (i = 0; i < 20; i++) {
1043
 
                hashed_password->str[i] = (guchar)response[i] ^ (guchar)hashed_password->str[i];
1044
 
        }
1045
 
 
1046
 
        return 0;
1047
 
}
1048
 
 
1049
 
/**
1050
 
 * check if response and challenge match a double-hashed password
1051
 
 *
1052
 
 * @param challenge        the challenge string as sent by the mysql-server
1053
 
 * @param challenge_len    length of the challenge
1054
 
 * @param response         auth response as sent by the client 
1055
 
 * @param response_len     length of response
1056
 
 * @param double_hashed    the double hashed password as stored in the mysql.server table (without * and unhexed)
1057
 
 * @param double_hashed_len length of double_hashed
1058
 
 *
1059
 
 * @see network_mysqld_proto_scramble
1060
 
 */
1061
 
gboolean network_mysqld_proto_password_check(
1062
 
                const char *challenge, gsize challenge_len,
1063
 
                const char *response, gsize response_len,
1064
 
                const char *double_hashed, gsize double_hashed_len) {
1065
 
 
1066
 
        GString *hashed_password, *step2;
1067
 
        gboolean is_same;
1068
 
 
1069
 
        g_return_val_if_fail(NULL != response, FALSE);
1070
 
        g_return_val_if_fail(20 == response_len, FALSE);
1071
 
        g_return_val_if_fail(NULL != challenge, FALSE);
1072
 
        g_return_val_if_fail(20 == challenge_len, FALSE);
1073
 
        g_return_val_if_fail(NULL != double_hashed, FALSE);
1074
 
        g_return_val_if_fail(20 == double_hashed_len, FALSE);
1075
 
 
1076
 
        hashed_password = g_string_new(NULL);
1077
 
 
1078
 
        network_mysqld_proto_password_unscramble(hashed_password, 
1079
 
                        challenge, challenge_len,
1080
 
                        response, response_len,
1081
 
                        double_hashed, double_hashed_len);
1082
 
 
1083
 
        /* 3. SHA1(hashed_password) */
1084
 
        step2 = g_string_new(NULL);
1085
 
        network_mysqld_proto_password_hash(step2, S(hashed_password));
1086
 
        
1087
 
        /* 4. the result of 3 should be the same what we got from the mysql.user table */
1088
 
        is_same = strleq(S(step2), double_hashed, double_hashed_len);
1089
 
 
1090
 
        g_string_free(step2, TRUE);
1091
 
        g_string_free(hashed_password, TRUE);
1092
 
 
1093
 
        return is_same;
1094
 
}
1095
 
 
1096
 
 
1097
 
network_packet *network_packet_new(void) {
1098
 
        network_packet *packet;
1099
 
 
1100
 
        packet = g_new0(network_packet, 1);
1101
 
 
1102
 
        return packet;
1103
 
}
1104
 
 
1105
 
void network_packet_free(network_packet *packet) {
1106
 
        if (!packet) return;
1107
 
 
1108
 
        g_free(packet);
1109
 
}
1110
 
 
1111
 
int network_mysqld_proto_skip_network_header(network_packet *packet) {
1112
 
        return network_mysqld_proto_skip(packet, NET_HEADER_SIZE);
 
629
        return network_mysqld_proto_append_int_len(packet, num, sizeof(num));
1113
630
}
1114
631
 
1115
632
/*@}*/