~ubuntu-branches/ubuntu/feisty/jnettop/feisty

« back to all changes in this revision

Viewing changes to juiadisplay.c

  • Committer: Bazaar Package Importer
  • Author(s): Ari Pollak
  • Date: 2006-04-19 22:44:18 UTC
  • mfrom: (1.1.3 upstream) (3.1.1 etch)
  • Revision ID: james.westby@ubuntu.com-20060419224418-3w4fik5kkveqbuz1
Tags: 0.12.0-2
Fix a small display bug that wasn't fixed in the previous revision

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 *    jnettop, network online traffic visualiser
 
3
 *    Copyright (C) 2002-2006 Jakub Skopal
 
4
 *
 
5
 *    This program is free software; you can redistribute it and/or modify
 
6
 *    it under the terms of the GNU General Public License as published by
 
7
 *    the Free Software Foundation; either version 2 of the License, or
 
8
 *    (at your option) any later version.
 
9
 *
 
10
 *    This program is distributed in the hope that it will be useful,
 
11
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
 *    GNU General Public License for more details.
 
14
 *
 
15
 *    You should have received a copy of the GNU General Public License
 
16
 *    along with this program; if not, write to the Free Software
 
17
 *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
18
 *
 
19
 *    $Header: /cvsroot/jnettop/jnettop/juiadisplay.c,v 1.1 2006/04/11 15:21:06 merunka Exp $
 
20
 *
 
21
 */
 
22
 
 
23
#include "jbase.h"
 
24
#include "jdevice.h"
 
25
#include "jprocessor.h"
 
26
#include "jconfig.h"
 
27
#include "jutil.h"
 
28
#include "juiadisplay.h"
 
29
 
 
30
#ifdef ENABLE_UIA
 
31
 
 
32
#define LISTEN_ERROR_ANSWER                     "listen:ASCII:NAK:Error compiling rule: syntax error\n\n"
 
33
#define ERROR_MAXIMUM_TIMEOUT_EXPIRED           "\n\n"
 
34
#define GET_REQUEST_END_BOUNDARY                "\n"
 
35
 
 
36
#define MAXRECV                                 32769
 
37
#define MAX_COMMAND_TIMEOUT_MINUTES             5
 
38
#define SMALL_WAIT                              100
 
39
#define DEFAULT_LINE_COUNT                      15
 
40
 
 
41
GMutex          *displayStreamsMutex;
 
42
jbase_stream    **displayStreams = NULL;
 
43
int             displayStreamsCount = 0;
 
44
gboolean        bHaveData = FALSE;
 
45
int             nLineCount = DEFAULT_LINE_COUNT;
 
46
 
 
47
gboolean        onoffPackets;
 
48
gboolean        onoffBitValues;
 
49
 
 
50
static void processStreamsFunc(GPtrArray * streamArray) {
 
51
        guint           i,j;
 
52
        guint           lines, oldLines;
 
53
        jbase_stream    **streams,**oldStreams;
 
54
 
 
55
        streams = g_new0(jbase_stream *, nLineCount);
 
56
        
 
57
        for (i=0,j=0; i<streamArray->len && j<nLineCount; i++) {
 
58
                jbase_stream *s = (jbase_stream *)g_ptr_array_index(streamArray, i);
 
59
                if (s->dead > 5) {
 
60
                        continue;
 
61
                }
 
62
                s->displayed ++;
 
63
                streams[j++] = s;
 
64
        }
 
65
 
 
66
        lines = j;
 
67
 
 
68
        g_mutex_lock(displayStreamsMutex);
 
69
        if(lines > 0)
 
70
                bHaveData = TRUE;
 
71
        oldStreams = displayStreams;
 
72
        oldLines   = displayStreamsCount;
 
73
        displayStreams = streams;
 
74
        displayStreamsCount = lines;
 
75
        g_mutex_unlock(displayStreamsMutex);
 
76
 
 
77
        for (i=0; i<oldLines; i++) {
 
78
                oldStreams[i]->displayed --;
 
79
        }
 
80
 
 
81
        if (oldStreams)
 
82
                g_free(oldStreams);
 
83
}
 
