blob: 394d31c9d922d9029df918812afad329f32f30a7 [file] [log] [blame]
Alexandre Lision51140e12013-12-02 10:54:09 -05001// Test ZRTP extension for ccRTP
2//
3// Copyright (C) 2008 Werner Dittmann <Werner.Dittmann@t-online.de>
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 3 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#include <cstdlib>
20#include <map>
21#include <libzrtpcpp/zrtpccrtp.h>
22#include <libzrtpcpp/ZrtpUserCallback.h>
23
24using namespace ost;
25using namespace std;
26using namespace GnuZrtpCodes;
27
28class PacketsPattern
29{
30public:
31 inline const InetHostAddress&
32 getDestinationAddress() const
33 { return destinationAddress; }
34
35 inline const tpport_t
36 getDestinationPort() const
37 { return destinationPort; }
38
39 uint32
40 getPacketsNumber() const
41 { return packetsNumber; }
42
43 uint32
44 getSsrc() const
45 { return 0xdeadbeef; }
46
47 const unsigned char*
48 getPacketData(uint32 i)
49 { return data[i%2]; }
50
51 const size_t
52 getPacketSize(uint32 i)
53 { return strlen((char*)data[i%2]) + 1 ; }
54
55private:
56 static const InetHostAddress destinationAddress;
57 static const uint16 destinationPort = 5002;
58 static const uint32 packetsNumber = 10;
59 static const uint32 packetsSize = 12;
60 static const unsigned char* data[];
61};
62
63const InetHostAddress PacketsPattern::destinationAddress =
64 InetHostAddress("localhost");
65
66const unsigned char* PacketsPattern::data[] = {
67 (unsigned char*)"0123456789\n",
68 (unsigned char*)"987654321\n"
69};
70
71PacketsPattern pattern;
72
73class ExtZrtpSession : public SymmetricZRTPSession {
74// ExtZrtpSession(InetMcastAddress& ima, tpport_t port) :
75// RTPSession(ima,port) {}
76//
77// ExtZrtpSession(InetHostAddress& ia, tpport_t port) :
78// RTPSession(ia,port) {}
79
80public:
81 ExtZrtpSession(uint32 ssrc, const InetHostAddress& ia) :
82 SingleThreadRTPSession(ssrc, ia){
83 cout << "Extended" << endl;
84 }
85
86 ExtZrtpSession(uint32 ssrc, const InetHostAddress& ia, tpport_t dataPort) :
87 SingleThreadRTPSession(ssrc, ia, dataPort) {
88 cout << "Extended" << endl;
89 }
90
91 ExtZrtpSession(const InetHostAddress& ia, tpport_t dataPort) :
92 SingleThreadRTPSession(ia, dataPort) {
93 cout << "Extended" << endl;
94 }
95
96 void onGotGoodbye(const SyncSource& source, const std::string& reason)
97 {
98 cout << "I got a Goodbye packet from "
99 << hex << (int)source.getID() << "@"
100 << dec
101 << source.getNetworkAddress() << ":"
102 << source.getControlTransportPort() << endl;
103 cout << " Goodbye reason: \"" << reason << "\"" << endl;
104 }
105 // redefined from QueueRTCPManager
106 void onGotRR(SyncSource& source, RecvReport& RR, uint8 blocks)
107 {
108 SingleThreadRTPSession::onGotRR(source,RR,blocks);
109 cout << "I got an RR RTCP report from "
110 << hex << (int)source.getID() << "@"
111 << dec
112 << source.getNetworkAddress() << ":"
113 << source.getControlTransportPort() << endl;
114 }
115};
116
117
118/**
119 * SymmetricZRTPSession in non-security mode (RTPSession compatible).
120 *
121 * The next two classes show how to use <code>SymmetricZRTPSession</code>
122 * in the same way as <code>RTPSession</code>. This is straightforward,
123 * just don't do any configuration or initialization.
124 */
125class SendPacketTransmissionTest: public Thread, public TimerPort {
126public:
127 void
128 run() {
129 doTest();
130 }
131
132 int doTest() {
133 // should be valid?
134 //RTPSession tx();
135 ExtZrtpSession tx(pattern.getSsrc(), InetHostAddress("localhost"));
136// SymmetricZRTPSession tx(pattern.getSsrc(), InetHostAddress("localhost"));
137 tx.setSchedulingTimeout(10000);
138 tx.setExpireTimeout(1000000);
139
140 tx.startRunning();
141
142 tx.setPayloadFormat(StaticPayloadFormat(sptPCMU));
143 if (!tx.addDestination(pattern.getDestinationAddress(),
144 pattern.getDestinationPort()) ) {
145 return 1;
146 }
147
148 // 2 packets per second (packet duration of 500ms)
149 uint32 period = 500;
150 uint16 inc = tx.getCurrentRTPClockRate()/2;
151 TimerPort::setTimer(period);
152 uint32 i;
153 for (i = 0; i < pattern.getPacketsNumber(); i++ ) {
154 tx.putData(i*inc,
155 pattern.getPacketData(i),
156 pattern.getPacketSize(i));
157 cout << "Sent some data: " << i << endl;
158 Thread::sleep(TimerPort::getTimer());
159 TimerPort::incTimer(period);
160 }
161 tx.putData(i*inc, (unsigned char*)"exit", 5);
162 Thread::sleep(TimerPort::getTimer());
163 return 0;
164 }
165};
166
167
168class RecvPacketTransmissionTest: public Thread {
169public:
170 void
171 run() {
172 doTest();
173 }
174
175 int
176 doTest() {
177 ExtZrtpSession rx(pattern.getSsrc()+1, pattern.getDestinationAddress(),
178 pattern.getDestinationPort());
179
180// SymmetricZRTPSession rx(pattern.getSsrc()+1, pattern.getDestinationAddress(),
181// pattern.getDestinationPort());
182 rx.setSchedulingTimeout(10000);
183 rx.setExpireTimeout(1000000);
184
185 rx.startRunning();
186 rx.setPayloadFormat(StaticPayloadFormat(sptPCMU));
187 // arbitrary number of loops to provide time to start transmitter
188 if (!rx.addDestination(pattern.getDestinationAddress(),
189 pattern.getDestinationPort()+2) ) {
190 return 1;
191 }
192 for ( int i = 0; i < 5000 ; i++ ) {
193 const AppDataUnit* adu;
194 while ( (adu = rx.getData(rx.getFirstTimestamp())) ) {
195 cerr << "got some data: " << adu->getData() << endl;
196 if (*adu->getData() == 'e') {
197 delete adu;
198 return 0;
199 }
200 delete adu;
201 }
202 Thread::sleep(70);
203 }
204 return 0;
205 }
206};
207
208
209/**
210 * SymmetricZRTPSession in security mode.
211 *
212 * The next two classes show how to use <code>SymmetricZRTPSession</code>
213 * using the standard ZRTP handshake an switching to encrypted (SRTP) mode.
214 * The application enables this by calling <code>initialize(...)</code>.
215 * Some embedded logging informs about the ZRTP processing.
216 */
217
218class ZrtpSendPacketTransmissionTest: public Thread, public TimerPort {
219public:
220 void
221 run() {
222 doTest();
223 }
224
225 int doTest() {
226 // should be valid?
227 //RTPSession tx();
228 ExtZrtpSession tx(pattern.getSsrc(), pattern.getDestinationAddress(),
229 pattern.getDestinationPort()+2);
230 tx.initialize("test_t.zid");
231
232 tx.setSchedulingTimeout(10000);
233 tx.setExpireTimeout(1000000);
234
235 tx.startRunning();
236
237 tx.setPayloadFormat(StaticPayloadFormat(sptPCMU));
238 if (!tx.addDestination(pattern.getDestinationAddress(),
239 pattern.getDestinationPort()) ) {
240 return 1;
241 }
242 tx.startZrtp();
243 // 2 packets per second (packet duration of 500ms)
244 uint32 period = 500;
245 uint16 inc = tx.getCurrentRTPClockRate()/2;
246 TimerPort::setTimer(period);
247 uint32 i;
248 for (i = 0; i < pattern.getPacketsNumber(); i++ ) {
249 tx.putData(i*inc,
250 pattern.getPacketData(i),
251 pattern.getPacketSize(i));
252 cout << "Sent some data: " << i << endl;
253 Thread::sleep(TimerPort::getTimer());
254 TimerPort::incTimer(period);
255 }
256 tx.putData(i*inc, (unsigned char*)"exit", 5);
257 Thread::sleep(200);
258 return 0;
259 }
260};
261
262class ZrtpRecvPacketTransmissionTest: public Thread {
263public:
264 void
265 run() {
266 doTest();
267 }
268
269 int
270 doTest() {
271 ExtZrtpSession rx(pattern.getSsrc()+1, pattern.getDestinationAddress(),
272 pattern.getDestinationPort());
273
274 rx.initialize("test_r.zid");
275
276 rx.setSchedulingTimeout(10000);
277 rx.setExpireTimeout(1000000);
278
279 rx.startRunning();
280 rx.setPayloadFormat(StaticPayloadFormat(sptPCMU));
281 // arbitrary number of loops to provide time to start transmitter
282 if (!rx.addDestination(pattern.getDestinationAddress(),
283 pattern.getDestinationPort()+2) ) {
284 return 1;
285 }
286 rx.startZrtp();
287 for ( int i = 0; i < 5000 ; i++ ) {
288 const AppDataUnit* adu;
289 while ( (adu = rx.getData(rx.getFirstTimestamp())) ) {
290 cerr << "got some data: " << adu->getData() << endl;
291 if (*adu->getData() == 'e') {
292 delete adu;
293 return 0;
294 }
295 delete adu;
296 }
297 Thread::sleep(70);
298 }
299 return 0;
300 }
301};
302
303/**
304 * Simple User Callback class
305 *
306 * This class overwrite some methods from ZrtpUserCallback to get information
307 * about ZRTP processing and information about ZRTP results. The standard
308 * implementation of this class just perform return, thus effectively
309 * supressing any callback or trigger.
310 */
311class MyUserCallback: public ZrtpUserCallback {
312
313 static map<int32, std::string*> infoMap;
314 static map<int32, std::string*> warningMap;
315 static map<int32, std::string*> severeMap;
316 static map<int32, std::string*> zrtpMap;
317
318 static bool initialized;
319
320 SymmetricZRTPSession* session;
321 public:
322 MyUserCallback(SymmetricZRTPSession* s) {
323 session = s;
324 if (initialized) {
325 return;
326 }
327 infoMap.insert(pair<int32, std::string*>(InfoHelloReceived, new string("Hello received, preparing a Commit")));
328 infoMap.insert(pair<int32, std::string*>(InfoCommitDHGenerated, new string("Commit: Generated a public DH key")));
329 infoMap.insert(pair<int32, std::string*>(InfoRespCommitReceived, new string("Responder: Commit received, preparing DHPart1")));
330 infoMap.insert(pair<int32, std::string*>(InfoDH1DHGenerated, new string("DH1Part: Generated a public DH key")));
331 infoMap.insert(pair<int32, std::string*>(InfoInitDH1Received, new string("Initiator: DHPart1 received, preparing DHPart2")));
332 infoMap.insert(pair<int32, std::string*>(InfoRespDH2Received, new string("Responder: DHPart2 received, preparing Confirm1")));
333 infoMap.insert(pair<int32, std::string*>(InfoInitConf1Received, new string("Initiator: Confirm1 received, preparing Confirm2")));
334 infoMap.insert(pair<int32, std::string*>(InfoRespConf2Received, new string("Responder: Confirm2 received, preparing Conf2Ack")));
335 infoMap.insert(pair<int32, std::string*>(InfoRSMatchFound, new string("At least one retained secrets matches - security OK")));
336 infoMap.insert(pair<int32, std::string*>(InfoSecureStateOn, new string("Entered secure state")));
337 infoMap.insert(pair<int32, std::string*>(InfoSecureStateOff, new string("No more security for this session")));
338
339 warningMap.insert(pair<int32, std::string*>(WarningDHAESmismatch,
340 new string("Commit contains an AES256 cipher but does not offer a Diffie-Helman 4096")));
341 warningMap.insert(pair<int32, std::string*>(WarningGoClearReceived, new string("Received a GoClear message")));
342 warningMap.insert(pair<int32, std::string*>(WarningDHShort,
343 new string("Hello offers an AES256 cipher but does not offer a Diffie-Helman 4096")));
344 warningMap.insert(pair<int32, std::string*>(WarningNoRSMatch, new string("No retained secret matches - verify SAS")));
345 warningMap.insert(pair<int32, std::string*>(WarningCRCmismatch, new string("Internal ZRTP packet checksum mismatch - packet dropped")));
346 warningMap.insert(pair<int32, std::string*>(WarningSRTPauthError, new string("Dropping packet because SRTP authentication failed!")));
347 warningMap.insert(pair<int32, std::string*>(WarningSRTPreplayError, new string("Dropping packet because SRTP replay check failed!")));
348
349 severeMap.insert(pair<int32, std::string*>(SevereHelloHMACFailed, new string("Hash HMAC check of Hello failed!")));
350 severeMap.insert(pair<int32, std::string*>(SevereCommitHMACFailed, new string("Hash HMAC check of Commit failed!")));
351 severeMap.insert(pair<int32, std::string*>(SevereDH1HMACFailed, new string("Hash HMAC check of DHPart1 failed!")));
352 severeMap.insert(pair<int32, std::string*>(SevereDH2HMACFailed, new string("Hash HMAC check of DHPart2 failed!")));
353 severeMap.insert(pair<int32, std::string*>(SevereCannotSend, new string("Cannot send data - connection or peer down?")));
354 severeMap.insert(pair<int32, std::string*>(SevereProtocolError, new string("Internal protocol error occured!")));
355 severeMap.insert(pair<int32, std::string*>(SevereNoTimer, new string("Cannot start a timer - internal resources exhausted?")));
356 severeMap.insert(pair<int32, std::string*>(SevereTooMuchRetries,
357 new string("Too much retries during ZRTP negotiation - connection or peer down?")));
358
359 zrtpMap.insert(pair<int32, std::string*>(MalformedPacket, new string("Malformed packet (CRC OK, but wrong structure)")));
360 zrtpMap.insert(pair<int32, std::string*>(CriticalSWError, new string("Critical software error")));
361 zrtpMap.insert(pair<int32, std::string*>(UnsuppZRTPVersion, new string("Unsupported ZRTP version")));
362 zrtpMap.insert(pair<int32, std::string*>(HelloCompMismatch, new string("Hello components mismatch")));
363 zrtpMap.insert(pair<int32, std::string*>(UnsuppHashType, new string("Hash type not supported")));
364 zrtpMap.insert(pair<int32, std::string*>(UnsuppCiphertype, new string("Cipher type not supported")));
365 zrtpMap.insert(pair<int32, std::string*>(UnsuppPKExchange, new string("Public key exchange not supported")));
366 zrtpMap.insert(pair<int32, std::string*>(UnsuppSRTPAuthTag, new string("SRTP auth. tag not supported")));
367 zrtpMap.insert(pair<int32, std::string*>(UnsuppSASScheme, new string("SAS scheme not supported")));
368 zrtpMap.insert(pair<int32, std::string*>(NoSharedSecret, new string("No shared secret available, DH mode required")));
369 zrtpMap.insert(pair<int32, std::string*>(DHErrorWrongPV, new string("DH Error: bad pvi or pvr ( == 1, 0, or p-1)")));
370 zrtpMap.insert(pair<int32, std::string*>(DHErrorWrongHVI, new string("DH Error: hvi != hashed data")));
371 zrtpMap.insert(pair<int32, std::string*>(SASuntrustedMiTM, new string("Received relayed SAS from untrusted MiTM")));
372 zrtpMap.insert(pair<int32, std::string*>(ConfirmHMACWrong, new string("Auth. Error: Bad Confirm pkt HMAC")));
373 zrtpMap.insert(pair<int32, std::string*>(NonceReused, new string("Nonce reuse")));
374 zrtpMap.insert(pair<int32, std::string*>(EqualZIDHello, new string("Equal ZIDs in Hello")));
375 zrtpMap.insert(pair<int32, std::string*>(GoCleatNotAllowed, new string("GoClear packet received, but not allowed")));
376
377 initialized = true;
378 }
379
380 void showMessage(GnuZrtpCodes::MessageSeverity sev, int32_t subCode) {
381 string* msg;
382 if (sev == Info) {
383 msg = infoMap[subCode];
384 if (msg != NULL) {
385 cout << *msg << endl;
386 }
387 }
388 if (sev == Warning) {
389 msg = warningMap[subCode];
390 if (msg != NULL) {
391 cout << *msg << endl;
392 }
393 }
394 if (sev == Severe) {
395 msg = severeMap[subCode];
396 if (msg != NULL) {
397 cout << *msg << endl;
398 }
399 }
400 if (sev == ZrtpError) {
401 if (subCode < 0) { // received an error packet from peer
402 subCode *= -1;
403 cout << "Received error packet: ";
404 }
405 else {
406 cout << "Sent error packet: ";
407 }
408 msg = zrtpMap[subCode];
409 if (msg != NULL) {
410 cout << *msg << endl;
411 }
412 }
413 }
414
415 void zrtpNegotiationFailed(GnuZrtpCodes::MessageSeverity sev, int32_t subCode) {
416 string* msg;
417 if (sev == ZrtpError) {
418 if (subCode < 0) { // received an error packet from peer
419 subCode *= -1;
420 cout << "Received error packet: ";
421 }
422 else {
423 cout << "Sent error packet: ";
424 }
425 msg = zrtpMap[subCode];
426 if (msg != NULL) {
427 cout << *msg << endl;
428 }
429 }
430 else {
431 msg = severeMap[subCode];
432 cout << *msg << endl;
433 }
434 }
435
436 void secureOn(std::string cipher) {
437 cout << "Using cipher:" << cipher << endl;
438 }
439
440 void showSAS(std::string sas, bool verified) {
441 cout << "SAS is: " << sas << endl;
442
443 }
444};
445
446map<int32, std::string*>MyUserCallback::infoMap;
447map<int32, std::string*>MyUserCallback::warningMap;
448map<int32, std::string*>MyUserCallback::severeMap;
449map<int32, std::string*>MyUserCallback::zrtpMap;
450
451bool MyUserCallback::initialized = false;
452
453/**
454 * SymmetricZRTPSession in security mode and using a callback class.
455 *
456 * The next two classes show how to use <code>SymmetricZRTPSession</code>
457 * using the standard ZRTP handshake an switching to encrypted (SRTP) mode.
458 * The application enables this by calling <code>initialize(...)</code>.
459 * In addition the application sets a callback class (see above). ZRTP calls
460 * the methods of the callback class and the application may implement
461 * appropriate methods to deal with these triggers.
462 */
463
464class
465ZrtpSendPacketTransmissionTestCB : public Thread, public TimerPort
466{
467public:
468 void
469 run()
470 {
471 doTest();
472 }
473
474 int doTest()
475 {
476 // should be valid?
477 //RTPSession tx();
478 ExtZrtpSession tx(/*pattern.getSsrc(),*/ pattern.getDestinationAddress(),
479 pattern.getDestinationPort()+2);
480 tx.initialize("test_t.zid");
481 // At this point the Hello hash is available. See ZRTP specification
482 // chapter 9.1 for further information when an how to use the Hello
483 // hash.
484 cout << "TX Hello hash: " << tx.getHelloHash() << endl;
485 cout << "TX Hello hash length: " << tx.getHelloHash().length() << endl;
486
487 tx.setUserCallback(new MyUserCallback(&tx));
488
489 tx.setSchedulingTimeout(10000);
490 tx.setExpireTimeout(1000000);
491
492 tx.startRunning();
493
494 tx.setPayloadFormat(StaticPayloadFormat(sptPCMU));
495 if (!tx.addDestination(pattern.getDestinationAddress(),
496 pattern.getDestinationPort()) ) {
497 return 1;
498 }
499 tx.startZrtp();
500
501 // 2 packets per second (packet duration of 500ms)
502 uint32 period = 500;
503 uint16 inc = tx.getCurrentRTPClockRate()/2;
504 TimerPort::setTimer(period);
505 uint32 i;
506 for (i = 0; i < pattern.getPacketsNumber(); i++ ) {
507 tx.putData(i*inc,
508 pattern.getPacketData(i),
509 pattern.getPacketSize(i));
510 cout << "Sent some data: " << i << endl;
511 Thread::sleep(TimerPort::getTimer());
512 TimerPort::incTimer(period);
513 }
514 tx.putData(i*inc, (unsigned char*)"exit", 5);
515 Thread::sleep(TimerPort::getTimer());
516 return 0;
517 }
518};
519
520
521class
522ZrtpRecvPacketTransmissionTestCB: public Thread
523{
524public:
525 void
526 run() {
527 doTest();
528 }
529
530 int
531 doTest() {
532 ExtZrtpSession rx( /*pattern.getSsrc()+1,*/ pattern.getDestinationAddress(),
533 pattern.getDestinationPort());
534
535 rx.initialize("test_r.zid");
536 // At this point the Hello hash is available. See ZRTP specification
537 // chapter 9.1 for further information when an how to use the Hello
538 // hash.
539 cout << "RX Hello hash: " << rx.getHelloHash() << endl;
540 cout << "RX Hello hash length: " << rx.getHelloHash().length() << endl;
541
542 rx.setUserCallback(new MyUserCallback(&rx));
543
544 rx.setSchedulingTimeout(10000);
545 rx.setExpireTimeout(1000000);
546
547 rx.startRunning();
548 rx.setPayloadFormat(StaticPayloadFormat(sptPCMU));
549 // arbitrary number of loops to provide time to start transmitter
550 if (!rx.addDestination(pattern.getDestinationAddress(),
551 pattern.getDestinationPort()+2) ) {
552 return 1;
553 }
554 rx.startZrtp();
555
556 for ( int i = 0; i < 5000 ; i++ ) {
557 const AppDataUnit* adu;
558 while ( (adu = rx.getData(rx.getFirstTimestamp())) ) {
559 cerr << "got some data: " << adu->getData() << endl;
560 if (*adu->getData() == 'e') {
561 delete adu;
562 return 0;
563 }
564 delete adu;
565 }
566 Thread::sleep(500);
567 }
568 return 0;
569 }
570};
571
572
573int main(int argc, char *argv[])
574{
575 int result = 0;
576 bool send = false;
577 bool recv = false;
578
579 char c;
580
581 /* check args */
582 while (1) {
583 c = getopt(argc, argv, "rs");
584 if (c == -1) {
585 break;
586 }
587 switch (c) {
588 case 'r':
589 recv = true;
590 break;
591 case 's':
592 send = true;
593 break;
594 default:
595 cerr << "Wrong Arguments, only -s and -r are accepted" << endl;
596 }
597 }
598
599 if (send || recv) {
600 if (send) {
601 cout << "Running as sender" << endl;
602 }
603 else {
604 cout << "Running as receiver" << endl;
605 }
606 }
607 else {
608 cerr << "No send or receive argument specificied" << endl;
609 exit(1);
610 }
611
612 // accept as parameter if must run as --send or --recv
613
614#if 0
615 RecvPacketTransmissionTest *rx;
616 SendPacketTransmissionTest *tx;
617
618 // run several tests in parallel threads
619 if ( send ) {
620 tx = new SendPacketTransmissionTest();
621 tx->start();
622 tx->join();
623 } else if ( recv ) {
624 rx = new RecvPacketTransmissionTest();
625 rx->start();
626 rx->join();
627 }
628//#endif
629//#if 0
630 ZrtpRecvPacketTransmissionTest *zrx;
631 ZrtpSendPacketTransmissionTest *ztx;
632
633 if ( send ) {
634 ztx = new ZrtpSendPacketTransmissionTest();
635 ztx->start();
636 ztx->join();
637 } else if ( recv ) {
638 zrx = new ZrtpRecvPacketTransmissionTest();
639 zrx->start();
640 zrx->join();
641 }
642#endif
643 ZrtpRecvPacketTransmissionTestCB *zrxcb;
644 ZrtpSendPacketTransmissionTestCB *ztxcb;
645
646 if ( send ) {
647 ztxcb = new ZrtpSendPacketTransmissionTestCB();
648 ztxcb->start();
649 ztxcb->join();
650 } else if ( recv ) {
651 zrxcb = new ZrtpRecvPacketTransmissionTestCB();
652 zrxcb->start();
653 zrxcb->join();
654 }
655
656 exit(result);
657}
658
659/** EMACS **
660 * Local variables:
661 * mode: c++
662 * c-default-style: ellemtel
663 * c-basic-offset: 4
664 * End:
665 */