diff options
Diffstat (limited to '')
-rw-r--r-- | src/lib/dns/message.h | 682 |
1 files changed, 682 insertions, 0 deletions
diff --git a/src/lib/dns/message.h b/src/lib/dns/message.h new file mode 100644 index 0000000..da8acfe --- /dev/null +++ b/src/lib/dns/message.h @@ -0,0 +1,682 @@ +// Copyright (C) 2009-2017 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#ifndef MESSAGE_H +#define MESSAGE_H 1 + +#include <stdint.h> + +#include <iterator> +#include <string> +#include <ostream> + +#include <dns/exceptions.h> + +#include <dns/edns.h> +#include <dns/question.h> +#include <dns/rrset.h> + +namespace isc { +namespace util { +class InputBuffer; +} + +namespace dns { +class TSIGContext; +class TSIGRecord; + +/// +/// \brief A standard DNS module exception that is thrown if a wire format +/// message parser encounters a short length of data that don't even contain +/// the full header section. +/// +class MessageTooShort : public isc::dns::Exception { +public: + MessageTooShort(const char* file, size_t line, const char* what) : + isc::dns::Exception(file, line, what) {} +}; + +/// +/// \brief A standard DNS module exception that is thrown if a section iterator +/// is being constructed for an incompatible section. Specifically, this +/// happens RRset iterator is being constructed for a Question section. +/// +class InvalidMessageSection : public isc::dns::Exception { +public: + InvalidMessageSection(const char* file, size_t line, const char* what) : + isc::dns::Exception(file, line, what) {} +}; + +/// +/// \brief A standard DNS module exception that is thrown if a \c Message +/// class method is called that is prohibited for the current mode of +/// the message. +/// +class InvalidMessageOperation : public isc::dns::Exception { +public: + InvalidMessageOperation(const char* file, size_t line, const char* what) : + isc::dns::Exception(file, line, what) {} +}; + +/// +/// \brief A standard DNS module exception that is thrown if a UDP buffer size +/// smaller than the standard default maximum (DEFAULT_MAX_UDPSIZE) is +/// being specified for the message. +/// +class InvalidMessageUDPSize : public isc::dns::Exception { +public: + InvalidMessageUDPSize(const char* file, size_t line, const char* what) : + isc::dns::Exception(file, line, what) {} +}; + +typedef uint16_t qid_t; + +class AbstractMessageRenderer; +class Message; +class MessageImpl; +class Opcode; +class Rcode; + +template <typename T> +struct SectionIteratorImpl; + +/// \c SectionIterator is a templated class to provide standard-compatible +/// iterators for Questions and RRsets for a given DNS message section. +/// The template parameter is either \c QuestionPtr (for the question section) +/// or \c RRsetPtr (for the answer, authority, or additional section). +template <typename T> +class SectionIterator : public std::iterator<std::input_iterator_tag, T> { +public: + SectionIterator() : impl_(NULL) {} + SectionIterator(const SectionIteratorImpl<T>& impl); + ~SectionIterator(); + SectionIterator(const SectionIterator<T>& source); + void operator=(const SectionIterator<T>& source); + SectionIterator<T>& operator++(); + SectionIterator<T> operator++(int); + const T& operator*() const; + const T* operator->() const; + bool operator==(const SectionIterator<T>& other) const; + bool operator!=(const SectionIterator<T>& other) const; +private: + SectionIteratorImpl<T>* impl_; +}; + +typedef SectionIterator<QuestionPtr> QuestionIterator; +typedef SectionIterator<RRsetPtr> RRsetIterator; + +/// \brief The \c Message class encapsulates a standard DNS message. +/// +/// Details of the design and interfaces of this class are still in flux. +/// Here are some notes about the current design. +/// +/// Since many realistic DNS applications deal with messages, message objects +/// will be frequently used, and can be performance sensitive. To minimize +/// the performance overhead of constructing and destructing the objects, +/// this class is designed to be reusable. The \c clear() method is provided +/// for this purpose. +/// +/// A \c Message class object is in either the \c PARSE or the \c RENDER mode. +/// A \c PARSE mode object is intended to be used to convert wire-format +/// message data into a complete \c Message object. +/// A \c RENDER mode object is intended to be used to convert a \c Message +/// object into wire-format data. +/// Some of the method functions of this class are limited to a specific mode. +/// In general, "set" type operations are only allowed for \c RENDER mode +/// objects. +/// The initial mode must be specified on construction, and can be changed +/// through some method functions. +/// +/// This class uses the "pimpl" idiom, and hides detailed implementation +/// through the \c impl_ pointer. Since a \c Message object is expected to +/// be reused, the construction overhead of this approach should be acceptable. +/// +/// Open issues (among other things): +/// - We may want to provide an "iterator" for all RRsets/RRs for convenience. +/// This will be for applications that do not care about performance much, +/// so the implementation can only be moderately efficient. +/// - We may want to provide a "find" method for a specified type +/// of RR in the message. +class Message { +public: + /// Constants to specify the operation mode of the \c Message. + enum Mode { + PARSE = 0, ///< Parse mode (handling an incoming message) + RENDER = 1 ///< Render mode (building an outgoing message) + }; + + /// \brief Constants for flag bit fields of a DNS message header. + /// + /// Only the defined constants are valid where a header flag is required + /// in this library (e.g., in \c Message::setHeaderFlag()). + /// Since these are enum constants, however, an invalid value could be + /// passed via casting without an error at compilation time. + /// It is generally the callee's responsibility to check and reject invalid + /// values. + /// Of course, applications shouldn't pass invalid values even if the + /// callee does not perform proper validation; the result in such usage + /// is undefined. + /// + /// In the current implementation, the defined values happen to be + /// a 16-bit integer with one bit being set corresponding to the + /// specified flag in the second 16 bits of the DNS Header section + /// in order to make the internal implementation simpler. + /// For example, \c HEADERFLAG_QR is defined to be 0x8000 as the QR + /// bit is the most significant bit of the second 16 bits of the header. + /// However, applications should not assume this coincidence and + /// must solely use the enum representations. + /// Any usage based on the assumption of the underlying values is invalid + /// and the result is undefined. + /// + /// Likewise, bit wise operations such as AND or OR on the flag values + /// are invalid and are not guaranteed to work, even if it could compile + /// with casting. + /// For example, the following code will compile: + /// \code const uint16_t combined_flags = + /// static_cast<uint16_t>(Message::HEADERFLAG_AA) | + /// static_cast<uint16_t>(Message::HEADERFLAG_CD); + /// message->setHeaderFlag(static_cast<Message::HeaderFlag>(combined_flags)); + /// \endcode + /// and (with the current definition) happens to work as if it were + /// validly written as follows: + /// \code message->setHeaderFlag(Message::HEADERFLAG_AA); + /// message->setHeaderFlag(Message::HEADERFLAG_CD); + /// \endcode + /// But the former notation is invalid and may not work in future versions. + /// We did not try to prohibit such usage at compilation time, e.g., by + /// introducing a separately defined class considering the balance + /// between the complexity and advantage, but hopefully the cast notation + /// is sufficiently ugly to prevent proliferation of the usage. + enum HeaderFlag { + HEADERFLAG_QR = 0x8000, ///< Query (if cleared) or response (if set) + HEADERFLAG_AA = 0x0400, ///< Authoritative answer + HEADERFLAG_TC = 0x0200, ///< Truncation + HEADERFLAG_RD = 0x0100, ///< Recursion desired + HEADERFLAG_RA = 0x0080, ///< Recursion available + HEADERFLAG_AD = 0x0020, ///< Authentic %data (RFC4035) + HEADERFLAG_CD = 0x0010 ///< DNSSEC checking disabled (RFC4035) + }; + + /// \brief Constants to specify sections of a DNS message. + /// + /// The sections are those defined in RFC 1035 excluding the Header + /// section; the fields of the Header section are accessed via specific + /// methods of the \c Message class (e.g., \c getQid()). + /// + /// <b>Open Design Issue:</b> + /// In the current implementation the values for the constants are + /// sorted in the order of appearance in DNS messages, i.e., + /// from %Question to Additional. + /// So, for example, + /// code <code>section >= Message::SECTION_AUTHORITY</code> can be + /// used to do something in or after the Authority section. + /// This would be convenient, but it is not clear if it's really a good + /// idea to rely on relationship between the underlying values of enum + /// constants. At the moment, applications are discouraged to rely on + /// this implementation detail. We will see if such usage is sufficiently + /// common to officially support it. + /// + /// Note also that since we don't define \c operator++ for this enum, + /// the following code intending to iterate over all sections will + /// \b not compile: + /// \code for (Section s; s <= SECTION_ADDITIONAL; ++s) { // ++s undefined + /// // do something + /// } \endcode + /// This is intentional at this moment, and we'll see if we need to allow + /// that as we have more experiences with this library. + /// + /// <b>Future Extension:</b> We'll probably also define constants for + /// the section names used in dynamic updates in future versions. + enum Section { + SECTION_QUESTION = 0, ///< %Question section + SECTION_ANSWER = 1, ///< Answer section + SECTION_AUTHORITY = 2, ///< Authority section + SECTION_ADDITIONAL = 3 ///< Additional section + }; + + /// + /// \name Constructors and Destructor + /// + /// Note: The copy constructor and the assignment operator are + /// intentionally defined as private. + /// The intended use case wouldn't require copies of a \c Message object; + /// once created, it would normally be expected to be reused, changing the + /// mode from \c PARSE to \c RENDER, and vice versa. + //@{ +public: + /// \brief The constructor. + /// The mode of the message is specified by the \c mode parameter. + Message(Mode mode); + /// \brief The destructor. + ~Message(); +private: + Message(const Message& source); + Message& operator=(const Message& source); + //@} +public: + /// \brief Return whether the specified header flag bit is set in the + /// header section. + /// + /// This method is basically exception free, but if + /// \c flag is not a valid constant of the \c HeaderFlag type, + /// an exception of class \c InvalidParameter will be thrown. + /// + /// \param flag The header flag constant to test. + /// \return \c true if the specified flag is set; otherwise \c false. + bool getHeaderFlag(const HeaderFlag flag) const; + + /// \brief Set or clear the specified header flag bit in the header + /// section. + /// + /// The optional parameter \c on indicates the operation mode, + /// set or clear; if it's \c true the corresponding flag will be set; + /// otherwise the flag will be cleared. + /// In either case the original state of the flag does not affect the + /// operation; for example, if a flag is already set and the "set" + /// operation is attempted, it effectively results in no operation. + /// + /// The parameter \c on can be omitted, in which case a value of \c true + /// (i.e., set operation) will be assumed. + /// This is based on the observation that the flag would have to be set + /// in the vast majority of the cases where an application needs to + /// use this method. + /// + /// This method is only allowed in the \c RENDER mode; + /// if the \c Message is in other mode, an exception of class + /// InvalidMessageOperation will be thrown. + /// + /// If \c flag is not a valid constant of the \c HeaderFlag type, + /// an exception of class \c InvalidParameter will be thrown. + /// + /// \param flag The header flag constant to set or clear. + /// \param on If \c true the flag will be set; otherwise the flag will be + /// cleared. + void setHeaderFlag(const HeaderFlag flag, const bool on = true); + + /// \brief Return the query ID given in the header section of the message. + qid_t getQid() const; + + /// \brief Set the query ID of the header section of the message. + /// + /// This method is only allowed in the \c RENDER mode; + /// if the \c Message is in other mode, an exception of class + /// InvalidMessageOperation will be thrown. + void setQid(qid_t qid); + + /// \brief Return the Response Code of the message. + /// + /// This includes extended codes specified by an EDNS OPT RR (when + /// included). In the \c PARSE mode, if the received message contains + /// an EDNS OPT RR, the corresponding extended code is identified and + /// returned. + /// + /// The message must have been properly parsed (in the case of the + /// \c PARSE mode) or an \c Rcode has been set (in the case of the + /// \c RENDER mode) beforehand. Otherwise, an exception of class + /// \c InvalidMessageOperation will be thrown. + const Rcode& getRcode() const; + + /// \brief Set the Response Code of the message. + /// + /// This method is only allowed in the \c RENDER mode; + /// if the \c Message is in other mode, an exception of class + /// InvalidMessageOperation will be thrown. + /// + /// If the specified code is an EDNS extended RCODE, an EDNS OPT RR will be + /// included in the message. + void setRcode(const Rcode& rcode); + + /// \brief Return the OPCODE given in the header section of the message. + /// + /// The message must have been properly parsed (in the case of the + /// \c PARSE mode) or an \c Opcode has been set (in the case of the + /// \c RENDER mode) beforehand. Otherwise, an exception of class + /// \c InvalidMessageOperation will be thrown. + const Opcode& getOpcode() const; + + /// \brief Set the OPCODE of the header section of the message. + /// + /// This method is only allowed in the \c RENDER mode; + /// if the \c Message is in other mode, an exception of class + /// InvalidMessageOperation will be thrown. + void setOpcode(const Opcode& opcode); + + /// \brief Return, if any, the EDNS associated with the message. + /// + /// This method never throws an exception. + /// + /// \return A shared pointer to the EDNS. This will be a null shared + /// pointer if the message is not associated with EDNS. + ConstEDNSPtr getEDNS() const; + + /// \brief Set EDNS for the message. + /// + /// This method is only allowed in the \c RENDER mode; + /// if the \c Message is in other mode, an exception of class + /// InvalidMessageOperation will be thrown. + /// + /// \param edns A shared pointer to an \c EDNS object to be set in + /// \c Message. + void setEDNS(ConstEDNSPtr edns); + + /// \brief Return, if any, the TSIG record contained in the received + /// message. + /// + /// Currently, this method is only intended to return a TSIG record + /// for an incoming message built via the \c fromWire() method in the + /// PARSE mode. A call to this method in the RENDER mode is invalid and + /// result in an exception. Also, calling this method is meaningless + /// unless \c fromWire() is performed. + /// + /// The returned pointer is valid only during the lifetime of the + /// \c Message object and until \c clear() is called. The \c Message + /// object retains the ownership of \c TSIGRecord; the caller must not + /// try to delete it. + /// + /// \exception InvalidMessageOperation Message is not in the PARSE mode. + /// + /// \return A pointer to the stored \c TSIGRecord or \c NULL. + const TSIGRecord* getTSIGRecord() const; + + /// \brief Returns the number of RRs contained in the given section. + /// + /// In the \c PARSE mode, the returned value may not be identical to + /// the actual number of RRs of the incoming message that is parsed. + /// The \c Message class handles some "meta" RRs such as EDNS OPT RR + /// separately. This method doesn't include such RRs. + /// Also, a future version of the parser will detect and unify duplicate + /// RRs (which should be rare in practice though), in which case + /// the stored RRs in the \c Message object will be fewer than the RRs + /// originally contained in the incoming message. + /// + /// Likewise, in the \c RENDER mode, even if \c EDNS is set in the + /// \c Message, this method doesn't count the corresponding OPT RR + /// in the Additional section. + /// + /// This method is basically exception free, but if + /// \c section is not a valid constant of the \c Section type, + /// an exception of class \c OutOfRange will be thrown. + /// + /// \param section The section in the message where RRs should be + /// counted. + /// \return The number of RRs stored in the specified section of the + /// message. + unsigned int getRRCount(const Section section) const; + + /// \brief Return an iterator corresponding to the beginning of the + /// Question section of the message. + const QuestionIterator beginQuestion() const; + + /// \brief Return an iterator corresponding to the end of the + /// Question section of the message. + const QuestionIterator endQuestion() const; + + /// \brief Return an iterator corresponding to the beginning of the + /// given section (other than Question) of the message. + /// + /// \c section must be a valid constant of the \c Section type; + /// otherwise, an exception of class \c OutOfRange will be thrown. + const RRsetIterator beginSection(const Section section) const; + + /// \brief Return an iterator corresponding to the end of the + /// given section (other than Question) of the message. + /// + /// \c section must be a valid constant of the \c Section type; + /// otherwise, an exception of class \c OutOfRange will be thrown. + const RRsetIterator endSection(const Section section) const; + + /// \brief Add a (pointer like object of) Question to the message. + /// + /// This method is only allowed in the \c RENDER mode; + /// if the \c Message is in other mode, an exception of class + /// InvalidMessageOperation will be thrown. + void addQuestion(QuestionPtr question); + + /// \brief Add a (pointer like object of) Question to the message. + /// + /// This version internally creates a \c QuestionPtr object from the + /// given \c question and calls the other version of this method. + /// So this is inherently less efficient, but is provided because this + /// form may be more intuitive and may make more sense for performance + /// insensitive applications. + /// + /// This method is only allowed in the \c RENDER mode; + /// if the \c Message is in other mode, an exception of class + /// InvalidMessageOperation will be thrown. + void addQuestion(const Question& question); + + /// \brief Add a (pointer like object of) RRset to the given section + /// of the message. + /// + /// Note that \c addRRset() does not currently check for duplicate + /// data before inserting RRsets. The caller is responsible for + /// checking for these (see \c hasRRset() below). + /// + /// \throw InvalidParameter rrset is NULL + /// \throw InvalidMessageOperation The message is not in the \c RENDER + /// mode. + /// \throw OutOfRange \c section doesn't specify a valid \c Section value. + /// + /// \param section The message section to which the rrset is to be added + /// \param rrset The rrset to be added. Must not be NULL. + void addRRset(const Section section, RRsetPtr rrset); + + /// \brief Determine whether the given section already has an RRset + /// matching the given name, RR class and RR type. + /// + /// \c section must be a valid constant of the \c Section type; + /// otherwise, an exception of class \c OutOfRange will be thrown. + /// + /// This should probably be extended to be a "find" method that returns + /// a matching RRset if found. + bool hasRRset(const Section section, const Name& name, + const RRClass& rrclass, const RRType& rrtype) const; + + /// \brief Determine whether the given section already has an RRset + /// matching the one pointed to by the argument + /// + /// \c section must be a valid constant of the \c Section type; + /// otherwise, an exception of class \c OutOfRange will be thrown. + bool hasRRset(const Section section, const RRsetPtr& rrset) const; + + /// \brief Remove RRSet from Message + /// + /// Removes the RRset identified by the section iterator from the message. + /// Note: if,.for some reason, the RRset is duplicated in the section, only + /// one occurrence is removed. + /// + /// If the operation is successful, all iterators into the section are + /// invalidated. + /// + /// \param section Section to which the iterator belongs + /// \param iterator Iterator pointing to the element to be removed + /// + /// \return true if the element was removed, false if the iterator was not + /// found in the specified section. + bool removeRRset(const Section section, RRsetIterator& iterator); + + /// \brief Remove all RRSets from the given Section + /// + /// This method is only allowed in the \c RENDER mode, and the given + /// section must be valid. + /// + /// \throw InvalidMessageOperation Message is not in the \c RENDER mode + /// \throw OutOfRange The specified section is not valid + /// + /// \param section Section to remove all rrsets from + void clearSection(const Section section); + + // The following methods are not currently implemented. + //void removeQuestion(QuestionPtr question); + // notyet: + //void addRR(const Section section, const RR& rr); + //void removeRR(const Section section, const RR& rr); + + /// \brief Clear the message content (if any) and reinitialize it in the + /// specified mode. + void clear(Mode mode); + + /// \brief Adds all rrsets from the source the given section in the + /// source message to the same section of this message + /// + /// \param section the section to append + /// \param source The source Message + void appendSection(const Section section, const Message& source); + + /// \brief Prepare for making a response from a request. + /// + /// This will clear the DNS header except those fields that should be kept + /// for the response, and clear answer and the following sections. + /// See also dns_message_reply() of BIND9. + void makeResponse(); + + /// \brief Convert the Message to a string. + /// + /// At least \c Opcode and \c Rcode must be validly set in the \c Message + /// (as a result of parse in the \c PARSE mode or by explicitly setting + /// in the \c RENDER mode); otherwise, an exception of + /// class \c InvalidMessageOperation will be thrown. + std::string toText() const; + + /// \brief Render the message in wire formant into a message renderer + /// object with (or without) TSIG. + /// + /// This \c Message must be in the \c RENDER mode and both \c Opcode and + /// \c Rcode must have been set beforehand; otherwise, an exception of + /// class \c InvalidMessageOperation will be thrown. + /// + /// If a non-NULL \c tsig_ctx is passed, it will also add a TSIG RR + /// with (in many cases) the TSIG MAC for the message along with the + /// given TSIG context (\c tsig_ctx). The TSIG RR will be placed at + /// the end of \c renderer. The \c TSIGContext at \c tsig_ctx will + /// be updated based on the fact it was used for signing and with + /// the latest MAC. + /// + /// \exception InvalidMessageOperation The message is not in the Render + /// mode, or either Rcode or Opcode is not set. + /// \exception InvalidParameter The allowable limit of \c renderer is too + /// small for a TSIG or the Header section. Note that this shouldn't + /// happen with parameters as defined in the standard protocols, + /// so it's more likely a program bug. + /// \exception Unexpected Rendering the TSIG RR fails. The implementation + /// internally makes sure this doesn't happen, so if that ever occurs + /// it should mean a bug either in the TSIG context or in the renderer + /// implementation. + /// + /// \note The renderer's internal buffers and data are automatically + /// cleared, keeping the length limit and the compression mode intact. + /// In case truncation is triggered, the renderer is cleared completely. + /// + /// \param renderer DNS message rendering context that encapsulates the + /// output buffer and name compression information. + /// \param tsig_ctx A TSIG context that is to be used for signing the + /// message + void toWire(AbstractMessageRenderer& renderer, + TSIGContext* tsig_ctx = NULL); + + /// Parse options. + /// + /// describe PRESERVE_ORDER: note doesn't affect EDNS or TSIG. + /// + /// The option values are used as a parameter for \c fromWire(). + /// These are values of a bitmask type. Bitwise operations can be + /// performed on these values to express compound options. + enum ParseOptions { + PARSE_DEFAULT = 0, ///< The default options + PRESERVE_ORDER = 1 ///< Preserve RR order and don't combine them + }; + + /// \brief Parse the header section of the \c Message. + /// + /// NOTE: If the header has already been parsed by a previous call + /// to this method, this method simply returns (i.e., it does not + /// read from the \c buffer). + void parseHeader(isc::util::InputBuffer& buffer); + + /// \brief (Re)build a \c Message object from wire-format data. + /// + /// This method parses the given wire format data to build a + /// complete Message object. On success, the values of the header section + /// fields can be accessible via corresponding get methods, and the + /// question and following sections can be accessible via the + /// corresponding iterators. If the message contains an EDNS or TSIG, + /// they can be accessible via \c getEDNS() and \c getTSIGRecord(), + /// respectively. + /// + /// This \c Message must be in the \c PARSE mode. + /// + /// This method performs strict validation on the given message based + /// on the DNS protocol specifications. If the given message data is + /// invalid, this method throws an exception (see the exception list). + /// + /// By default, this method combines RRs of the same name, RR type and + /// RR class in a section into a single RRset, even if they are interleaved + /// with a different type of RR (though it would be a rare case in + /// practice). If the \c PRESERVE_ORDER option is specified, it handles + /// each RR separately, in the appearing order, and converts it to a + /// separate RRset (so this RRset should contain exactly one Rdata). + /// This mode will be necessary when the higher level protocol is + /// ordering conscious. For example, in AXFR and IXFR, the position of + /// the SOA RRs are crucial. + /// + /// \exception InvalidMessageOperation \c Message is in the RENDER mode + /// \exception DNSMessageFORMERR The given message data is syntactically + /// \exception MessageTooShort The given data is shorter than a valid + /// header section + /// \exception std::bad_alloc Memory allocation failure + /// \exception Others \c Name, \c Rdata, and \c EDNS classes can also throw + /// + /// \param buffer A input buffer object that stores the wire + /// data. This method reads from position 0 in the passed buffer. + /// \param options Parse options + void fromWire(isc::util::InputBuffer& buffer, ParseOptions options + = PARSE_DEFAULT); + + /// + /// \name Protocol constants + /// + //@{ + /// \brief The default maximum size of UDP DNS messages that don't cause + /// truncation. + /// + /// With EDNS the maximum size can be increased per message. + static const uint16_t DEFAULT_MAX_UDPSIZE = 512; + + /// \brief The default maximum size of UDP DNS messages we can handle + static const uint16_t DEFAULT_MAX_EDNS0_UDPSIZE = 4096; + //@} + +private: + MessageImpl* impl_; +}; + +/// \brief Pointer-like type pointing to a \c Message +/// +/// This type is expected to be used as an argument in asynchronous +/// callback functions. The internal reference-counting will ensure that +/// that ongoing state information will not be lost if the object +/// that originated the asynchronous call falls out of scope. +typedef boost::shared_ptr<Message> MessagePtr; +typedef boost::shared_ptr<const Message> ConstMessagePtr; + +/// Insert the \c Message as a string into stream. +/// +/// This method convert \c message into a string and inserts it into the +/// output stream \c os. +/// +/// \param os A \c std::ostream object on which the insertion operation is +/// performed. +/// \param message A \c Message object output by the operation. +/// \return A reference to the same \c std::ostream object referenced by +/// parameter \c os after the insertion operation. +std::ostream& operator<<(std::ostream& os, const Message& message); +} +} +#endif // MESSAGE_H + +// Local Variables: +// mode: c++ +// End: |