84
 
 
85
static gchar * get_next_token_colon_delim(gchar ** text){
 
86
        gchar * tmp = NULL;
 
87
        if(!text)
 
88
                return NULL;
 
89
 
 
90
        tmp = strsep(text, ":");
 
91
        if(tmp && *tmp != '\0'){
 
92
                return strdup(tmp);
 
93
        }else{
 
94
                return NULL;
 
95
        }
 
96
}
 
97
 
 
98
void    doWriteFormatedNetworkStreams(pid_t nSessionID, gulong lUSecsWaited) {
 
99
        int i;
 
100
        gchar buffer[32768];
 
101
        gchar srcport[10], dstport[10], srcbps[10], dstbps[10], bps[10];
 
102
 
 
103
        debug(LOG_DEBUG, "streams count %d", displayStreamsCount);
 
104
 
 
105
        // dump out the totals line...
 
106
        jutil_formatNumber(onoffPackets?jprocessor_Stats.totalPPS:(onoffBitValues?8:1)*jprocessor_Stats.totalBPS, onoffPackets, bps, 6);
 
107
        g_strlcat(bps, "/s", sizeof(bps));
 
108
        jutil_formatNumber(onoffPackets?jprocessor_Stats.totalSrcPPS:(onoffBitValues?8:1)*jprocessor_Stats.totalSrcBPS, onoffPackets, srcbps, 6);
 
109
        g_strlcat(srcbps, "/s", sizeof(srcbps));
 
110
        jutil_formatNumber(onoffPackets?jprocessor_Stats.totalDstPPS:(onoffBitValues?8:1)*jprocessor_Stats.totalDstBPS, onoffPackets, dstbps, 6);
 
111
        g_strlcat(dstbps, "/s", sizeof(dstbps));
 
112
 
 
113
        sprintf(buffer, "get:ASCII:%d:%d:ACK:TOTAL:::::%s:%s:%s\n", nSessionID, (int)lUSecsWaited, srcbps, dstbps, bps);
 
114
        //debug(LOG_DEBUG, "sending %d characters '%s'", strlen(buffer), buffer);
 
115
        printf("%s", buffer);
 
116
 
 
117
        for (i=0; i< displayStreamsCount; i++) {
 
118
                gchar srcaddr[INET6_ADDRSTRLEN + 1], dstaddr[INET6_ADDRSTRLEN + 1];
 
119
                gchar total[10], totalsrc[10], totaldst[10];
 
120
                uint tmp;
 
121
                gchar linebuffer[1024];
 
122
                const gchar *psrcaddr, *pdstaddr;
 
123
                jbase_stream *s = displayStreams[i];
 
124
                tmp = onoffPackets ? s->totalpps : (onoffBitValues?8:1)*s->totalbps;
 
125
                jutil_formatNumber(tmp, onoffPackets, bps, 6);
 
126
                g_strlcat(bps, "/s", sizeof(bps));
 
127
                tmp = onoffPackets ? s->srcpps : (onoffBitValues?8:1)*s->srcbps;
 
128
                jutil_formatNumber(tmp, onoffPackets, srcbps, 6);
 
129
                g_strlcat(srcbps, "/s", sizeof(srcbps));
 
130
                tmp = onoffPackets ? s->dstpps : (onoffBitValues?8:1)*s->dstbps;
 
131
                jutil_formatNumber(tmp, onoffPackets, dstbps, 6);
 
132
                g_strlcat(dstbps, "/s", sizeof(dstbps));
 
133
                jutil_formatNumber(onoffPackets ? s->totalpackets : s->totalbytes, onoffPackets, total, 6);
 
134
                jutil_formatNumber(onoffPackets ? s->srcpackets : s->srcbytes, onoffPackets, totalsrc, 6);
 
135
                jutil_formatNumber(onoffPackets ? s->dstpackets : s->dstbytes, onoffPackets, totaldst, 6);
 
136
                jutil_Address2String(JBASE_AF(s->proto), &s->src, srcaddr, INET6_ADDRSTRLEN);
 
137
                if (s->srcresolv == NULL || s->srcresolv->name == NULL) {
 
138
                        psrcaddr = srcaddr;
 
139
                } else {
 
140
                        psrcaddr = s->srcresolv->name;
 
141
                }
 
142
                jutil_Address2String(JBASE_AF(s->proto), &s->dst, dstaddr, INET6_ADDRSTRLEN);
 
143
                if (s->dstresolv == NULL || s->dstresolv->name == NULL) {
 
144
                        pdstaddr = dstaddr;
 
145
                } else {
 
146
                        pdstaddr = s->dstresolv->name;
 
147
                }
 
148
                if (s->srcport == -1)
 
149
                        strcpy(srcport, "AGGR.");
 
150
                else
 
151
                        sprintf(srcport, "%d", s->srcport);
 
152
                if (s->dstport == -1)
 
153
                        strcpy(dstport, "AGGR.");
 
154
                else
 
155
                        sprintf(dstport, "%d", s->dstport);
 
156
                sprintf(linebuffer, "%s:%s", psrcaddr, pdstaddr);
 
157
                
 
158
                sprintf(buffer, "get:ASCII:%d:%d:ACK:%s:%s:%s:%s:%s:%s:%s:%s\n", nSessionID, (int)lUSecsWaited, srcaddr, srcport, JBASE_PROTOCOLS[s->proto], dstaddr, dstport, srcbps, dstbps, bps);
 
159
                //debug(LOG_DEBUG, "sending %d characters '%s'", strlen(buffer), buffer);
 
160
                printf("%s", buffer);
 
161
        }
 
162
        printf(GET_REQUEST_END_BOUNDARY);
 
163
}
 
