996e2c10c1b3e01e02b7766eadd35e4af7e3a1f3
[senf.git] / Packets / Packet.hh
1 // $Id$
2 //
3 // Copyright (C) 2007 
4 // Fraunhofer Institute for Open Communication Systems (FOKUS) 
5 // Competence Center NETwork research (NET), St. Augustin, GERMANY 
6 //     Stefan Bund <g0dil@berlios.de>
7 //
8 // This program is free software; you can redistribute it and/or modify
9 // it under the terms of the GNU General Public License as published by
10 // the Free Software Foundation; either version 2 of the License, or
11 // (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 // GNU General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the
20 // Free Software Foundation, Inc.,
21 // 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22
23 /** \file
24     \brief Packet public header */
25
26 #ifndef HH_Packet_
27 #define HH_Packet_ 1
28
29 // Custom includes
30 #include <boost/operators.hpp>
31
32 #include "../Utils/Exception.hh"
33 #include "../Utils/safe_bool.hh"
34 #include "PacketInterpreter.hh"
35
36 //#include "Packet.mpp"
37 ///////////////////////////////hh.p////////////////////////////////////////
38
39 namespace senf {
40
41     /** \defgroup packet_module Packet Handling
42
43         The basic groundwork of the Packet library is the packet handling:
44
45         \li The packet classes provide access to a chain of packet headers (more generically called
46             interpreters).
47         \li They automatically manage the required memory resources and the shared packet data.
48
49         \section packet_module_chain The Interpreter Chain
50
51         The central data structure for a packet is the interpreter chain
52
53         \image html structure.png The Interpreter Chain
54
55         This image depicts a packet with several headers. Each interpreter is responsible for a
56         specific sub-range of the complete packet. This range always \e includes the packets payload
57         (This is, why we call the data structure interpreter and not header: The interpreter is
58         responsible for interpreting a range of the packet according to a specific protocol), the
59         packet interpreters are nested inside each other.
60     
61         For each interpreter, this structure automatically divides the packet into three areas (each
62         of which are optional): The header, the payload and the trailer. Every packet will have
63         either a header or a payload section while most don't have a trailer.
64
65         As user of the library you always interact with the chain through one (or more) of the
66         interpreters. The interpreter provides methods to traverse to the following or preceding
67         header (interpreter) and provides two levels of access to the packet data: Generic low-level
68         access in the form of an STL compatible sequence and access to the parsed fields which are
69         provided by the parser associated with the concrete packet type.
70
71         \section packet_module_management Resource Management
72
73         The interface to the packet library is provided using a handle class (\ref Packet for
74         generic, protocol agnostic access and \ref ConcretePacket derived from \ref Packet to access
75         a specific protocol). This handle automatically manages the resources associated with the
76         packet (the interpreter chain and the data storage holding the packet data). The resources
77         are automatically released when the last packet handle referencing a specific packet is
78         destroyed.
79
80         \implementation The packet chain is provided on two levels: The internal representation \ref
81             PacketInterpreterBase and \ref PacketInterpreter which are referenced by the Handle
82             classes \ref Packet and \ref ConcretePacket. \n
83             The internal representation classes are pertinent in the sense, that they exist
84             regardless of the existence of a handle referencing them (as long as the packet
85             exists). Still the interpreter chain is lazy and packet interpreters beside the first
86             are only created dynamically when accessed (this is implemented in the handle not in the
87             internal representation). \n
88             The packet interpreters make use of a pool allocator. This provides extremely efficient
89             creation and destruction of packet interpreter's and removes the dynamic memory
90             management overhead from the packet interpreter management. The packet implementation
91             class (\ref PacketImpl which holds the packet data itself) however is still dynamically
92             managed (however there is only a single instance for each packet).
93      */
94
95     template <class PackeType> class ConcretePacket;
96
97     ///\addtogroup packet_module
98     ///@{
99     
100     /** \brief Main Packet class
101
102         Packet is the main externally visible class of the packet library. Packet is a handle into
103         the internal packet representation. From Packet you may access the data of that specific
104         sub-packet/header/interpreter and navigate to the neighboring
105         sub-packets/headers/interpreters.
106
107         Packet is protocol agnostic. This class only provides non-protocol dependent members. To
108         access the protocol specific features of a packet (like header fields) the ConcretePacket
109         class extending Packet is provided.
110
111         \section packet_semantics Semantics
112         
113         All operations accessing the data of \c this packet in some way will ignore any preceding
114         packets/headers/interpreters in the chain. It does not matter, whether a given packet is
115         taken from the middle or the beginning of the chain, all operations (except those explicitly
116         accessing the chain of course) should work the same.
117         
118         This especially includes members like clone() or append(): clone() will clone \e only from
119         \c this packet until the end of the chain, append() will append the given packet \e ignoring
120         any possibly preceding packets/headers/interpreters.
121
122         In the same way, the data() member provides an STL-sequence compatible view of the packet
123         data. This only includes the data which is part of \c this packet including header, trailer
124         \e and payload but \e not the headers or trailers of packets \e before \c this packet in the
125         packet/header/interpreter chain (nonetheless, this data overlaps with the data of other
126         packets).
127
128         Several members are member templates taking an \a OtherPacket template parameter. This
129         parameter must be the ConcretePacket instantiation associated with some concrete packet type
130         (protocol). For each implemented protocol, typedefs should be provided for these
131         instantiations (Example: \ref EthernetPacket is a typedef for
132         \ref ConcretePacket < \ref EthernetPacketType >).
133
134         \see 
135             \ref ConcretePacket for the type specific interface\n
136             \ref PacketData for the sequence interface\n
137             \ref packetparser for a specification of the parser interface
138       */
139     class Packet
140         : public safe_bool<Packet>,
141           public boost::equality_comparable<Packet>
142     {
143     public:
144         ///////////////////////////////////////////////////////////////////////////
145         // Types
146         
147         typedef void type;              ///< Type of the packet.
148         typedef senf::detail::packet::size_type size_type;
149                                         ///< Unsigned type to represent packet size
150         typedef PacketInterpreterBase::factory_t factory_t; ///< Packet factory type (see below)
151
152         enum NoInit_t { noinit };       ///< Special argument flag
153                                         /**< Used in some ConcretePacket constructors */
154
155         ///////////////////////////////////////////////////////////////////////////
156         ///\name Structors and default members
157         ///@{
158
159         // default copy constructor
160         // default copy assignment
161         // default destructor
162         
163         Packet();                       ///< Create uninitialized packet handle
164                                         /**< An uninitialized handle is in - valid(). It does not
165                                              allow any operation except assignment and checking for
166                                              validity. */
167         Packet clone() const;           ///< Create copy packet
168                                         /**< clone() will create a complete copy of \c this
169                                              packet. The returned packet will have the same data and
170                                              packet chain. It does however not share any data with
171                                              the original packet. */
172
173         // conversion constructors
174
175         template <class PacketType>     
176         Packet(ConcretePacket<PacketType> packet); ///< Copy-construct Packet from ConcretePacket
177                                         /**< This constructor allows to convert an arbitrary
178                                              ConcretePacket into a general Packet, loosing the
179                                              protocol specific interface. */
180
181         ///@}
182         ///////////////////////////////////////////////////////////////////////////
183
184         ///\name Interpreter chain access
185         ///@{
186
187                                      Packet      next() const; 
188                                         ///< Get next packet in chain
189                                         /**< \throws InvalidPacketChainException if no next packet 
190                                              exists */
191                                      Packet      next(NoThrow_t) const; 
192                                         ///< Get next packet in chain
193                                         /**< \returns in - valid() packet if no next packet 
194                                              exists */
195         template <class OtherPacket> OtherPacket next() const; 
196                                         ///< Get next packet in chain and cast to \a OtherPacket
197                                         /**< \throws std::bad_cast if the next() packet is not of
198                                              type \a OtherPacket
199                                              \throws InvalidPacketChainException if no next packet
200                                                  exists */
201         template <class OtherPacket> OtherPacket next(NoThrow_t) const; 
202                                         ///< Get next packet in chain and cast to \a OtherPacket
203                                         /**< \throws std::bad_cast if the next() packet is not of
204                                              type \a OtherPacket
205                                              \returns in - valid() packet if no next packet
206                                                  exists */
207         template <class OtherPacket> OtherPacket find() const;
208                                         ///< Search chain forward for packet of type \a OtherPacket
209                                         /**< The search will start with the current packet.
210                                              \throws InvalidPacketChainException if no packet of
211                                                  type \a OtherPacket can be found. */
212         template <class OtherPacket> OtherPacket find(NoThrow_t) const;
213                                         ///< Search chain forward for packet of type \a OtherPacket
214                                         /**< The search will start with the current packet.
215                                              \returns in - valid() packet if no packet of type \a
216                                                  OtherPacket can be found. */
217         
218                                      Packet      prev() const; 
219                                         ///< Get previous packet in chain
220                                         /**< \throws InvalidPacketChainException if no previous
221                                              packet exists */
222                                      Packet      prev(NoThrow_t) const; 
223                                         ///< Get previous packet in chain
224                                         /**< \returns in - valid() packet if no previous packet
225                                              exists */
226         template <class OtherPacket> OtherPacket prev() const; 
227                                         ///< Get previous packet in chain and cast to \a OtherPacket
228                                         /**< \throws std::bad_cast, if the previous packet is not of
229                                              type \a OtherPacket
230                                              \throws InvalidPacketChainException if no previous
231                                                  packet exists */
232         template <class OtherPacket> OtherPacket prev(NoThrow_t) const; 
233                                         ///< Get previous packet in chain and cast to \a OtherPacket
234                                         /**< \throws std::bad_cast, if the previous packet is not of
235                                              type \a OtherPacket
236                                              \returns in - valid() packet if no previous packet 
237                                                  exists */
238         template <class OtherPacket> OtherPacket rfind() const;
239                                         ///< Search chain backwards for packet of type \a OtherPacket
240                                         /**< The search will start with the current packet.
241                                              \throws InvalidPacketChainException if no packet of
242                                                  type \a OtherPacket can be found. */
243         template <class OtherPacket> OtherPacket rfind(NoThrow_t) const;
244                                         ///< Search chain backwards for packet of type \a OtherPacket
245                                         /**< The search will start with the current packet.
246                                              \returns in - valid() packet if no packet of type \a
247                                                  OtherPacket can be found. */
248
249
250                                      Packet      first() const;
251                                         ///< Return first packet in chain
252         template <class OtherPacket> OtherPacket first() const;
253                                         ///< Return first packet in chain and cast
254                                         /**< \throws std::bad_cast if the first() packet is not of
255                                              type \a OtherPacket */
256
257                                      Packet      last() const;
258                                         ///< Return last packet in chain
259         template <class OtherPacket> OtherPacket last() const;
260                                         ///< Return last packet in chain and cast
261                                         /**< \throws std::bad_cast if the last() packet is not of
262                                              type \a OtherPacket  */
263
264
265         template <class OtherPacket> OtherPacket parseNextAs() const;
266                                         ///< Interpret payload of \c this as \a OtherPacket
267                                         /**< parseNextAs() will throw away the packet chain after
268                                              the current packet if necessary. It will then parse the
269                                              payload section of \c this packet as given by \a
270                                              OtherPacket. The new packet is added to the chain after
271                                              \c this.
272                                              \returns new packet instance sharing the same data and
273                                                  placed after \c this packet in the chain. */
274                                      Packet      parseNextAs(factory_t factory) const;
275                                         ///< Interpret payload of \c this as \a factory type packet
276                                         /**< parseNextAs() will throw away the packet chain after
277                                              the current packet if necessary. It will then parse the
278                                              payload section of \c this packet as given by \a
279                                              factory. The new packet is added to the chain after
280                                              \c this.
281                                              \returns new packet instance sharing the same data and
282                                                  placed after \c this packet in the chain. */
283
284         template <class OtherPacket> bool        is() const;
285                                         ///< Check, whether \c this packet is of the given type
286         template <class OtherPacket> OtherPacket as() const;
287                                         ///< Cast current packet to the given type
288                                         /**< This operations returns a handle to the same packet
289                                              header/interpreter however cast to the given
290                                              ConcretePacket type.
291                                              \throws std::bad_cast if the current packet is not of
292                                                  type \a OtherPacket */
293
294         Packet append(Packet packet) const; ///< Append the given packet to \c this packet
295                                         /**< This operation will replace the payload section of \c
296                                              this packet with \a packet. This operation will replace
297                                              the packet chain after \c this packet with a clone of
298                                              \a packet and will replace the raw data of the payload
299                                              of \c this with the raw data of \a packet. \c this
300                                              packet will not share any date with \a packet.
301                                              \returns Packet handle to the cloned \a packet, placed
302                                                  after \c this in the packet/header/interpreter
303                                                  chain. */
304
305         ///@}
306
307         ///\name Data access
308         ///@{
309
310         PacketData & data() const;      ///< Access the packets raw data container
311         size_type size() const;         ///< Return size of packet in bytes
312                                         /**< This size does \e not include the size of any preceding
313                                              headers/packets/interpreters. It does however include
314                                              \c this packets payload. */
315         
316         ///@}
317
318         ///\name Other methods
319         ///@{
320
321         bool operator==(Packet other) const; ///< Check for packet identity
322                                         /**< Two packet handles compare equal if they really are the
323                                              same packet header in the same packet chain. */
324         bool boolean_test() const;      ///< Check, whether the packet is valid()
325                                         /**< \see valid() */
326         bool valid() const;             ///< Check, whether the packet is valid()
327                                         /**< An in - valid() packet does not allow any operation
328                                              except checking for validity and assignment. in -
329                                              valid() packets serve the same role as 0-pointers. 
330                                              
331                                              This is an alias for boolean_test() which is called
332                                              when using a packet in a boolean context. */
333
334         void finalize() const;          ///< Update calculated fields
335                                         /**< This call will update all calculated fields of the
336                                              packet after it has been created or changed. This
337                                              includes checksums, payload size fields or other
338                                              fields, which can be set from other information in the
339                                              packet. Each concrete packet type should document,
340                                              which fields are set by finalize().
341
342                                              finalize() will automatically process all
343                                              packets/headers/interpreters from the end of the chain
344                                              backwards up to \c this. */
345
346         void dump(std::ostream & os) const; ///< Write out a printable packet representation
347                                         /**< This method is provided mostly to help debugging packet
348                                              problems. Each concrete packet should implement a dump
349                                              method writing out all fields of the packet in a
350                                              readable representation. dump() will call this member
351                                              for each packet/header/interpreter in the chain from \c
352                                              this packet up to the end of the chain. */
353
354         TypeIdValue typeId() const;     ///< Get id of \c this packet
355                                         /**< This value is used e.g. in the packet registry to
356                                              associate packet types with other information.
357                                              \returns A type holding the same information as a
358                                                  type_info object, albeit assignable */
359         factory_t factory() const;      ///< Return factory instance of \c this packet
360                                         /**< The returned factory instance can be used to create new
361                                              packets of the given type without knowing the concrete
362                                              type of the packet. The value may be stored away for
363                                              later use if needed. */
364         
365         ///@}
366
367     protected:
368         explicit Packet(PacketInterpreterBase::ptr packet);
369
370         PacketInterpreterBase::ptr ptr() const;
371
372     private:
373         Packet checkNext() const;
374         Packet checkLast() const;
375         
376         PacketInterpreterBase::ptr packet_;
377         
378         template <class PacketType>
379         friend class ConcretePacket;
380         friend class PacketParserBase;
381     };
382
383     /** \brief Protocol specific packet handle
384
385         The ConcretePacket template class extends Packet to provide protocol/packet type specific
386         aspects. These are packet constructors and access to the parsed packet fields.
387
388         The \c PacketType template argument to ConcretePacket is a protocol specific and internal
389         policy class which defines the protocol specific behavior. To access a specific type of
390         packet, the library provides corresponding typedefs of ConcretePacket < \a SomePacketType >
391         (e.g. \ref EthernetPacket as typedef for \ref ConcretePacket < \ref EthernetPacketType >).
392
393         The new members provided by ConcretePacket over packet are mostly comprised of the packet
394         constructors. These come in three major flavors:
395         
396         \li The create() family of constructors will create completely new packets.
397         \li The createAfter() family of constructors will create new packets (with new data for the
398             packet) \e after a given existing packet.
399         \li The createBefore()  family of constructors will create new packets (again with new data)
400             \e before a given existing packet.
401         
402         Whereas create() will create a completely new packet with it's own chain and data storage,
403         createAfter() and createBefore() extend a packet with additional
404         headers/interpreters. createAfter() will set the payload of the given packet to the new
405         packet whereas createBefore() will create a new packet with the existing packet as it's
406         payload. 
407
408         createAfter() differs from Packet::parseNextAs() in that the former creates a new packet \e
409         replacing any possibly existing data whereas the latter will interpret the already \e
410         existing data as given by the type argument.
411         
412         \see \ref PacketTypeBase for a specification of the interface to be provided by the \a
413             PacketType policy class.
414       */
415     template <class PacketType>
416     class ConcretePacket 
417         : public Packet
418     {
419     public:
420         ///////////////////////////////////////////////////////////////////////////
421         // Types
422         
423         typedef PacketType type;
424         typedef typename PacketType::parser Parser;
425
426         ///////////////////////////////////////////////////////////////////////////
427         ///\name Structors and default members
428         ///@{
429
430         // default copy constructor
431         // default copy assignment
432         // default destructor
433         // no conversion constructors
434
435         ConcretePacket();               ///< Create uninitialized packet handle
436                                         /**< An uninitialized handle is not valid(). It does not
437                                              allow any operation except assignment and checking for
438                                              validity. */
439
440         static factory_t factory();     ///< Return factory for packets of specific type
441                                         /**< This \e static member is like Packet::factory() for a
442                                              specific packet of type \a PacketType */
443
444         // Create completely new packet
445
446         static ConcretePacket create(); ///< Create default initialized packet
447                                         /**< The packet will be initialized to it's default empty
448                                              state. */
449         static ConcretePacket create(NoInit_t); ///< Create uninitialized empty packet
450                                         /**< This will create a completely empty and uninitialized
451                                              packet with <tt>size() == 0</tt>.
452                                              \param[in] noinit This parameter must always have the
453                                                  value \c senf::noinit. */
454         static ConcretePacket create(size_type size); ///< Create default initialized packet
455                                         /**< This member will create a default initialized packet
456                                              with the given size. If the size parameter is smaller
457                                              than the minimum allowed packet size an exception will
458                                              be thrown.
459                                              \param[in] size Size of the packet to create in bytes.
460                                              \throws TruncatedPacketException if \a size is smaller
461                                                  than the smallest permissible size for this type of
462                                                  packet. */
463         static ConcretePacket create(size_type size, NoInit_t); ///< Create uninitialized packet
464                                         /**< Creates an uninitialized (all-zero) packet of the exact
465                                              given size. 
466                                              \param[in] size Size of the packet to create in bytes
467                                              \param[in] noinit This parameter must always have the
468                                                  value \c senf::noinit. */
469         template <class ForwardReadableRange>
470         static ConcretePacket create(ForwardReadableRange const & range); 
471                                         ///< Create packet from given data
472                                         /**< The packet will be created from a copy of the given
473                                              data. The data from the range will be copied directly
474                                              into the packet representation. The data will \e not be
475                                              validated in any way.
476                                              \param[in] range <a
477                                                  href="http://www.boost.org/libs/range/index.html">Boost.Range</a> 
478                                                  of data to construct packet from. */
479
480         // Create packet as new packet after a given packet
481
482         static ConcretePacket createAfter(Packet packet); 
483                                         ///< Create default initialized packet after \a packet
484                                         /**< The packet will be initialized to it's default empty
485                                              state. It will be appended as next header/interpreter
486                                              after \a packet in that packets interpreter chain.
487                                              \param[in] packet Packet to append new packet to. */
488         static ConcretePacket createAfter(Packet packet, NoInit_t);
489                                         ///< Create uninitialized empty packet after\a packet
490                                         /**< This will create a completely empty and uninitialized
491                                              packet with <tt>size() == 0</tt>. It will be appended
492                                              as next header/interpreter after \a packet in that
493                                              packets interpreter chain.
494                                              \param[in] packet Packet to append new packet to.
495                                              \param[in] noinit This parameter must always have the
496                                                  value \c senf::noinit. */
497         static ConcretePacket createAfter(Packet packet, size_type size);
498                                         ///< Create default initialized packet after \a packet
499                                         /**< This member will create a default initialized packet
500                                              with the given size. If the size parameter is smaller
501                                              than the minimum allowed packet size an exception will
502                                              be thrown. It will be appended as next
503                                              header/interpreter after \a packet in that packets
504                                              interpreter chain.
505                                              \param[in] packet Packet to append new packet to.
506                                              \param[in] size Size of the packet to create in bytes.
507                                              \throws TruncatedPacketException if \a size is smaller
508                                                  than the smallest permissible size for this type of
509                                                  packet. */
510         static ConcretePacket createAfter(Packet packet, size_type size, NoInit_t);
511                                         ///< Create uninitialized packet after \a packet
512                                         /**< Creates an uninitialized (all-zero) packet of the exact
513                                              given size.  It will be appended as next
514                                              header/interpreter after \a packet in that packets
515                                              interpreter chain.
516                                              \param[in] packet Packet to append new packet to.
517                                              \param[in] size Size of the packet to create in bytes
518                                              \param[in] noinit This parameter must always have the
519                                                  value \c senf::noinit. */
520         template <class ForwardReadableRange>
521         static ConcretePacket createAfter(Packet packet, 
522                                           ForwardReadableRange const & range);
523                                         ///< Create packet from given data after \a packet
524                                         /**< The packet will be created from a copy of the given
525                                              data. The data from the range will be copied directly
526                                              into the packet representation. The data will \e not be
527                                              validated in any way.  It will be appended as next
528                                              header/interpreter after \a packet in that packets
529                                              interpreter chain.
530                                              \param[in] packet Packet to append new packet to.
531                                              \param[in] range <a
532                                                  href="http://www.boost.org/libs/range/index.html">Boost.Range</a> 
533                                                  of data to construct packet from. */
534
535         // Create packet as new packet (header) before a given packet
536
537         static ConcretePacket createBefore(Packet packet); 
538                                         ///< Create default initialized packet before \a packet
539                                         /**< The packet will be initialized to it's default empty
540                                              state. It will be prepended as previous
541                                              header/interpreter before \a packet in that packets
542                                              interpreter chain.
543                                              \param[in] packet Packet to prepend new packet to. */
544         static ConcretePacket createBefore(Packet packet, NoInit_t);
545                                         ///< Create uninitialized empty packet before \a packet
546                                         /**< Creates a completely empty and uninitialized packet. It
547                                              will be prepended as previous header/interpreter before
548                                              \a packet in that packets interpreter chain.
549                                              \param[in] packet Packet to prepend new packet to. */
550         
551         // Create a clone of the current packet
552
553         ConcretePacket clone() const;
554
555         ///@}
556         ///////////////////////////////////////////////////////////////////////////
557
558         // Field access
559
560         Parser * operator->() const;    ///< Access packet fields
561                                         /**< This operator allows to access the parsed fields of the
562                                              packet using the notation <tt>packet->field()</tt>. The
563                                              fields of the packet are specified by the PacketType's
564                                              \c parser member. 
565
566                                              The members are not strictly restricted to simple field
567                                              access. The parser class may have any member which is
568                                              needed for full packet access (e.g. checksum validation
569                                              / recreation ...)
570                                              \see \ref packetparser for the parser interface. */
571
572         Parser parser() const;          ///< Access packet field parser directly
573                                         /**< Access the parser of the packet. This is the same
574                                              object returned by the operator->() operator. The
575                                              operator however does not allow to access this object
576                                              itself, only it's members.
577                                              \see \ref packetparser for the parser interface */
578
579     protected:
580
581     private:
582         typedef PacketInterpreter<PacketType> interpreter;
583
584         ConcretePacket(typename interpreter::ptr packet_);
585         
586         typename interpreter::ptr ptr() const;
587
588         friend class Packet;
589         friend class PacketInterpreter<PacketType>;
590     };
591
592     ///@}
593
594 }
595
596 ///////////////////////////////hh.e////////////////////////////////////////
597 #endif
598 #if !defined(HH_Packets__decls_) && !defined(HH_Packet_i_)
599 #define HH_Packet_i_
600 #include "Packet.cci"
601 #include "Packet.ct"
602 #include "Packet.cti"
603 #endif
604
605 \f
606 // Local Variables:
607 // mode: c++
608 // fill-column: 100
609 // c-file-style: "senf"
610 // indent-tabs-mode: nil
611 // ispell-local-dictionary: "american"
612 // compile-command: "scons -u test"
613 // comment-column: 40
614 // End:
615