4 // Fraunhofer Institute for Open Communication Systems (FOKUS)
5 // Competence Center NETwork research (NET), St. Augustin, GERMANY
6 // Stefan Bund <g0dil@berlios.de>
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.
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.
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.
24 \brief Packet public header */
30 #include <boost/operators.hpp>
31 #include <boost/utility.hpp>
32 #include <boost/type_traits/is_integral.hpp>
33 #include "../Utils/Exception.hh"
34 #include "../Utils/Tags.hh"
35 #include "../Utils/safe_bool.hh"
36 #include "PacketInterpreter.hh"
38 //#include "Packet.mpp"
39 ///////////////////////////////hh.p////////////////////////////////////////
43 /** \defgroup packet_module Packet Handling
45 The basic groundwork of the %Packet library is the packet handling:
47 \li The packet classes provide access to a chain of packet headers (more generically called
49 \li They automatically manage the required memory resources and the shared packet data.
51 \section packet_module_chain The Interpreter Chain
53 The central data structure for a packet is the interpreter chain
55 \image html structure.png The Interpreter Chain
57 This image depicts a packet with several headers. Each interpreter is responsible for a
58 specific sub-range of the complete packet. This range always \e includes the packets payload
59 (This is, why we call the data structure interpreter and not header: The interpreter is
60 responsible for interpreting a range of the packet according to a specific protocol), the
61 packet interpreters are nested inside each other.
63 For each interpreter, this structure automatically divides the packet into three areas (each
64 of which are optional): The header, the payload and the trailer. Every packet will have
65 either a header or a payload section while most don't have a trailer.
67 As user of the library you always interact with the chain through one (or more) of the
68 interpreters. The interpreter provides methods to traverse to the following or preceding
69 header (interpreter) and provides two levels of access to the packet data: Generic low-level
70 access in the form of an STL compatible sequence and access to the parsed fields which are
71 provided by the parser associated with the concrete packet type.
73 \section packet_module_management Resource Management
75 The interface to the packet library is provided using a handle class (\ref Packet for
76 generic, protocol agnostic access and \ref ConcretePacket derived from \ref Packet to access
77 a specific protocol). This handle automatically manages the resources associated with the
78 packet (the interpreter chain and the data storage holding the packet data). The resources
79 are automatically released when the last packet handle referencing a specific packet is
82 \implementation The packet chain is provided on two levels: The internal representation \ref
83 PacketInterpreterBase and \ref PacketInterpreter which are referenced by the Handle
84 classes \ref Packet and \ref ConcretePacket. \n
85 The internal representation classes are pertinent in the sense, that they exist
86 regardless of the existence of a handle referencing them (as long as the packet
87 exists). Still the interpreter chain is lazy and packet interpreters beside the first
88 are only created dynamically when accessed (this is implemented in the handle not in the
89 internal representation). \n
90 The packet interpreters make use of a pool allocator. This provides extremely efficient
91 creation and destruction of packet interpreter's and removes the dynamic memory
92 management overhead from the packet interpreter management. The packet implementation
93 class (\ref PacketImpl which holds the packet data itself) however is still dynamically
94 managed (however there is only a single instance for each packet).
97 template <class PackeType> class ConcretePacket;
99 ///\addtogroup packet_module
102 /** \brief Main %Packet class
104 %Packet is the main externally visible class of the packet library. %Packet is a handle into
105 the internal packet representation. From %Packet you may access the data of that specific
106 sub-packet/header/interpreter and navigate to the neighboring
107 sub-packets/headers/interpreters.
109 %Packet is protocol agnostic. This class only provides non-protocol dependent members. To
110 access the protocol specific features of a packet (like header fields) the ConcretePacket
111 class extending %Packet is provided.
113 \section packet_semantics Semantics
115 All operations accessing the data of \c this packet in some way will ignore any preceding
116 packets/headers/interpreters in the chain. It does not matter, whether a given packet is
117 taken from the middle or the beginning of the chain, all operations (except those explicitly
118 accessing the chain of course) should work the same.
120 This especially includes members like clone() or append(): clone() will clone \e only from
121 \c this packet until the end of the chain, append() will append the given packet \e ignoring
122 any possibly preceding packets/headers/interpreters.
124 In the same way, the data() member provides an STL-sequence compatible view of the packet
125 data. This only includes the data which is part of \c this packet including header, trailer
126 \e and payload but \e not the headers or trailers of packets \e before \c this packet in the
127 packet/header/interpreter chain (nonetheless, this data overlaps with the data of other
130 Several members are member templates taking an \a OtherPacket template parameter. This
131 parameter must be the ConcretePacket instantiation associated with some concrete packet type
132 (protocol). For each implemented protocol, typedefs should be provided for these
133 instantiations (Example: \ref EthernetPacket is a typedef for
134 \ref ConcretePacket < \ref EthernetPacketType >).
137 \ref ConcretePacket for the %type specific interface\n
138 \ref PacketData for the sequence interface\n
139 \ref packetparser for a specification of the parser interface
142 : public safe_bool<Packet>,
143 public boost::equality_comparable<Packet>
146 ///////////////////////////////////////////////////////////////////////////
149 typedef void type; ///< Type of the packet.
150 typedef senf::detail::packet::size_type size_type;
151 ///< Unsigned type to represent packet size
152 typedef PacketInterpreterBase::factory_t factory_t; ///< Packet factory type (see below)
154 ///////////////////////////////////////////////////////////////////////////
155 ///\name Structors and default members
158 // default copy constructor
159 // default copy assignment
160 // default destructor
162 Packet(); ///< Create uninitialized packet handle
163 /**< An uninitialized handle is in - valid(). It does not
164 allow any operation except assignment and checking for
166 Packet clone() const; ///< Create copy packet
167 /**< clone() will create a complete copy of \c this
168 packet. The returned packet will have the same data and
169 packet chain. It does however not share any data with
170 the original packet. */
172 // conversion constructors
174 template <class PacketType>
175 Packet(ConcretePacket<PacketType> packet); ///< Copy-construct Packet from ConcretePacket
176 /**< This constructor allows to convert an arbitrary
177 ConcretePacket into a general Packet, loosing the
178 protocol specific interface. */
181 ///////////////////////////////////////////////////////////////////////////
183 ///\name Interpreter chain access
187 ///< Get next packet in chain
188 /**< \throws InvalidPacketChainException if no next packet
190 Packet next(NoThrow_t) const;
191 ///< Get next packet in chain
192 /**< \returns in - valid() packet if no next packet
194 template <class OtherPacket> OtherPacket next() const;
195 ///< Get next packet in chain and cast to \a OtherPacket
196 /**< \throws std::bad_cast if the next() packet is not of
198 \throws InvalidPacketChainException if no next packet
200 template <class OtherPacket> OtherPacket next(NoThrow_t) const;
201 ///< Get next packet in chain and cast to \a OtherPacket
202 /**< \throws std::bad_cast if the next() packet is not of
204 \returns in - valid() packet if no next packet
206 template <class OtherPacket> OtherPacket find() const;
207 ///< Search chain forward for packet of type \a OtherPacket
208 /**< The search will start with the current packet.
209 \throws InvalidPacketChainException if no packet of
210 type \a OtherPacket can be found. */
211 template <class OtherPacket> OtherPacket find(NoThrow_t) const;
212 ///< Search chain forward for packet of type \a OtherPacket
213 /**< The search will start with the current packet.
214 \returns in - valid() packet if no packet of type \a
215 OtherPacket can be found. */
218 ///< Get previous packet in chain
219 /**< \throws InvalidPacketChainException if no previous
221 Packet prev(NoThrow_t) const;
222 ///< Get previous packet in chain
223 /**< \returns in - valid() packet if no previous packet
225 template <class OtherPacket> OtherPacket prev() const;
226 ///< Get previous packet in chain and cast to \a OtherPacket
227 /**< \throws std::bad_cast, if the previous packet is not of
229 \throws InvalidPacketChainException if no previous
231 template <class OtherPacket> OtherPacket prev(NoThrow_t) const;
232 ///< Get previous packet in chain and cast to \a OtherPacket
233 /**< \throws std::bad_cast, if the previous packet is not of
235 \returns in - valid() packet if no previous packet
237 template <class OtherPacket> OtherPacket rfind() const;
238 ///< Search chain backwards for packet of type \a OtherPacket
239 /**< The search will start with the current packet.
240 \throws InvalidPacketChainException if no packet of
241 type \a OtherPacket can be found. */
242 template <class OtherPacket> OtherPacket rfind(NoThrow_t) const;
243 ///< Search chain backwards for packet of type \a OtherPacket
244 /**< The search will start with the current packet.
245 \returns in - valid() packet if no packet of type \a
246 OtherPacket can be found. */
249 Packet first() const;
250 ///< Return first packet in chain
251 template <class OtherPacket> OtherPacket first() const;
252 ///< Return first packet in chain and cast
253 /**< \throws std::bad_cast if the first() packet is not of
254 type \a OtherPacket */
257 ///< Return last packet in chain
258 template <class OtherPacket> OtherPacket last() const;
259 ///< Return last packet in chain and cast
260 /**< \throws std::bad_cast if the last() packet is not of
261 type \a OtherPacket */
264 template <class OtherPacket> OtherPacket parseNextAs() const;
265 ///< Interpret payload of \c this as \a OtherPacket
266 /**< parseNextAs() will throw away the packet chain after
267 the current packet if necessary. It will then parse the
268 payload section of \c this packet as given by \a
269 OtherPacket. The new packet is added to the chain after
271 \returns new packet instance sharing the same data and
272 placed after \c this packet in the chain. */
273 Packet parseNextAs(factory_t factory) const;
274 ///< Interpret payload of \c this as \a factory type packet
275 /**< parseNextAs() will throw away the packet chain after
276 the current packet if necessary. It will then parse the
277 payload section of \c this packet as given by \a
278 factory. The new packet is added to the chain after
280 \returns new packet instance sharing the same data and
281 placed after \c this packet in the chain. */
283 template <class OtherPacket> bool is() const;
284 ///< Check, whether \c this packet is of the given type
285 template <class OtherPacket> OtherPacket as() const;
286 ///< Cast current packet to the given type
287 /**< This operations returns a handle to the same packet
288 header/interpreter however cast to the given
290 \throws std::bad_cast if the current packet is not of
291 type \a OtherPacket */
293 Packet append(Packet packet) const; ///< Append the given packet to \c this packet
294 /**< This operation will replace the payload section of \c
295 this packet with \a packet. This operation will replace
296 the packet chain after \c this packet with a clone of
297 \a packet and will replace the raw data of the payload
298 of \c this with the raw data of \a packet. \c this
299 packet will not share any date with \a packet.
300 \returns Packet handle to the cloned \a packet, placed
301 after \c this in the packet/header/interpreter
309 PacketData & data() const; ///< Access the packets raw data container
310 size_type size() const; ///< Return size of packet in bytes
311 /**< This size does \e not include the size of any preceding
312 headers/packets/interpreters. It does however include
313 \c this packets payload. */
320 template <class Annotation>
321 Annotation & annotation(); ///< Get packet annotation
322 /**< This member will retrieve an arbitrary packet
323 annotation. Every annotation is identified by a unique
324 \a Annotation type. This type should \e always be a \c
328 struct MyAnnotation {
332 senf::Packet p (...);
334 p.annotation<MyAnnotation>().value = 1;
337 Annotations are shared by all headers / interpreters
338 within a single packet chain.
340 If an annotation is \e not a POD type (more
341 specifically, if it's constructor or destructor is not
342 trivial including base classes and members), the \a
343 Annotation type \e must inherit from
344 senf::ComplexAnnotation. Failing to follow this rule
345 will result in undefined behavior and will probably
346 lead to a program crash.
349 struct MyStringAnnotation : senf::ComplexAnnotation {
353 (This type is not POD since \c std::string is not POD)
355 \see \ref packet_usage_annotation
357 \implementation The annotation system is implemented
358 quite efficiently since annotations are stored
359 within a packet embedded vector of fixed size (the
360 size is determined automatically at runtime by the
361 number of different annotations
362 used). Additionally, non-complex small annotations
363 require no additional memory management (\c new /
366 \idea Pool the annotation vectors: In the destructor
367 swap the vector into a vector graveyard (swapping
368 two vectors is an O(1) no allocation operation). In
369 the constructor, if there is a vector in the
370 graveyard, swap it in from there. Of course, it
371 would be better to do away with the vector and just
372 allocate the space together with the packet but
373 that looks quite complicated to do ... especially
374 considering that the packetimpl itself uses a pool.
379 ///\name Other methods
382 bool operator==(Packet other) const; ///< Check for packet identity
383 /**< Two packet handles compare equal if they really are the
384 same packet header in the same packet chain. */
385 bool boolean_test() const; ///< Check, whether the packet is valid()
387 bool valid() const; ///< Check, whether the packet is valid()
388 /**< An in - valid() packet does not allow any operation
389 except checking for validity and assignment. in -
390 valid() packets serve the same role as 0-pointers.
392 This is an alias for boolean_test() which is called
393 when using a packet in a boolean context. */
395 void finalizeThis(); ///< Update calculated fields
396 /**< The finalize() fammily of members will update
397 calculated packet fields: checksums, size fields and so
398 on. This includes any field, which can be set from
399 other information in the packet. Each concrete packet
400 type should document, which fields are set by
403 finalizeThis() will \e only process the current
404 header. Even if only changing fields in this protocol,
405 depending on the protocol it may not be enough to
406 finalize this header only. See the packet type
409 template <class Other>
410 void finalizeTo(); ///< Update calculated fields
411 /**< The finalize() fammily of members will update
412 calculated packet fields: checksums, size fields and so
413 on. This includes any field, which can be set from
414 other information in the packet. Each concrete packet
415 type should document, which fields are set by
418 finalizeTo() will automatically process all
419 packets/headers/interpreters from the \e first
420 occurrence of packet type \a Other (beginning at \c
421 this packet searching forward towards deeper nested
422 packets) backwards up to \c this.
424 This call is equivalent to
426 p.finalizeTo(p.next<Other>())
429 void finalizeTo(Packet other); ///< Update calculated fields
430 /**< The finalize() fammily of members will update
431 calculated packet fields: checksums, size fields and so
432 on. This includes any field, which can be set from
433 other information in the packet. Each concrete packet
434 type should document, which fields are set by
437 finalizeTo(other) will automatically process all
438 packets/headers/interpreters beginning at \a other
439 backwards towards outer packets up to \c this. */
441 void finalizeAll(); ///< Update calculated fields
442 /**< The finalize() fammily of members will update
443 calculated packet fields: checksums, size fields and so
444 on. This includes any field, which can be set from
445 other information in the packet. Each concrete packet
446 type should document, which fields are set by
449 finalizeAll() will automatically process all
450 packets/headers/interpreters from the end of the chain
451 (the most inner packet) backwards up to \c this.
453 This call is equivalent to
455 p.finalizeTo(p.last())
458 Beware, that finalizeAll() will \e not finalize any
459 headers before \c this, it will \e only process inner
462 void dump(std::ostream & os) const; ///< Write out a printable packet representation
463 /**< This method is provided mostly to help debugging packet
464 problems. Each concrete packet should implement a dump
465 method writing out all fields of the packet in a
466 readable representation. dump() will call this member
467 for each packet/header/interpreter in the chain from \c
468 this packet up to the end of the chain. */
470 TypeIdValue typeId() const; ///< Get id of \c this packet
471 /**< This value is used e.g. in the packet registry to
472 associate packet types with other information.
473 \returns A type holding the same information as a
474 type_info object, albeit assignable */
475 factory_t factory() const; ///< Return factory instance of \c this packet
476 /**< The returned factory instance can be used to create new
477 packets of the given type without knowing the concrete
478 type of the packet. The value may be stored away for
479 later use if needed. */
484 explicit Packet(PacketInterpreterBase::ptr packet);
486 PacketInterpreterBase::ptr ptr() const;
489 Packet checkNext() const;
490 Packet checkLast() const;
492 PacketInterpreterBase::ptr packet_;
494 template <class PacketType>
495 friend class ConcretePacket;
496 friend class PacketParserBase;
499 /** \brief Protocol specific packet handle
501 The ConcretePacket template class extends Packet to provide protocol/packet type specific
502 aspects. These are packet constructors and access to the parsed packet fields.
504 The \c PacketType template argument to ConcretePacket is a protocol specific and internal
505 policy class which defines the protocol specific behavior. To access a specific type of
506 packet, the library provides corresponding typedefs of ConcretePacket < \a SomePacketType >
507 (e.g. \ref EthernetPacket as typedef for \ref ConcretePacket < \ref EthernetPacketType >).
509 The new members provided by ConcretePacket over packet are mostly comprised of the packet
510 constructors. These come in three major flavors:
512 \li The create() family of constructors will create completely new packets.
513 \li The createAfter() family of constructors will create new packets (with new data for the
514 packet) \e after a given existing packet.
515 \li The createBefore() family of constructors will create new packets (again with new data)
516 \e before a given existing packet.
518 Whereas create() will create a completely new packet with it's own chain and data storage,
519 createAfter() and createBefore() extend a packet with additional
520 headers/interpreters. createAfter() will set the payload of the given packet to the new
521 packet whereas createBefore() will create a new packet with the existing packet as it's
524 createAfter() differs from Packet::parseNextAs() in that the former creates a new packet \e
525 replacing any possibly existing data whereas the latter will interpret the already \e
526 existing data as given by the type argument.
528 \see \ref PacketTypeBase for a specification of the interface to be provided by the \a
529 PacketType policy class.
531 template <class PacketType>
536 ///////////////////////////////////////////////////////////////////////////
539 typedef PacketType type;
540 typedef typename PacketType::parser Parser;
542 ///////////////////////////////////////////////////////////////////////////
543 ///\name Structors and default members
546 // default copy constructor
547 // default copy assignment
548 // default destructor
549 // no conversion constructors
551 ConcretePacket(); ///< Create uninitialized packet handle
552 /**< An uninitialized handle is not valid(). It does not
553 allow any operation except assignment and checking for
556 static factory_t factory(); ///< Return factory for packets of specific type
557 /**< This \e static member is like Packet::factory() for a
558 specific packet of type \a PacketType */
560 // Create completely new packet
562 static ConcretePacket create(); ///< Create default initialized packet
563 /**< The packet will be initialized to it's default empty
565 static ConcretePacket create(senf::NoInit_t); ///< Create uninitialized empty packet
566 /**< This will create a completely empty and uninitialized
567 packet with <tt>size() == 0</tt>.
568 \param[in] senf::noinit This parameter must always have the
569 value \c senf::noinit. */
570 static ConcretePacket create(size_type size); ///< Create default initialized packet
571 /**< This member will create a default initialized packet
572 with the given size. If the size parameter is smaller
573 than the minimum allowed packet size an exception will
575 \param[in] size Size of the packet to create in bytes.
576 \throws TruncatedPacketException if \a size is smaller
577 than the smallest permissible size for this type of
579 static ConcretePacket create(size_type size, senf::NoInit_t);
580 ///< Create uninitialized packet
581 /**< Creates an uninitialized (all-zero) packet of the exact
583 \param[in] size Size of the packet to create in bytes
584 \param[in] senf::noinit This parameter must always have the
585 value \c senf::noinit. */
587 template <class ForwardReadableRange>
588 static ConcretePacket create(
589 ForwardReadableRange const & range,
590 typename boost::disable_if< boost::is_integral<ForwardReadableRange> >::type * = 0);
592 template <class ForwardReadableRange>
593 static ConcretePacket create(ForwardReadableRange const & range);
594 ///< Create packet from given data
595 /**< The packet will be created from a copy of the given
596 data. The data from the range will be copied directly
597 into the packet representation. The data will \e not be
598 validated in any way.
600 href="http://www.boost.org/libs/range/index.html">Boost.Range</a>
601 of data to construct packet from. */
604 // Create packet as new packet after a given packet
606 static ConcretePacket createAfter(Packet packet);
607 ///< Create default initialized packet after \a packet
608 /**< The packet will be initialized to it's default empty
609 state. It will be appended as next header/interpreter
610 after \a packet in that packets interpreter chain.
611 \param[in] packet Packet to append new packet to. */
612 static ConcretePacket createAfter(Packet packet, senf::NoInit_t);
613 ///< Create uninitialized empty packet after\a packet
614 /**< This will create a completely empty and uninitialized
615 packet with <tt>size() == 0</tt>. It will be appended
616 as next header/interpreter after \a packet in that
617 packets interpreter chain.
618 \param[in] packet Packet to append new packet to.
619 \param[in] senf::noinit This parameter must always have the
620 value \c senf::noinit. */
621 static ConcretePacket createAfter(Packet packet, size_type size);
622 ///< Create default initialized packet after \a packet
623 /**< This member will create a default initialized packet
624 with the given size. If the size parameter is smaller
625 than the minimum allowed packet size an exception will
626 be thrown. It will be appended as next
627 header/interpreter after \a packet in that packets
629 \param[in] packet Packet to append new packet to.
630 \param[in] size Size of the packet to create in bytes.
631 \throws TruncatedPacketException if \a size is smaller
632 than the smallest permissible size for this type of
634 static ConcretePacket createAfter(Packet packet, size_type size, senf::NoInit_t);
635 ///< Create uninitialized packet after \a packet
636 /**< Creates an uninitialized (all-zero) packet of the exact
637 given size. It will be appended as next
638 header/interpreter after \a packet in that packets
640 \param[in] packet Packet to append new packet to.
641 \param[in] size Size of the packet to create in bytes
642 \param[in] senf::noinit This parameter must always have the
643 value \c senf::noinit. */
645 template <class ForwardReadableRange>
646 static ConcretePacket createAfter(
648 ForwardReadableRange const & range,
649 typename boost::disable_if< boost::is_integral<ForwardReadableRange> >::type * = 0);
651 template <class ForwardReadableRange>
652 static ConcretePacket createAfter(Packet packet,
653 ForwardReadableRange const & range);
654 ///< Create packet from given data after \a packet
655 /**< The packet will be created from a copy of the given
656 data. The data from the range will be copied directly
657 into the packet representation. The data will \e not be
658 validated in any way. It will be appended as next
659 header/interpreter after \a packet in that packets
661 \param[in] packet Packet to append new packet to.
663 href="http://www.boost.org/libs/range/index.html">Boost.Range</a>
664 of data to construct packet from. */
667 // Create packet as new packet (header) before a given packet
669 static ConcretePacket createBefore(Packet packet);
670 ///< Create default initialized packet before \a packet
671 /**< The packet will be initialized to it's default empty
672 state. It will be prepended as previous
673 header/interpreter before \a packet in that packets
675 \param[in] packet Packet to prepend new packet to. */
676 static ConcretePacket createBefore(Packet packet, senf::NoInit_t);
677 ///< Create uninitialized empty packet before \a packet
678 /**< Creates a completely empty and uninitialized packet. It
679 will be prepended as previous header/interpreter before
680 \a packet in that packets interpreter chain.
681 \param[in] packet Packet to prepend new packet to. */
683 // Create a clone of the current packet
685 ConcretePacket clone() const;
688 ///////////////////////////////////////////////////////////////////////////
692 Parser * operator->() const; ///< Access packet fields
693 /**< This operator allows to access the parsed fields of the
694 packet using the notation <tt>packet->field()</tt>. The
695 fields of the packet are specified by the PacketType's
698 The members are not strictly restricted to simple field
699 access. The parser class may have any member which is
700 needed for full packet access (e.g. checksum validation
702 \see \ref packetparser for the parser interface. */
704 Parser parser() const; ///< Access packet field parser directly
705 /**< Access the parser of the packet. This is the same
706 object returned by the operator->() operator. The
707 operator however does not allow to access this object
708 itself, only it's members.
709 \see \ref packetparser for the parser interface */
714 typedef PacketInterpreter<PacketType> interpreter;
716 ConcretePacket(typename interpreter::ptr packet_);
718 typename interpreter::ptr ptr() const;
721 friend class PacketInterpreter<PacketType>;
728 ///////////////////////////////hh.e////////////////////////////////////////
730 #if !defined(HH_Packets__decls_) && !defined(HH_Packet_i_)
732 #include "Packet.cci"
734 #include "Packet.cti"
741 // c-file-style: "senf"
742 // indent-tabs-mode: nil
743 // ispell-local-dictionary: "american"
744 // compile-command: "scons -u test"
745 // comment-column: 40