164
 
 
165
static GTimeVal timeNow(){
 
166
        GTimeVal timeNow;
 
167
        g_get_current_time(&timeNow);
 
168
        return timeNow;
 
169
}
 
170
 
 
171
static void networkConnectionLoop(){
 
172
        // get our pid...
 
173
        pid_t ourpid = getpid();
 
174
        gboolean bExit = FALSE;
 
175
 
 
176
        // setup timer here...
 
177
        GTimeVal        commandTimeout;
 
178
        g_get_current_time(&commandTimeout);
 
179
 
 
180
        while(!bExit && (timeNow().tv_sec - commandTimeout.tv_sec < MAX_COMMAND_TIMEOUT_MINUTES * 60)){
 
181
                // make sure we don't block forever...
 
182
                int nSelectReturn = 0;
 
183
                fd_set listenSet;
 
184
                struct timeval tm;
 
185
                tm.tv_sec = 10;  // wait ten seconds...
 
186
                tm.tv_usec = 0;
 
187
                FD_ZERO(&listenSet);
 
188
                FD_SET(fileno(stdin), &listenSet);
 
189
 
 
190
                nSelectReturn = select(fileno(stdin)+1, &listenSet, NULL, NULL, &tm);
 
191
 
 
192
                if(nSelectReturn > 0){
 
193
                        if(FD_ISSET(fileno(stdin), &listenSet)){
 
194
                                int nDataRecievedCount = 0;
 
195
                                gchar data[ MAXRECV + 1];
 
196
                                bzero(data, MAXRECV);
 
197
                                nDataRecievedCount = read(fileno(stdin), data, MAXRECV - 2);
 
198
                                                                
 
199
                                if(nDataRecievedCount > 0){
 
200
                                        gchar * strPid = NULL;
 
201
                                        gchar * strType = NULL;
 
202
                                        gchar * strMethod = NULL;
 
203
                                        gchar * strMaxWaitUSecs = NULL;
 
204
                                        gchar * cpdata = data;
 
205
                                        int nIndex;
 
206
 
 
207
                                        // make sure we end the string...
 
208
                                        data[nDataRecievedCount] = ':';
 
209
                                        data[nDataRecievedCount + 1] = '\0';
 
210
 
 
211
                                        // remove any control charaters...
 
212
                                        for(nIndex = 0; nIndex<nDataRecievedCount; nIndex++){
 
213
                                                if(iscntrl(data[nIndex])){
 
214
                                                        // this is a control character - change it to a :
 
215
                                                        data[nIndex] = ':';
 
216
                                                }
 
217
                                        }
 
218
 
 
219
                                        strMethod = get_next_token_colon_delim(&cpdata);
 
220
                                        strType = get_next_token_colon_delim(&cpdata);
 
221
                                        strPid = get_next_token_colon_delim(&cpdata);
 
222
                                        strMaxWaitUSecs = get_next_token_colon_delim(&cpdata);
 
223
 
 
224
                                        if(strPid && ourpid != atoi(strPid)){
 
225
                                                // error - key id not correct
 
226
                                                debug(LOG_DEBUG, "Invalid session - %s given, %d expected", strPid, ourpid);
 
227
                                                printf(LISTEN_ERROR_ANSWER);
 
228
                                        }else if(strncmp(strMethod, "end", strlen("end")) == 0){
 
229
                                                gchar endAnswer[16384];
 
230
                                                g_get_current_time(&commandTimeout);
 
231
                                                // recived an end command
 
232
                                                sprintf(endAnswer, "end:ASCII:%d:ACK\n\n", ourpid);
 
233
                                                debug(LOG_DEBUG, "Sending '%s'", endAnswer);
 
234
                                                printf("%s", endAnswer);
 
235
                                                bExit = TRUE;
 
236
                                        }else if(strncmp(data, "get", strlen("get")) == 0){
 
237
                                                gulong lMicroSeconds = 0;
 
238
                                                gulong lWaitedSeconds = 0;
 
239
                                                g_get_current_time(&commandTimeout);
 
240
                                                // see how long we should wait...
 
241
                                
 
242
                                                if(strMaxWaitUSecs)
 
243
                                                        lMicroSeconds = atol(strMaxWaitUSecs);
 
244
                                        
 
245
 
 
246
                                                debug(LOG_DEBUG, "waiting for data - will wait %d useconds", lMicroSeconds);
 
247
                                                while(!bHaveData && lWaitedSeconds < lMicroSeconds){
 
248
                                                        lWaitedSeconds += SMALL_WAIT;
 
249
                                                        g_usleep(SMALL_WAIT);
 
250
                                                }       
 
251
 
 
252
                                                debug(LOG_DEBUG, "waited %d useconds - bHaveData - %d", lWaitedSeconds, bHaveData);
 
253
 
 
254
                                                if(bHaveData){
 
255
                                                        // recieved get request
 
256
                                                        // lock the mutex...
 
257
                                                        g_mutex_lock(displayStreamsMutex);
 
258
                                                        doWriteFormatedNetworkStreams(ourpid, lWaitedSeconds);
 
259
                                                        g_mutex_unlock(displayStreamsMutex);
 
260
                                                }else{
 
261
                                                        debug(LOG_DEBUG, "Timed out waiting for data - sending '%s'", ERROR_MAXIMUM_TIMEOUT_EXPIRED);
 
262
                                                        printf(ERROR_MAXIMUM_TIMEOUT_EXPIRED);
 
263
                                                }
 
264
                                        }
 
265
                                        fflush(NULL);
 
266
                                } // nDataRecievedCount
 
267
                        } // if(FD_ISSET)
 
268
                } // select != -1
 
269
        } // while (!bExit) 
 
270
        if(!(timeNow().tv_sec - commandTimeout.tv_sec  < MAX_COMMAND_TIMEOUT_MINUTES * 60)){
 
271
                // send timeout error
 
272
                debug(LOG_WARNING, "Timed out while waiting for get/end command - waited %d seconds", timeNow().tv_sec - commandTimeout.tv_sec);
 
273
                printf(ERROR_MAXIMUM_TIMEOUT_EXPIRED);
 
274
        }
 
275
}
 
