// Copyright (C) 2017-2018 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 JSON_FEED_H #define JSON_FEED_H #include #include #include #include #include #include #include namespace isc { namespace config { class JSONFeed; /// @brief Pointer to the @ref JSONFeed. typedef boost::shared_ptr JSONFeedPtr; /// @brief Pointer to the const @ref JSONFeed. typedef boost::shared_ptr ConstJSONFeedPtr; /// @brief A generic exception thrown upon an error in the @ref JSONFeed. class JSONFeedError : public Exception { public: JSONFeedError(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) { }; }; /// @brief State model for asynchronous read of data in JSON format. /// /// Kea control channel uses stream sockets for forwarding commands received /// by the Kea Control Agent to respective Kea services. The responses may /// contain large amounts of data (e.g. lease queries may return thousands /// of leases). Such responses rarely fit into a single data buffer and /// require multiple calls to receive/read or asynchronous receive/read. /// /// A receiver performing multiple reads from a socket must be able to /// locate the boundaries of the command within the data stream. The /// @ref JSONFeed state model solves this problem. /// /// When the partial data is read from the stream socket it should be provided /// to the @ref JSONFeed using @ref JSONFeed::postBuffer and then the /// @ref JSONFeed::poll should be called to start processing the received /// data. The actual JSON structure can be preceded by whitespaces. When first /// occurrence of one of the '{' or '[' characters is found in the stream it is /// considered a beginning of the JSON structure. The model includes an internal /// counter of new '{' and '[' occurrences. The counter increases one of these /// characters is found. When any of the '}' or ']' is found, the counter /// is decreased. When the counter is decreased to 0 it indicates that the /// entire JSON structure has been received and processed. /// /// As '{', '}', '[' and ']' can be embedded in JSON strings two states /// for strings and escape in strings are required. Note the processing /// of escapes is greatly simplified compared to ECMA 404 figure 5. /// Note that this mechanism doesn't check if the JSON structure is well /// formed. It merely detects the end of the JSON structure if this structure /// is well formed. The structure is validated when @ref JSONFeed::toElement /// is called to retrieve the data structures encapsulated with /// @ref isc::data::Element objects. class JSONFeed : public util::StateModel { public: /// @name States supported by the @ref JSONFeed /// //@{ /// @brief State indicating a beginning of a feed. static const int RECEIVE_START_ST = SM_DERIVED_STATE_MIN + 1; /// @brief Skipping whitespaces before actual JSON. static const int WHITESPACE_BEFORE_JSON_ST = SM_DERIVED_STATE_MIN + 2; /// @brief Found first opening brace or square bracket. static const int JSON_START_ST = SM_DERIVED_STATE_MIN + 3; /// @brief Parsing JSON. static const int INNER_JSON_ST = SM_DERIVED_STATE_MIN + 4; /// @brief Parsing JSON string. static const int STRING_JSON_ST = SM_DERIVED_STATE_MIN + 5; /// @brief JSON escape next character. static const int ESCAPE_JSON_ST = SM_DERIVED_STATE_MIN + 6; /// @brief Found last closing brace or square bracket. static const int JSON_END_ST = SM_DERIVED_STATE_MIN + 7; /// @brief Found opening and closing brace or square bracket. /// /// This doesn't however indicate that the JSON is well formed. It /// only means that matching closing brace or square bracket was /// found. static const int FEED_OK_ST = SM_DERIVED_STATE_MIN + 100; /// @brief Invalid syntax detected. /// /// For example, non matching braces or invalid characters found. static const int FEED_FAILED_ST = SM_DERIVED_STATE_MIN + 101; //@} /// @name Events used during data processing. /// //@{ /// @brief Chunk of data successfully read and parsed. static const int DATA_READ_OK_EVT = SM_DERIVED_EVENT_MIN + 1; /// @brief Unable to proceed with parsing until new data is provided. static const int NEED_MORE_DATA_EVT = SM_DERIVED_EVENT_MIN + 2; /// @brief New data provided and parsing should continue. static const int MORE_DATA_PROVIDED_EVT = SM_DERIVED_EVENT_MIN + 3; /// @brief Found opening brace and the matching closing brace. static const int FEED_OK_EVT = SM_DERIVED_EVENT_MIN + 100; /// @brief Invalid syntax detected. static const int FEED_FAILED_EVT = SM_DERIVED_EVENT_MIN + 101; //@} /// @brief Constructor. JSONFeed(); /// @brief Initializes state model. /// /// Initializes events and states. It sets the model to @c RECEIVE_START_ST /// and the next event to @c START_EVT. void initModel(); /// @brief Runs the model as long as data is available. /// /// It processes the input data character by character until it reaches the /// end of the input buffer, in which case it returns. The next event is set /// to @c NEED_MORE_DATA_EVT to indicate the need for providing additional /// data using @ref JSONFeed::postBuffer. This function also returns when /// the end of the JSON structure has been detected or when an error has /// occurred. void poll(); /// @brief Checks if the model needs additional data to continue. /// /// The caller can use this method to check if the model expects additional /// data to be provided to finish processing input data. /// /// @return true if more data is needed, false otherwise. bool needData() const; /// @brief Checks if the data have been successfully processed. bool feedOk() const; /// @brief Returns error string when data processing has failed. std::string getErrorMessage() const { return (error_message_); } /// @brief Returns the text parsed into the buffer. std::string getProcessedText() const { return (output_); } /// @brief Returns processed data as a structure of @ref isc::data::Element /// objects. /// /// @throw JSONFeedError if the received JSON is not well formed. data::ElementPtr toElement() const; /// @brief Receives additional data read from a data stream. /// /// A caller invokes this method to pass additional chunk of data received /// from the stream. /// /// @param buf Pointer to a buffer holding additional input data. /// @param buf_size Size of the data in the input buffer. void postBuffer(const void* buf, const size_t buf_size); private: /// @brief Make @ref runModel private to make sure that the caller uses /// @ref poll method instead. using StateModel::runModel; /// @brief Define events used by the feed. virtual void defineEvents(); /// @brief Verifies events used by the feed. virtual void verifyEvents(); /// @brief Defines states of the feed. virtual void defineStates(); /// @brief Transition to failure state. /// /// This method transitions the model to @ref FEED_FAILED_ST and /// sets next event to FEED_FAILED_EVT. /// /// @param error_msg Error message explaining the failure. void feedFailure(const std::string& error_msg); /// @brief A method called when state model fails. /// /// @param explanation Error message explaining the reason for failure. virtual void onModelFailure(const std::string& explanation); /// @brief Retrieves next byte of data from the buffer. /// /// During normal operation, when there is no more data in the buffer, /// the NEED_MORE_DATA_EVT is set as next event to signal the need for /// calling @ref JSONFeed::postBuffer. /// /// @throw JSONFeedError If current event is already set to /// NEED_MORE_DATA_EVT or MORE_DATA_PROVIDED_EVT. In the former case, it /// indicates that the caller failed to provide new data using /// @ref JSONFeed::postBuffer. The latter case is highly unlikely /// as it indicates that no new data were provided but the state of the /// parser was changed from NEED_MORE_DATA_EVT or the data were provided /// but the data buffer is empty. In both cases, it is a programming /// error. char getNextFromBuffer(); /// @brief This method is called when invalid event occurred in a particular /// state. /// /// This method simply throws @ref JSONFeedError informing about invalid /// event occurring for the particular state. The error message includes /// the name of the handler in which the exception has been thrown. /// It also includes the event which caused the exception. /// /// @param handler_name Name of the handler in which the exception is /// thrown. /// @param event An event which caused the exception. /// /// @throw JSONFeedError. void invalidEventError(const std::string& handler_name, const unsigned int event); /// @brief Tries to read next byte from buffer. /// /// @param [out] next A reference to the variable where read data should be /// stored. /// /// @return true if character was successfully read, false otherwise. bool popNextFromBuffer(char& next); /// @name State handlers. /// //@{ /// @brief Handler for RECEIVE_START_ST. void receiveStartHandler(); /// @brief Handler for WHITESPACE_BEFORE_JSON_ST. void whiteSpaceBeforeJSONHandler(); /// @brief Handler for the FIRST_BRACE_ST. void innerJSONHandler(); /// @brief Handler for the STRING_JSON_ST. void stringJSONHandler(); /// @brief Handler for the ESCAPE_JSON_ST; void escapeJSONHandler(); /// @brief Handler for the JSON_END_ST. void endJSONHandler(); //@} /// @brief Internal buffer from which the feed reads data. std::vector buffer_; /// @brief Holds pointer to the next byte in the buffer to be read. size_t data_ptr_; /// @brief Error message set by @ref onModelFailure. std::string error_message_; /// @brief A counter increased when '{' or '[' is found and decreased when /// '}' or ']' is found in the stream. uint64_t open_scopes_; /// @brief Holds processed data. std::string output_; }; } // end of namespace config } // end of namespace isc #endif // JSON_FEED_H