blob: 20e4b96b3687748f9d01a1d4bb4fb25efe47b16c [file] [log] [blame]
Emeric Vigier2f625822012-08-06 11:09:52 -04001// Copyright (C) 2001,2002,2004,2007 Federico Montesino Pouzols <fedemp@altern.org>.
Alexandre Lisionddd731e2014-01-31 11:50:08 -05002//
Emeric Vigier2f625822012-08-06 11:09:52 -04003// 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; either version 2 of the License, or
6// (at your option) any later version.
Alexandre Lisionddd731e2014-01-31 11:50:08 -05007//
Emeric Vigier2f625822012-08-06 11:09:52 -04008// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
Alexandre Lisionddd731e2014-01-31 11:50:08 -050012//
Emeric Vigier2f625822012-08-06 11:09:52 -040013// You should have received a copy of the GNU General Public License
Alexandre Lisionddd731e2014-01-31 11:50:08 -050014// along with this program; if not, write to the Free Software
Emeric Vigier2f625822012-08-06 11:09:52 -040015// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Alexandre Lisionddd731e2014-01-31 11:50:08 -050016//
Emeric Vigier2f625822012-08-06 11:09:52 -040017// As a special exception, you may use this file as part of a free software
18// library without restriction. Specifically, if other files instantiate
19// templates or use macros or inline functions from this file, or you compile
20// this file and link it with other files to produce an executable, this
21// file does not by itself cause the resulting executable to be covered by
Alexandre Lisionddd731e2014-01-31 11:50:08 -050022// the GNU General Public License. This exception does not however
Emeric Vigier2f625822012-08-06 11:09:52 -040023// invalidate any other reasons why the executable file might be covered by
Alexandre Lisionddd731e2014-01-31 11:50:08 -050024// the GNU General Public License.
Emeric Vigier2f625822012-08-06 11:09:52 -040025//
26// This exception applies only to the code released under the name GNU
27// ccRTP. If you copy code from other releases into a copy of GNU
28// ccRTP, as the General Public License permits, the exception does
29// not apply to the code that you add in this way. To avoid misleading
30// anyone as to the status of such modified files, you must delete
31// this exception notice from them.
32//
33// If you write modifications of your own for GNU ccRTP, it is your choice
34// whether to permit this exception to apply to your modifications.
35// If you do not wish that, delete this exception notice.
36//
37
Alexandre Lisionddd731e2014-01-31 11:50:08 -050038#ifndef CCXX_RTP_RTCPPKT_H_
Emeric Vigier2f625822012-08-06 11:09:52 -040039#define CCXX_RTP_RTCPPKT_H_
40
41#include <ccrtp/base.h>
42
Alexandre Lisionddd731e2014-01-31 11:50:08 -050043NAMESPACE_COMMONCPP
Emeric Vigier2f625822012-08-06 11:09:52 -040044
Alexandre Lisionddd731e2014-01-31 11:50:08 -050045/**
46 * @file rtcppkt.h
Emeric Vigier2f625822012-08-06 11:09:52 -040047 *
48 * @short RTCP packets handling.
49 **/
50
51/**
52 * @defgroup rtcppacket RTCP compound packets manipulation.
53 * @{
54 **/
55
Alexandre Lisionddd731e2014-01-31 11:50:08 -050056/**
Emeric Vigier2f625822012-08-06 11:09:52 -040057 * @enum SDESItemType
58 * @short SDES items that may be carried in a Source DEScription RTCP packet.
59 *
60 * CNAME is mandatory in each RTCP compound packet (except when
61 * splitted for partial encryption), the others are optional and have
62 * different sending frequencies, though with recommended default
63 * values.
64 **/
65typedef enum
66{
Alexandre Lisionddd731e2014-01-31 11:50:08 -050067 SDESItemTypeEND = 0, ///< END of SDES item list.
68 SDESItemTypeCNAME, ///< Canonical end-point identifier.
69 SDESItemTypeNAME, ///< Personal NAME of the user.
70 SDESItemTypeEMAIL, ///< EMAIL address of the user.
71 SDESItemTypePHONE, ///< Phone number of the user.
72 SDESItemTypeLOC, ///< Location where the user is.
73 SDESItemTypeTOOL, ///< Application or tool.
74 SDESItemTypeNOTE, ///< Comment usually reporting state.
75 SDESItemTypePRIV, ///< Private extension.
76 SDESItemTypeH323CADDR, ///< H323 callable address.
77 SDESItemTypeLast = SDESItemTypeH323CADDR ///< Last defined code.
Emeric Vigier2f625822012-08-06 11:09:52 -040078} SDESItemType;
79
80/**
81 * @class RTCPCompoundHandler
82 * @short low level structs and RTCP packet parsing and building
83 * methods.
84 *
85 * Intended to be used, through inheritance, in RTCP management
86 * classes, such as QueueRTCPManager.
87 *
Alexandre Lisionddd731e2014-01-31 11:50:08 -050088 * @author Federico Montesino Pouzols <fedemp@altern.org>
Emeric Vigier2f625822012-08-06 11:09:52 -040089 **/
Alexandre Lisionddd731e2014-01-31 11:50:08 -050090class __EXPORT RTCPCompoundHandler
Emeric Vigier2f625822012-08-06 11:09:52 -040091{
92public:
Alexandre Lisionddd731e2014-01-31 11:50:08 -050093 inline void setPathMTU(uint16 mtu)
94 { pathMTU = mtu; }
Emeric Vigier2f625822012-08-06 11:09:52 -040095
Alexandre Lisionddd731e2014-01-31 11:50:08 -050096 inline uint16 getPathMTU()
97 { return pathMTU; }
Emeric Vigier2f625822012-08-06 11:09:52 -040098
Alexandre Lisionddd731e2014-01-31 11:50:08 -050099#ifdef CCXX_PACKED
100#pragma pack(1)
Emeric Vigier2f625822012-08-06 11:09:52 -0400101#endif
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500102 /**
103 * @struct ReceiverInfo
104 *
105 * Struct for the data contained in a receiver info
106 * block. Receiver info blocks can be found in SR (sender
107 * report) or RR (receiver report) RTCP packets.
108 **/
109 struct ReceiverInfo
110 {
111 uint8 fractionLost; ///< packet fraction lost.
112 uint8 lostMSB; ///< cumulative lost MSB of 3 octets.
113 uint16 lostLSW; ///< cumulative lost two LSB.
114 uint32 highestSeqNum; ///< highest sequence number.
115 uint32 jitter; ///< arrival jitter.
116 uint32 lsr; ///< last sender report timestamp.
117 uint32 dlsr; ///< delay since last sender report.
118 };
Emeric Vigier2f625822012-08-06 11:09:52 -0400119
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500120 /**
121 * @struct RRBlock
122 *
123 * Struct for a receiver info block in a SR (sender report) or an RR
124 * (receiver report) RTCP packet.
125 **/
126 struct RRBlock
127 {
128 uint32 ssrc; ///< source identifier.
129 ReceiverInfo rinfo; ///< info about the source.
130 };
Emeric Vigier2f625822012-08-06 11:09:52 -0400131
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500132 /**
133 * @struct RecvReport
134 *
135 * @short raw structure of the source and every receiver report in an
136 * SR or RR RTCP packet.
137 **/
138 struct RecvReport
139 {
140 uint32 ssrc; ///< source identifier.
141 RRBlock blocks[1]; ///< receiver report blocks.
142 };
Emeric Vigier2f625822012-08-06 11:09:52 -0400143
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500144 /**
145 * @struct SenderInfo
146 *
147 * Struct for the sender info block in a SR (sender report)
148 * RTCP packet.
149 **/
150 struct SenderInfo
151 {
152 uint32 NTPMSW; ///< NTP timestamp higher octets.
153 uint32 NTPLSW; ///< NTP timestamp lower octets.
154 uint32 RTPTimestamp; ///< RTP timestamp.
155 uint32 packetCount; ///< cumulative packet counter.
156 uint32 octetCount; ///< cumulative octet counter.
157 };
Emeric Vigier2f625822012-08-06 11:09:52 -0400158
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500159 /**
160 * @struct SendReport
161 *
162 * Struct for SR (sender report) RTCP packets.
163 **/
164 struct SendReport
165 {
166 uint32 ssrc; ///< source identifier.
167 SenderInfo sinfo; ///< actual sender info.
168 RRBlock blocks[1]; ///< possibly several receiver info blocks.
169 };
Emeric Vigier2f625822012-08-06 11:09:52 -0400170
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500171 /**
172 * @struct SDESItem
173 *
174 * Struct for an item description of a SDES packet.
175 **/
176 struct SDESItem
177 {
178 uint8 type; ///< item identifier.
179 uint8 len; ///< item len in octets.
180 char data[1]; ///< item content.
181 };
Emeric Vigier2f625822012-08-06 11:09:52 -0400182
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500183 /**
184 * @struct SDESChunk
185 *
186 * Struct for a chunk of items in a SDES RTCP packet.
187 **/
188 struct SDESChunk
189 {
190 uint32 getSSRC() const
191 { return (ntohl(ssrc)); }
192
193 uint32 ssrc; ///< SSRC identifer from sender.
194 SDESItem item; ///< SDES item from sender.
195 };
196
197 /**
198 * @struct BYEPacket
199 *
200 * @short Struct for BYE (leaving session) RTCP packets.
201 **/
202 struct BYEPacket
203 {
204 uint32 ssrc; ///< ssrc identifier of source leaving.
205 uint8 length; ///< [optional] length of reason.
206 };
207
208 /**
209 * @struct APPPacket
210 *
211 * @short Struct for APP (application specific) RTCP packets.
212 **/
213 struct APPPacket
214 {
215 uint32 ssrc; ///< ssrc identifier of source.
216 char name [4]; ///< Name of the APP packet,
217 ///interpreted as a sequence of
218 ///four characters.
219 unsigned char data[1]; ///< application dependent data.
220 };
221
222 /**
223 * @struct FIRPacket
224 *
225 * @short Struct for Full Intra-frame Request (FIR) RTCP
226 * packet. Specific for H.261 sessions (see RFC 2032).
227 **/
228 struct FIRPacket
229 {
230 uint32 ssrc; ///< ssrc identifier of source.
231 };
232
233 /**
234 * @struct NACKPacket
235 *
236 * @short Struct for Negative ACKnowledgements (NACK) RTCP
237 * packet. Specific for H.261 sessions (see RFC 2032).
238 **/
239 struct NACKPacket
240 {
241 uint32 ssrc; ///< ssrc identifier of source.
242 uint16 fsn; ///< First Sequence Number lost.
243 uint16 blp; ///< Bitmask of following Lost Packets.
244 };
245
246 /**
247 * @struct RTCPFixedHeader
248 * Fixed RTCP packet header. First 32-bit word in any RTCP
249 * packet.
250 */
251 struct RTCPFixedHeader
252 {
253#if __BYTE_ORDER == __BIG_ENDIAN
254 ///< For big endian boxes
255 unsigned char version:2; ///< Version, currently 2.
256 unsigned char padding:1; ///< Padding bit.
257 unsigned char block_count:5; ///< Number of RR, SR, or SDES chunks.
Emeric Vigier2f625822012-08-06 11:09:52 -0400258#else
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500259 ///< For little endian boxes
260 unsigned char block_count:5; ///< Number of RR, SR, or SDES chunks.
261 unsigned char padding:1; ///< Padding bit.
262 unsigned char version:2; ///< Version, currently 2.
Emeric Vigier2f625822012-08-06 11:09:52 -0400263#endif
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500264 uint8 type; ///< type of RTCP packet.
265 uint16 length; ///< number of 32-bit words in the packet (*minus one*).
266 };
Emeric Vigier2f625822012-08-06 11:09:52 -0400267
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500268 /**
269 * @struct RTCPPacket
270 *
271 * @short Struct representing general RTCP packet headers as they are
272 * sent through the network.
273 *
274 * This struct consists of a fixed header, always at the
275 * beginning of any RTCP packet, and a union for all the RTCP
276 * packet types supported.
277 **/
278 struct RTCPPacket
279 {
280 /**
281 * @enum Type rtp.h cc++/rtp.h
282 *
283 * RTCP packet types. They are registered with IANA.
284 */
285 typedef enum {
286 tSR = 200, ///< Sender Report.
287 tRR, ///< Receiver Report.
288 tSDES, ///< Source DEScription.
289 tBYE, ///< End of participation.
290 tAPP, ///< APPlication specific.
291 tFIR = 192, ///< Full Intra-frame request.
292 tNACK = 193, ///< Negative ACK.
293 tXR ///< Extended Report.
294 } Type;
Emeric Vigier2f625822012-08-06 11:09:52 -0400295
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500296 /**
297 * Get the packet length specified in its header, in
298 * octets and in host order.
299 **/
300 uint32 getLength() const
301 { return ((ntohs(fh.length) + 1) << 2); }
Emeric Vigier2f625822012-08-06 11:09:52 -0400302
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500303 /**
304 * Get the SSRC identifier specified in the packet
305 * header, in host order.
306 **/
307 uint32 getSSRC() const
308 { return (ntohl(info.RR.ssrc)); } // SSRC is always the first
309 // word after fh.
Emeric Vigier2f625822012-08-06 11:09:52 -0400310
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500311 RTCPFixedHeader fh; ///< Fixed RTCP header.
312
313 // An RTCP packet may be of any of the types defined
314 // above, including APP specific ones.
315 union
316 {
317 SendReport SR;
318 RecvReport RR;
319 SDESChunk SDES;
320 BYEPacket BYE;
321 APPPacket APP;
322 NACKPacket NACK;
323 FIRPacket FIR;
324 } info; ///< Union for SR, RR, SDES, BYE and APP
325 };
326#ifdef CCXX_PACKED
Emeric Vigier2f625822012-08-06 11:09:52 -0400327#pragma pack()
328#endif
329
330protected:
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500331 enum { defaultPathMTU = 1500 };
Emeric Vigier2f625822012-08-06 11:09:52 -0400332
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500333 RTCPCompoundHandler(uint16 mtu = defaultPathMTU);
Emeric Vigier2f625822012-08-06 11:09:52 -0400334
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500335 ~RTCPCompoundHandler();
Emeric Vigier2f625822012-08-06 11:09:52 -0400336
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500337 /**
338 * Perform RTCP compound packet header validity check as
339 * specified in draft-ietv-avt-rtp-new. This method follows
340 * appendix A.2. Correct version, payload type, padding bit
341 * and length of every RTCP packet in the compound are
342 * verified.
343 *
344 * @param len length of the RTCP compound packet in
345 * the reception buffer
346 * @return whether the header is valid.
347 */
348 bool
349 checkCompoundRTCPHeader(size_t len);
Emeric Vigier2f625822012-08-06 11:09:52 -0400350
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500351 // buffer to hold RTCP compound packets being sent. Allocated
352 // in construction time
353 unsigned char* rtcpSendBuffer;
354 // buffer to hold RTCP compound packets being
355 // received. Allocated at construction time
356 unsigned char* rtcpRecvBuffer;
Emeric Vigier2f625822012-08-06 11:09:52 -0400357
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500358 friend class RTCPSenderInfo;
359 friend class RTCPReceiverInfo;
Emeric Vigier2f625822012-08-06 11:09:52 -0400360private:
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500361 // path MTU. RTCP packets should not be greater than this
362 uint16 pathMTU;
363 // masks for RTCP header validation;
364 static const uint16 RTCP_VALID_MASK;
365 static const uint16 RTCP_VALID_VALUE;
Emeric Vigier2f625822012-08-06 11:09:52 -0400366};
367
368/**
369 * @class RTCPReceiverInfo
370 * @short Report block information of SR/RR RTCP reports.
371 *
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500372 * @author Federico Montesino Pouzols <fedemp@altern.org>
Emeric Vigier2f625822012-08-06 11:09:52 -0400373 **/
374class __EXPORT RTCPReceiverInfo
375{
376public:
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500377 RTCPReceiverInfo(void* ri)
378 { memcpy(&receiverInfo,&ri,
379 sizeof(RTCPCompoundHandler::ReceiverInfo));}
Emeric Vigier2f625822012-08-06 11:09:52 -0400380
381 RTCPReceiverInfo(RTCPCompoundHandler::ReceiverInfo& si)
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500382 : receiverInfo( si )
Emeric Vigier2f625822012-08-06 11:09:52 -0400383 {
384 }
385
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500386 ~RTCPReceiverInfo()
387 { }
Emeric Vigier2f625822012-08-06 11:09:52 -0400388
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500389 /**
390 * Get fraction of lost packets, as a number between 0 and
391 * 255.
392 **/
393 inline uint8
394 getFractionLost() const
395 { return receiverInfo.fractionLost; }
Emeric Vigier2f625822012-08-06 11:09:52 -0400396
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500397 inline uint32
398 getCumulativePacketLost() const
399 { return ( ((uint32)ntohs(receiverInfo.lostLSW)) +
400 (((uint32)receiverInfo.lostMSB) << 16) ); }
Emeric Vigier2f625822012-08-06 11:09:52 -0400401
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500402 inline uint32
403 getExtendedSeqNum() const
404 { return ntohl(receiverInfo.highestSeqNum); }
Emeric Vigier2f625822012-08-06 11:09:52 -0400405
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500406 /**
407 * Get the statistical variance of the RTP data packets
408 * interarrival time.
409 *
410 * @return Interarrival jitter, in timestamp units.
411 **/
412 uint32
413 getJitter() const
414 { return ntohl(receiverInfo.jitter); }
Emeric Vigier2f625822012-08-06 11:09:52 -0400415
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500416 /**
417 * Get the integer part of the NTP timestamp of the last SR
418 * RTCP packet received from the source this receiver report
419 * refers to.
420 **/
421 uint16
422 getLastSRNTPTimestampInt() const
423 { return (uint16)((ntohl(receiverInfo.lsr) & 0xFFFF0000) >> 16); }
Emeric Vigier2f625822012-08-06 11:09:52 -0400424
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500425 /**
426 * Get the fractional part of the NTP timestamp of the last SR
427 * RTCP packet received from the source this receiver report
428 * refers to.
429 **/
430 uint16
431 getLastSRNTPTimestampFrac() const
432 { return (uint16)(ntohl(receiverInfo.lsr) & 0xFFFF); }
433
434 /**
435 * Get the delay between the last SR packet received and the
436 * transmission of this report.
437 *
438 * @return Delay, in units of 1/65536 seconds
439 **/
440 uint32
441 getDelayLastSR() const
442 { return ntohl(receiverInfo.dlsr); }
Emeric Vigier2f625822012-08-06 11:09:52 -0400443
444private:
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500445 RTCPCompoundHandler::ReceiverInfo receiverInfo;
Emeric Vigier2f625822012-08-06 11:09:52 -0400446};
447
448/**
449 * @class RTCPSenderInfo
450 * @short Sender block information of SR RTCP reports.
451 *
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500452 * @author Federico Montesino Pouzols <fedemp@altern.org>
Emeric Vigier2f625822012-08-06 11:09:52 -0400453 **/
454class __EXPORT RTCPSenderInfo
455{
456public:
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500457 RTCPSenderInfo(void* si)
458 { memcpy(&senderInfo,&si,
459 sizeof(RTCPCompoundHandler::SenderInfo));}
Emeric Vigier2f625822012-08-06 11:09:52 -0400460
461 RTCPSenderInfo(RTCPCompoundHandler::SenderInfo& si)
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500462 : senderInfo( si )
Emeric Vigier2f625822012-08-06 11:09:52 -0400463 {
464 }
465
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500466 ~RTCPSenderInfo()
467 { }
Emeric Vigier2f625822012-08-06 11:09:52 -0400468
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500469 /**
470 * Get integer part of the NTP timestamp of this packet.
471 * @see NTP2Timeval
472 **/
473 uint32
474 getNTPTimestampInt() const
475 { return ntohl(senderInfo.NTPMSW); }
Emeric Vigier2f625822012-08-06 11:09:52 -0400476
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500477 /**
478 * Get fractional part of the NTP timestamp of this packet.
479 * @see NTP2Timeval
480 **/
481 uint32
482 getNTPTimestampFrac() const
483 { return ntohl(senderInfo.NTPLSW); }
Emeric Vigier2f625822012-08-06 11:09:52 -0400484
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500485 inline uint32
486 getRTPTimestamp() const
487 { return ntohl(senderInfo.RTPTimestamp); }
Emeric Vigier2f625822012-08-06 11:09:52 -0400488
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500489 /**
490 * Get count of sent data packets.
491 **/
492 inline uint32
493 getPacketCount() const
494 { return ntohl(senderInfo.packetCount); }
495
496 inline uint32
497 getOctetCount() const
498 { return ntohl(senderInfo.octetCount); }
Emeric Vigier2f625822012-08-06 11:09:52 -0400499
500private:
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500501 RTCPCompoundHandler::SenderInfo senderInfo;
Emeric Vigier2f625822012-08-06 11:09:52 -0400502};
503
504/**
505 * Convert a NTP timestamp, expressed as two 32-bit long words, into a
506 * timeval value.
507 *
508 * @param msw Integer part of NTP timestamp.
509 * @param lsw Fractional part of NTP timestamp.
510 * @return timeval value corresponding to the given NTP timestamp.
511 **/
512timeval
513NTP2Timeval(uint32 msw, uint32 lsw);
514
515/**
516 * Convert a time interval, expressed as a timeval, into a 32-bit time
517 * interval expressed in units of 1/65536 seconds.
518 *
519 * @param t Timeval interval.
520 * @return 32-bit value corresponding to the given timeval interval.
521 **/
522uint32
523timevalIntervalTo65536(timeval& t);
524
525/** @}*/ // rtcppacket
526
Alexandre Lisionddd731e2014-01-31 11:50:08 -0500527END_NAMESPACE
Emeric Vigier2f625822012-08-06 11:09:52 -0400528
529#endif // ndef CCXX_RTP_RTCPPKT_H_
530
531/** EMACS **
532 * Local variables:
533 * mode: c++
534 * c-basic-offset: 8
535 * End:
536 */
537