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