276
 
 
277
gboolean parseListenLineAndConfig(){
 
278
        // wait here for a listen command with parameters...
 
279
        gboolean bInitialized = FALSE;
 
280
 
 
281
        static gchar data[ MAXRECV + 1];
 
282
 
 
283
        pid_t ourpid = getpid();
 
284
        gboolean bBitValuesBackup = onoffBitValues;
 
285
        GTimeVal        commandTimeout;
 
286
 
 
287
        // backup the device name
 
288
        gchar strDeviceBackup[30];
 
289
        strDeviceBackup[0] = '\0';
 
290
 
 
291
        if(jconfig_Settings.deviceName)
 
292
                strcpy(strDeviceBackup, jconfig_Settings.deviceName);
 
293
 
 
294
 
 
295
        // setup timer here...
 
296
        g_get_current_time(&commandTimeout);
 
297
 
 
298
 
 
299
        // keep going while we have not initialized, aren't shutting down, and haven't timed out
 
300
        while(!bInitialized && (timeNow().tv_sec - commandTimeout.tv_sec < MAX_COMMAND_TIMEOUT_MINUTES * 60)){
 
301
                // make sure we don't block forever...
 
302
                fd_set listenSet;
 
303
                struct timeval tm;
 
304
                int nSelectReturn = 0;
 
305
                FD_ZERO(&listenSet);
 
306
                FD_SET(fileno(stdin), &listenSet);
 
307
                tm.tv_sec = 10;  // wait ten seconds...
 
308
                tm.tv_usec = 0;
 
309
                nSelectReturn = select(fileno(stdin)+1, &listenSet, NULL, NULL, &tm);
 
310
 
 
311
                if(nSelectReturn != -1){
 
312
                // try to read stdout...
 
313
                        if(FD_ISSET(fileno(stdin), &listenSet)){
 
314
                                int nDataRecievedCount = read(fileno(stdin), data, MAXRECV - 2);
 
315
                                                                                        
 
316
                                if(nDataRecievedCount > 0){
 
317
                                        int nIndex;
 
318
                                        gchar * strMethod = NULL;
 
319
                                        gchar * strType = NULL;
 
320
                                        gchar * strDevice = NULL;
 
321
                                        gchar * strBits = NULL;
 
322
                                        gchar * strFilter = NULL;
 
323
                                        gchar * strMaxLines = NULL;
 
324
                                        gchar * cpdata = data; //(gchar *) strdup(data);
 
325
 
 
326
                                        // make sure we end the string...
 
327
                                        data[nDataRecievedCount] = ':';
 
328
                                        data[nDataRecievedCount+1] = '\0';
 
329
 
 
330
                                        // clear the bpf filter...
 
331
                                        JCONFIG_BPFFILTERS_SETNONE;
 
332
                                        // reset the device name
 
333
                                        if(strlen(strDeviceBackup) > 0){
 
334
                                                strcpy(jconfig_Settings.deviceName, strDeviceBackup);
 
335
                                        }       
 
336
                                        // reset the bit values
 
337
                                        onoffBitValues = bBitValuesBackup;
 
338
 
 
339
                                        // remove any control charaters...
 
340
                                        for(nIndex = 0; nIndex<nDataRecievedCount; nIndex++){
 
341
                                                if(iscntrl(data[nIndex])){
 
342
                                                        // this is a control character - change it to a :
 
343
                                                        data[nIndex] = ':';
 
344
                                                }       
 
345
                                        }       
 
346
 
 
347
                                        strMethod = get_next_token_colon_delim(&cpdata);
 
348
                                        strType = get_next_token_colon_delim(&cpdata);
 
349
                                        strDevice = get_next_token_colon_delim(&cpdata);
 
350
                                        strBits = get_next_token_colon_delim(&cpdata);
 
351
                                        strFilter = get_next_token_colon_delim(&cpdata);
 
352
                                        strMaxLines = get_next_token_colon_delim(&cpdata);
 
353
 
 
354
                                        if(strncmp(strMethod, "listen", strlen("listen")) == 0){
 
355
                                                gchar firstAnswer[16384];
 
356
                                                int nStrMaxLines = 0;
 
357
                                                gboolean bIsRequestGood = TRUE;
 
358
 
 
359
                                                if(strMaxLines)
 
360
                                                        nStrMaxLines = atoi(strMaxLines);
 
361
 
 
362
                                                debug(LOG_DEBUG, "Got listen request");
 
363
                                                // got a listen request
 
364
                
 
365
                
 
366
                                                if(strFilter){
 
367
                                                        // set the filter...
 
368
                                                        const char * strFilterResult = jutil_ValidateBPFFilter(strFilter);
 
369
                                                        if(!strFilterResult){
 
370
                                                                // good - set filter
 
371
                                                                JCONFIG_BPFFILTERS_SETSELECTEDFILTER(JCONFIG_BPFFILTERS_LEN);
 
372
                                                                jconfig_AddBpfFilter("<fromlisten>", strFilter);
 
373
                                                        }else{
 
374
                                                                debug(LOG_WARNING, "strFilter is BAD - %s", strFilterResult);
 
375
                                                                bIsRequestGood = FALSE;
 
376
                                                                printf(LISTEN_ERROR_ANSWER);
 
377
                                                        }
 
378
                                                }       
 
379
                
 
380
                                                if(strDevice){
 
381
                                                        debug(LOG_DEBUG, "Setting device name '%s'", strDevice);
 
382
                                                        // set the device...
 
383
                                                        jconfig_Settings.deviceName = strDevice;
 
384
                                                }
 
385
                
 
386
                                                if(strBits && strcmp(strBits, "bits") == 0){
 
387
                                                        debug(LOG_DEBUG, "Setting bits");
 
388
                                                        // set bits...
 
389
                                                        onoffBitValues = TRUE;
 
390
                                                }       
 
391
                
 
392
                                
 
393
                                                if(bIsRequestGood){
 
394
                                                        if(nStrMaxLines != 0)
 
395
                                                                nLineCount = nStrMaxLines;
 
396
 
 
397
                                                        sprintf(firstAnswer, "listen:ASCII:%d:ACK:%s:%s:%s:%s\n\n", ourpid, strDevice, strBits, strFilter, strMaxLines);
 
398
                                                        debug(LOG_DEBUG,"sending '%s'", firstAnswer);
 
399
                                                        printf(firstAnswer);
 
400
                                                        bInitialized = TRUE;
 
401
                                                } else {
 
402
                                                        printf(LISTEN_ERROR_ANSWER);
 
403
                                                }
 
404
                                                fflush(NULL);
 
405
                                                // reset the timeout timer..
 
406
                                                g_get_current_time(&commandTimeout);
 
407
                                        }
 
408
                                } // if recv
 
409
                        } // if FD_ISSET        
 
410
                } // select     
 
411
        } // while      
 
412
        if(!(timeNow().tv_sec - commandTimeout.tv_sec < MAX_COMMAND_TIMEOUT_MINUTES * 60)){
 
413
                // send timeout error
 
414
                debug(LOG_NOTICE, "Timed out while waiting for listen command - waited %d seconds", timeNow().tv_sec - commandTimeout.tv_sec);
 
415
                printf(ERROR_MAXIMUM_TIMEOUT_EXPIRED);fflush(NULL);
 
416
        }
 
417
        return bInitialized;
 
418
}
 
419
 
 
420
static gboolean juiadisplay_PreSetup() {
 
421
        setvbuf(stdin, NULL, _IOLBF, 0);
 
422
        setvbuf(stdout, NULL, _IOLBF, 0);
 
423
        return parseListenLineAndConfig();
 
424
}
 
425
 
 
426
static void juiadisplay_Setup() {
 
427
        displayStreamsMutex = g_mutex_new();
 
428
 
 
429
        jprocessor_SetProcessStreamsFunc((ProcessStreamsFunc) processStreamsFunc);
 
430
        onoffBitValues = FALSE;
 
431
        onoffPackets = FALSE;
 
432
}
 
433
 
 
434
static void juiadisplay_PreRun() {
 
435
}
 
436
 
 
437
static gboolean juiadisplay_Run() {
 
438
        networkConnectionLoop();
 
439
        return FALSE;
 
440
}
 
441
 
 
442
static void juiadisplay_Shutdown() {
 
443
}
 
444
 
 
445
static void juiadisplay_DrawStatus(const gchar *msg) {
 
446
}
 
447
 
 
448
static int juiadisplay_ProcessArgument(const gchar **arg, int argc) {
 
449
        if (!strcmp(*arg, "-b") || !strcmp(*arg, "--bit-units")) {
 
450
                onoffBitValues = TRUE;
 
451
                return 1;
 
452
        }
 
453
        return 0;
 
454
}
 
455
 
 
456
jbase_display   juiadisplay_Functions = {
 
457
        TRUE,
 
458
        juiadisplay_PreSetup,
 
459
        juiadisplay_Setup,
 
460
        juiadisplay_PreRun,
 
461
        juiadisplay_Run,
 
462
        juiadisplay_Shutdown,
 
463
        juiadisplay_DrawStatus,
 
464
        juiadisplay_ProcessArgument
 
465
};
 
466
 
 
467
#else
 
468
 
 
469
jbase_display   juiadisplay_Functions = { FALSE };
 
470
 
 
471
#endif