summaryrefslogtreecommitdiffstats
path: root/src/lib/eval/token.h
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/lib/eval/token.h1300
1 files changed, 1300 insertions, 0 deletions
diff --git a/src/lib/eval/token.h b/src/lib/eval/token.h
new file mode 100644
index 0000000..83833af
--- /dev/null
+++ b/src/lib/eval/token.h
@@ -0,0 +1,1300 @@
+// Copyright (C) 2015-2023 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 TOKEN_H
+#define TOKEN_H
+
+#include <exceptions/exceptions.h>
+#include <dhcp/pkt.h>
+#include <stack>
+
+namespace isc {
+namespace dhcp {
+
+class Token;
+
+/// @brief Pointer to a single Token
+typedef boost::shared_ptr<Token> TokenPtr;
+
+/// This is a structure that holds an expression converted to RPN
+///
+/// For example expression: option[123].text == 'foo' will be converted to:
+/// [0] = option[123].text (TokenOption object)
+/// [1] = 'foo' (TokenString object)
+/// [2] = == operator (TokenEqual object)
+typedef std::vector<TokenPtr> Expression;
+
+typedef boost::shared_ptr<Expression> ExpressionPtr;
+
+/// Evaluated values are stored as a stack of strings
+typedef std::stack<std::string> ValueStack;
+
+/// @brief EvalBadStack is thrown when more or less parameters are on the
+/// stack than expected.
+class EvalBadStack : public Exception {
+public:
+ EvalBadStack(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief EvalTypeError is thrown when a value on the stack has a content
+/// with an unexpected type.
+class EvalTypeError : public Exception {
+public:
+ EvalTypeError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Base class for all tokens
+///
+/// It provides an interface for all tokens and storage for string representation
+/// (all tokens evaluate to string).
+///
+/// This class represents a single token. Examples of a token are:
+/// - "foo" (a constant string)
+/// - option[123].text (a token that extracts textual value of option 123)
+/// - == (an operator that compares two other tokens)
+/// - substring(a,b,c) (an operator that takes three arguments: a string,
+/// first character and length)
+class Token {
+public:
+
+ /// @brief This is a generic method for evaluating a packet.
+ ///
+ /// We need to pass the packet being evaluated and possibly previously
+ /// evaluated values. Specific implementations may ignore the packet altogether
+ /// and just put their own value on the stack (constant tokens), look at the
+ /// packet and put some data extracted from it on the stack (option tokens),
+ /// or pop arguments from the stack and put back the result (operators).
+ ///
+ /// The parameters passed will be:
+ ///
+ /// @param pkt - packet being classified
+ /// @param values - stack of values with previously evaluated tokens
+ virtual void evaluate(Pkt& pkt, ValueStack& values) = 0;
+
+ /// @brief Virtual destructor
+ virtual ~Token() {}
+
+ /// @brief Coverts a (string) value to a boolean
+ ///
+ /// Only "true" and "false" are expected.
+ ///
+ /// @param value the (string) value
+ /// @return the boolean represented by the value
+ /// @throw EvalTypeError when the value is not either "true" or "false".
+ static inline bool toBool(std::string value) {
+ if (value == "true") {
+ return (true);
+ } else if (value == "false") {
+ return (false);
+ } else {
+ isc_throw(EvalTypeError, "Incorrect boolean. Expected exactly "
+ "\"false\" or \"true\", got \"" << value << "\"");
+ }
+ }
+};
+
+/// The order where Token subtypes are declared should be:
+/// - literal terminals
+/// - option & co
+/// - pkt field & co
+/// - ==
+/// - substring & co
+/// - not, and, or
+
+/// @brief Token representing a constant string
+///
+/// This token holds value of a constant string, e.g. it represents
+/// "MSFT" in expression option[vendor-class].text == "MSFT"
+class TokenString : public Token {
+public:
+ /// Value is set during token construction.
+ ///
+ /// @param str constant string to be represented.
+ TokenString(const std::string& str)
+ : value_(str){
+ }
+
+ /// @brief Token evaluation (puts value of the constant string on the stack)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented string will be pushed here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+protected:
+ std::string value_; ///< Constant value
+};
+
+/// @brief Token representing a constant string in hexadecimal format
+///
+/// This token holds value of a constant string giving in an hexadecimal
+/// format, for instance 0x666f6f is "foo"
+class TokenHexString : public Token {
+public:
+ /// Value is set during token construction.
+ ///
+ /// @param str constant string to be represented
+ /// (must be "0x" or "0X" followed by a string of hexadecimal digits
+ /// or decoding will fail)
+ TokenHexString(const std::string& str);
+
+ /// @brief Token evaluation (puts value of the constant string on
+ /// the stack after decoding or an empty string if decoding fails
+ /// (note it should not if the parser is correct)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented string will be pushed here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+protected:
+ std::string value_; ///< Constant value
+};
+
+/// @brief Token representing an unsigned 32 bit integer
+///
+/// For performance reasons, the constant integer value is converted to a string
+/// just once (in the constructor). Afterwards, this effectively works as a constant
+/// 4 byte long string. Hence this class is derived from TokenString and
+/// does not even need its own evaluate() method.
+class TokenInteger : public TokenString {
+public:
+ /// @brief Integer value set during construction.
+ ///
+ /// The value is converted to string and stored in value_ provided by the
+ /// base class.
+ ///
+ /// @param value integer value to be stored.
+ TokenInteger(const uint32_t value);
+
+ /// @brief Returns integer value
+ ///
+ /// Used in tests only.
+ ///
+ /// @return integer value
+ uint32_t getInteger() const {
+ return (int_value_);
+ }
+
+protected:
+ uint32_t int_value_; ///< value as integer (stored for testing only)
+};
+
+/// @brief Token representing an IP address as a constant string
+///
+/// This token holds the value of an IP address as a constant string,
+/// for instance 10.0.0.1 is 0x10000001
+class TokenIpAddress : public Token {
+public:
+ /// Value is set during token construction.
+ ///
+ /// @param addr IP address to be represented as a constant string
+ TokenIpAddress(const std::string& addr);
+
+ /// @brief Token evaluation (puts value of the constant string on
+ /// the stack after decoding)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented IP address will be pushed here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+protected:
+ ///< Constant value (empty string if the IP address cannot be converted)
+ std::string value_;
+};
+
+/// @brief Token representing an IP address as a string
+///
+/// This token holds the value of an IP address as a string, for instance
+/// 10.0.0.1 is '10.0.0.1'
+class TokenIpAddressToText : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenIpAddressToText() {}
+
+ /// @brief Token evaluation (puts value of the string on the stack after
+ /// decoding)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented IP address as a string will be pushed here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token representing an 8 bit integer as a string
+///
+/// This token holds the value of an 8 bit integer as a string, for instance
+/// 0xff is '-1'
+class TokenInt8ToText : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenInt8ToText() {}
+
+ /// @brief Token evaluation (puts value of the string on the stack after
+ /// decoding)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented 8 bit integer as a string will be pushed
+ /// here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token representing a 16 bit integer as a string
+///
+/// This token holds the value of a 16 bit integer as a string, for instance
+/// 0xffff is '-1'
+class TokenInt16ToText : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenInt16ToText() {}
+
+ /// @brief Token evaluation (puts value of the string on the stack after
+ /// decoding)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented 16 bit integer as a string will be pushed
+ /// here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token representing a 32 bit integer as a string
+///
+/// This token holds the value of a 32 bit integer as a string, for instance
+/// 0xffffffff is '-1'
+class TokenInt32ToText : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenInt32ToText() {}
+
+ /// @brief Token evaluation (puts value of the string on the stack after
+ /// decoding)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented 32 bit integer as a string will be pushed
+ /// here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token representing an 8 bit unsigned integer as a string
+///
+/// This token holds the value of an 8 bit unsigned integer as a string, for
+/// instance 0xff is '255'
+class TokenUInt8ToText : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenUInt8ToText() {}
+
+ /// @brief Token evaluation (puts value of the string on the stack after
+ /// decoding)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented 8 bit unsigned integer as a string will be
+ /// pushed here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token representing a 16 bit unsigned integer as a string
+///
+/// This token holds the value of a 16 bit unsigned integer as a string, for
+/// instance 0xffff is '65535'
+class TokenUInt16ToText : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenUInt16ToText() {}
+
+ /// @brief Token evaluation (puts value of the string on the stack after
+ /// decoding)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented 16 bit unsigned integer as a string will be
+ /// pushed here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token representing a 32 bit unsigned integer as a string
+///
+/// This token holds the value of a 32 bit unsigned integer as a string, for
+/// instance 0xffffffff is '4294967295'
+class TokenUInt32ToText : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenUInt32ToText() {}
+
+ /// @brief Token evaluation (puts value of the string on the stack after
+ /// decoding)
+ ///
+ /// @param pkt (ignored)
+ /// @param values (represented 32 bit unsigned integer as a string will be
+ /// pushed here)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents a value of an option
+///
+/// This represents a reference to a given option, e.g. in the expression
+/// option[vendor-class].text == "MSFT", it represents
+/// option[vendor-class].text
+///
+/// During the evaluation it tries to extract the value of the specified
+/// option. If the option is not found, an empty string ("") is returned
+/// (or "false" when the representation is EXISTS).
+class TokenOption : public Token {
+public:
+
+ /// @brief Token representation type.
+ ///
+ /// There are many possible ways in which option can be presented.
+ /// Currently the textual, hexadecimal and exists representations are
+ /// supported. The type of representation is specified in the
+ /// constructor and it affects the value generated by the
+ /// @c TokenOption::evaluate function.
+ enum RepresentationType {
+ TEXTUAL,
+ HEXADECIMAL,
+ EXISTS
+ };
+
+ /// @brief Constructor that takes an option code as a parameter
+ ///
+ /// Note: There is no constructor that takes option_name, as it would
+ /// introduce complex dependency of the libkea-eval on libdhcpsrv.
+ ///
+ /// @param option_code code of the option to be represented.
+ /// @param rep_type Token representation type.
+ TokenOption(const uint16_t option_code, const RepresentationType& rep_type)
+ : option_code_(option_code), representation_type_(rep_type) {}
+
+ /// @brief Evaluates the values of the option
+ ///
+ /// This token represents a value of the option, so this method attempts
+ /// to extract the option from the packet and put its value on the stack.
+ /// If the option is not there, an empty string ("") is put on the stack.
+ ///
+ /// @param pkt specified option will be extracted from this packet (if present)
+ /// @param values value of the option will be pushed here (or "")
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+ /// @brief Returns option-code
+ ///
+ /// This method is used in testing to determine if the parser had
+ /// instantiated TokenOption with correct parameters.
+ ///
+ /// @return option-code of the option this token expects to extract.
+ uint16_t getCode() const {
+ return (option_code_);
+ }
+
+ /// @brief Returns representation-type
+ ///
+ /// This method is used in testing to determine if the parser had
+ /// instantiated TokenOption with correct parameters.
+ ///
+ /// @return representation-type of the option this token expects to use.
+ RepresentationType getRepresentation() const {
+ return (representation_type_);
+ }
+
+protected:
+ /// @brief Attempts to retrieve an option
+ ///
+ /// For this class it simply attempts to retrieve the option from the packet,
+ /// but there may be derived classes that would attempt to extract it from
+ /// other places (e.g. relay option, or as a suboption of other specific option).
+ ///
+ ///
+ /// @param pkt the option will be retrieved from here
+ /// @return option instance (or NULL if not found)
+ virtual OptionPtr getOption(Pkt& pkt);
+
+ /// @brief Auxiliary method that puts string representing a failure
+ ///
+ /// Depending on the representation type, this is either "" or "false".
+ ///
+ /// @param values a string representing failure will be pushed here.
+ /// @return value pushed
+ virtual std::string pushFailure(ValueStack& values);
+
+ uint16_t option_code_; ///< Code of the option to be extracted
+ RepresentationType representation_type_; ///< Representation type.
+};
+
+/// @brief Represents a sub-option inserted by the DHCPv4 relay.
+///
+/// DHCPv4 relays insert sub-options in option 82. This token attempts to extract
+/// such sub-options. Note in DHCPv6 it is radically different (possibly
+/// many encapsulation levels), thus there are separate classes for v4 and v6.
+///
+/// This token can represent the following expressions:
+/// relay[13].text - Textual representation of sub-option 13 in RAI (option 82)
+/// relay[13].hex - Binary representation of sub-option 13 in RAI (option 82)
+/// relay[vendor-class].text - Text representation of sub-option X in RAI (option 82)
+/// relay[vendor-class].hex - Binary representation of sub-option X in RAI (option 82)
+class TokenRelay4Option : public TokenOption {
+public:
+
+ /// @brief Constructor for extracting sub-option from RAI (option 82)
+ ///
+ /// @param option_code code of the requested sub-option
+ /// @param rep_type code representation (currently .hex and .text are supported)
+ TokenRelay4Option(const uint16_t option_code,
+ const RepresentationType& rep_type);
+
+protected:
+ /// @brief Attempts to obtain specified sub-option of option 82 from the packet
+ /// @param pkt DHCPv4 packet (that hopefully contains option 82)
+ /// @return found sub-option from option 82
+ virtual OptionPtr getOption(Pkt& pkt);
+};
+
+/// @brief Token that represents a value of an option within a DHCPv6 relay
+/// encapsulation
+///
+/// This represents a reference to a given option similar to TokenOption
+/// but from within the information from a relay. In the expression
+/// relay6[nest-level].option[option-code], nest-level indicates which
+/// of the relays to examine and option-code which option to extract.
+///
+/// During the evaluation it tries to extract the value of the specified
+/// option from the requested relay block. If the relay block doesn't
+/// exist or the option is not found an empty string ("") is returned
+/// (or "false" when the representation is EXISTS).
+///
+/// The nesting level can go from 0 (closest to the server) to 31,
+/// or from -1 (closest to the client) to -32
+class TokenRelay6Option : public TokenOption {
+public:
+ /// @brief Constructor that takes a nesting level and an option
+ /// code as parameters.
+ ///
+ /// @param nest_level the nesting for which relay to examine.
+ /// @param option_code code of the option.
+ /// @param rep_type Token representation type.
+ TokenRelay6Option(const int8_t nest_level, const uint16_t option_code,
+ const RepresentationType& rep_type)
+ : TokenOption(option_code, rep_type), nest_level_(nest_level) {}
+
+ /// @brief Returns nest-level
+ ///
+ /// This method is used in testing to determine if the parser has
+ /// instantiated TokenRelay6Option with correct parameters.
+ ///
+ /// @return nest-level of the relay block this token expects to use
+ /// for extraction.
+ int8_t getNest() const {
+ return (nest_level_);
+ }
+
+protected:
+ /// @brief Attempts to obtain specified option from the specified relay block
+ /// @param pkt DHCPv6 packet that hopefully contains the proper relay block
+ /// @return option instance if available
+ virtual OptionPtr getOption(Pkt& pkt);
+
+ int8_t nest_level_; ///< nesting level of the relay block to use
+};
+
+/// @brief Token that represents meta data of a DHCP packet.
+///
+/// For example in the expression pkt.iface == 'eth0'
+/// this token represents the pkt.iface expression.
+///
+/// Currently supported meta datas are:
+/// - iface (incoming/outgoinginterface name)
+/// - src (source IP address, 4 or 16 octets)
+/// - dst (destination IP address, 4 or 16 octets)
+/// - len (length field in the UDP header, padded to 4 octets)
+class TokenPkt : public Token {
+public:
+
+ /// @brief enum value that determines the field.
+ enum MetadataType {
+ IFACE, ///< interface name (string)
+ SRC, ///< source (IP address)
+ DST, ///< destination (IP address)
+ LEN ///< length (4 octets)
+ };
+
+ /// @brief Constructor (does nothing)
+ TokenPkt(const MetadataType type)
+ : type_(type) {}
+
+ /// @brief Gets a value from the specified packet.
+ ///
+ /// Evaluation uses metadata available in the packet. It does not
+ /// require any values to be present on the stack.
+ ///
+ /// @param pkt - metadata will be extracted from here
+ /// @param values - stack of values (1 result will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+ /// @brief Returns metadata type
+ ///
+ /// This method is used only in tests.
+ /// @return type of the metadata.
+ MetadataType getType() {
+ return (type_);
+ }
+
+private:
+ /// @brief Specifies metadata of the DHCP packet
+ MetadataType type_;
+};
+
+/// @brief Token that represents fields of a DHCPv4 packet.
+///
+/// For example in the expression pkt4.chaddr == 0x0102030405
+/// this token represents the pkt4.chaddr expression.
+///
+/// Currently supported fields are:
+/// - chaddr (client hardware address, hlen [0..16] octets)
+/// - giaddr (relay agent IP address, 4 octets)
+/// - ciaddr (client IP address, 4 octets)
+/// - yiaddr ('your' (client) IP address, 4 octets)
+/// - siaddr (next server IP address, 4 octets)
+/// - hlen (hardware address length, padded to 4 octets)
+/// - htype (hardware address type, padded to 4 octets)
+class TokenPkt4 : public Token {
+public:
+
+ /// @brief enum value that determines the field.
+ enum FieldType {
+ CHADDR, ///< chaddr field (up to 16 bytes link-layer address)
+ GIADDR, ///< giaddr (IPv4 address)
+ CIADDR, ///< ciaddr (IPv4 address)
+ YIADDR, ///< yiaddr (IPv4 address)
+ SIADDR, ///< siaddr (IPv4 address)
+ HLEN, ///< hlen (hardware address length)
+ HTYPE, ///< htype (hardware address type)
+ MSGTYPE, ///< message type (not really a field, content of option 53)
+ TRANSID, ///< transaction-id (xid)
+ };
+
+ /// @brief Constructor (does nothing)
+ TokenPkt4(const FieldType type)
+ : type_(type) {}
+
+ /// @brief Gets a value from the specified packet.
+ ///
+ /// Evaluation uses fields available in the packet. It does not require
+ /// any values to be present on the stack.
+ ///
+ /// @throw EvalTypeError when called for DHCPv6 packet
+ ///
+ /// @param pkt - fields will be extracted from here
+ /// @param values - stack of values (1 result will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+ /// @brief Returns field type
+ ///
+ /// This method is used only in tests.
+ /// @return type of the field.
+ FieldType getType() {
+ return (type_);
+ }
+
+private:
+ /// @brief Specifies field of the DHCPv4 packet
+ FieldType type_;
+};
+
+/// @brief Token that represents fields of DHCPv6 packet.
+///
+/// For example in the expression pkt6.msgtype == 1
+/// this token represents the message type of the DHCPv6 packet.
+/// The integer values are placed on the value stack as 4 byte
+/// strings.
+///
+/// Currently supported fields are:
+/// - msgtype
+/// - transid
+class TokenPkt6 : public Token {
+public:
+ /// @brief enum value that determines the field.
+ enum FieldType {
+ MSGTYPE, ///< msg type
+ TRANSID ///< transaction id (integer but manipulated as a string)
+ };
+
+ /// @brief Constructor (does nothing)
+ TokenPkt6(const FieldType type)
+ : type_(type) {}
+
+ /// @brief Gets a value of the specified packet.
+ ///
+ /// The evaluation uses fields that are available in the packet. It does not
+ /// require any values to be present on the stack.
+ ///
+ /// @throw EvalTypeError when called for a DHCPv4 packet
+ ///
+ /// @param pkt - packet from which to extract the fields
+ /// @param values - stack of values, 1 result will be pushed
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+ /// @brief Returns field type
+ ///
+ /// This method is used only in tests.
+ /// @return type of the field.
+ FieldType getType() {
+ return (type_);
+ }
+
+private:
+ /// @brief Specifies field of the DHCPv6 packet to get
+ FieldType type_;
+};
+
+/// @brief Token that represents a value of a field within a DHCPv6 relay
+/// encapsulation
+///
+/// This represents a reference to a field with a given DHCPv6 relay encapsulation.
+/// In the expression relay6[nest-level].field-name, nest-level indicates which of
+/// the relays to examine and field-name which of the fields to extract.
+///
+/// During the evaluation it tries to extract the value of the specified
+/// field from the requested relay block. If the relay block doesn't exist
+/// an empty string ("") is returned. If the relay block does exist the field
+/// is always returned as a 16 byte IPv6 address. As the relay may not have
+/// set the field it may be 0s.
+///
+/// The nesting level can go from 0 (closest to the server) to 31,
+/// or from -1 (closest to the client) to -32
+class TokenRelay6Field : public Token {
+public:
+
+ /// @brief enum value that determines the field.
+ enum FieldType {
+ PEERADDR, ///< Peer address field (IPv6 address)
+ LINKADDR ///< Link address field (IPv6 address)
+ };
+
+ /// @brief Constructor that takes a nesting level and field type
+ /// as parameters.
+ ///
+ /// @param nest_level the nesting level for which relay to examine.
+ /// @param type which field to extract.
+ TokenRelay6Field(const int8_t nest_level, const FieldType type)
+ : nest_level_(nest_level), type_(type) {}
+
+ /// @brief Extracts the specified field from the requested relay
+ ///
+ /// Evaluation uses fields available in the packet. It does not require
+ /// any values to be present on the stack.
+ ///
+ /// @param pkt fields will be extracted from here
+ /// @param values - stack of values (1 result will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+ /// @brief Returns nest-level
+ ///
+ /// This method is used in testing to determine if the parser has
+ /// instantiated TokenRelay6Field with correct parameters.
+ ///
+ /// @return nest-level of the relay block this token expects to use
+ /// for extraction.
+ int8_t getNest() const {
+ return (nest_level_);
+ }
+
+ /// @brief Returns field type
+ ///
+ /// This method is used only in testing to determine if the parser has
+ /// instantiated TokenRelay6Field with correct parameters.
+ ///
+ /// @return type of the field.
+ FieldType getType() {
+ return (type_);
+ }
+
+protected:
+ /// @brief Specifies field of the DHCPv6 relay option to get
+ int8_t nest_level_; ///< nesting level of the relay block to use
+ FieldType type_; ///< field to get
+};
+
+/// @brief Token that represents equality operator (compares two other tokens)
+///
+/// For example in the expression option[vendor-class].text == "MSFT"
+/// this token represents the equal (==) sign.
+class TokenEqual : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenEqual() {}
+
+ /// @brief Compare two values.
+ ///
+ /// Evaluation does not use packet information, but rather consumes the last
+ /// two parameters. It does a simple string comparison and sets the value to
+ /// either "true" or "false". It requires at least two parameters to be
+ /// present on stack.
+ ///
+ /// @throw EvalBadStack if there are less than 2 values on stack
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (2 arguments will be popped, 1 result
+ /// will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents the substring operator (returns a portion
+/// of the supplied string)
+///
+/// This token represents substring(str, start, len) An operator that takes three
+/// arguments: a string, the first character and the length.
+class TokenSubstring : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenSubstring() {}
+
+ /// @brief Extract a substring from a string
+ ///
+ /// Evaluation does not use packet information. It requires at least
+ /// three values to be present on the stack. It will consume the top
+ /// three values on the stack as parameters and push the resulting substring
+ /// onto the stack. From the top it expects the values on the stack as:
+ /// - len
+ /// - start
+ /// - str
+ ///
+ /// str is the string to extract a substring from. If it is empty, an empty
+ /// string is pushed onto the value stack.
+ ///
+ /// start is the position from which the code starts extracting the substring.
+ /// 0 is the first character and a negative number starts from the end, with
+ /// -1 being the last character. If the starting point is outside of the
+ /// original string an empty string is pushed onto the value stack.
+ ///
+ /// length is the number of characters from the string to extract.
+ /// "all" means all remaining characters from start to the end of string.
+ /// A negative number means to go from start towards the beginning of
+ /// the string, but doesn't include start.
+ /// If length is longer than the remaining portion of string
+ /// then the entire remaining portion is placed on the value stack.
+ ///
+ /// The following examples all use the base string "foobar", the first number
+ /// is the starting position and the second is the length. Note that
+ /// a negative length only selects which characters to extract it does not
+ /// indicate an attempt to reverse the string.
+ /// - 0, all => "foobar"
+ /// - 0, 6 => "foobar"
+ /// - 0, 4 => "foob"
+ /// - 2, all => "obar"
+ /// - 2, 6 => "obar"
+ /// - -1, all => "r"
+ /// - -1, -4 => "ooba"
+ ///
+ /// @throw EvalBadStack if there are less than 3 values on stack
+ /// @throw EvalTypeError if start is not a number or length a number or
+ /// the special value "all".
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (3 arguments will be popped, 1 result
+ /// will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+class TokenSplit : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenSplit() {}
+
+ /// @brief Extract a field from a delimited string
+ ///
+ /// Evaluation does not use packet information. It requires at least
+ /// three values to be present on the stack. It will consume the top
+ /// three values on the stack as parameters and push the resulting substring
+ /// onto the stack. From the top it expects the values on the stack as:
+ /// - field
+ /// - delims
+ /// - str
+ ///
+ /// str is the string to split. If it is empty, an empty
+ /// string is pushed onto the value stack.
+ /// delims is string of character delimiters by which to split str. If it is
+ /// empty the entire value of str will be pushed on onto the value stack.
+ /// field is the field number (starting at 1) of the desired field. If it is
+ /// out of range an empty string is pushed on the value stack.
+ ///
+ /// The following examples all use the base string "one.two..four" and shows
+ /// the value returned for a given field:
+ /// ```
+ /// field => value
+ /// --------------
+ /// - 0 => ""
+ /// - 1 => "one"
+ /// - 2 => "two"
+ /// - 3 => ""
+ /// - 4 => "four"
+ /// - 5 => ""
+ /// ```
+ ///
+ /// @throw EvalBadStack if there are less than 3 values on stack
+ /// @throw EvalTypeError if field is not a number
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (3 arguments will be popped, 1 result
+ /// will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents concat operator (concatenates two other tokens)
+///
+/// For example in the sub-expression "concat('foo','bar')" the result
+/// of the evaluation is "foobar"
+/// For user convenience the "'foo' + 'bar'" alternative does the same.
+class TokenConcat : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenConcat() {}
+
+ /// @brief Concatenate two values.
+ ///
+ /// Evaluation does not use packet information, but rather consumes the last
+ /// two parameters. It does a simple string concatenation. It requires
+ /// at least two parameters to be present on stack.
+ ///
+ /// @throw EvalBadStack if there are less than 2 values on stack
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (2 arguments will be popped, 1 result
+ /// will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents an alternative
+///
+/// For example in the sub-expression "ifelse(cond, iftrue, iffalse)"
+/// the boolean "cond" expression is evaluated, if it is true then
+/// the "iftrue" value is returned else the "iffalse" value is returned.
+/// Please note that "iftrue" and "iffalse" must be plain string (vs. boolean)
+/// expressions and they are always evaluated. If you want a similar
+/// operator on boolean expressions it can be built from "and", "or" and
+/// "not" boolean operators.
+class TokenIfElse : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenIfElse() { }
+
+ /// @brief Alternative.
+ ///
+ /// Evaluation does not use packet information, but rather consumes the
+ /// last three results. It does a simple string comparison on the
+ /// condition (third value on the stack) which is required to be
+ /// either "true" or "false", and leaves the second and first
+ /// value if the condition is "true" or "false".
+ ///
+ /// @throw EvalBadStack if there are less than 3 values on stack
+ /// @throw EvalTypeError if the third value (the condition) is not
+ /// either "true" or "false"
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (two items are removed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that converts to hexadecimal string
+///
+/// For example in the sub-expression "hexstring(pkt4.mac, ':')"
+/// the binary MAC address is converted to its usual hexadecimal
+/// representation as a list of (6) pairs of hexadecimal digits
+/// separated by colons (':').
+/// Please note the token is named TokenToHexString when the syntax
+/// use the hexstring name without a leading "to".
+class TokenToHexString : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenToHexString() { }
+
+ /// @brief Convert a binary value to its hexadecimal string representation
+ ///
+ /// Evaluation does not use packet information. It requires at least
+ /// two values to be present on the stack. It will consume the top
+ /// two values on the stack as parameters and push the resulting
+ /// hexadecimal string onto the stack.
+ /// From the top it expects the values on the stack as:
+ /// - separator
+ /// - binary
+ ///
+ /// binary is the binary value (note it can be any value, i.e.
+ /// it is not checked to really be not printable).
+ /// separator is literal for instance '-' or ':'. The empty separator
+ /// means no separator.
+ ///
+ /// The following example use a binary MAC address 06:ce:8f:55:b3:33:
+ /// - mac, '-' => "06-ce-8f-55-b3-33"
+ ///
+ /// @throw EvalBadStack if there are less than 2 values on stack
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (2 arguments will be popped, 1 result
+ /// will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents logical negation operator
+///
+/// For example in the expression "not(option[vendor-class].text == 'MSF')"
+/// this token represents the leading "not"
+class TokenNot : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenNot() {}
+
+ /// @brief Logical negation.
+ ///
+ /// Evaluation does not use packet information, but rather consumes the last
+ /// result. It does a simple string comparison and sets the value to
+ /// either "true" or "false". It requires at least one value to be
+ /// present on stack and to be either "true" or "false".
+ ///
+ /// @throw EvalBadStack if there are less than 1 value on stack
+ /// @throw EvalTypeError if the top value on the stack is not either
+ /// "true" or "false"
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (logical top value negated)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents logical and operator
+///
+/// For example "option[10].exists and option[11].exists"
+class TokenAnd : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenAnd() {}
+
+ /// @brief Logical and.
+ ///
+ /// Evaluation does not use packet information, but rather consumes the last
+ /// two parameters. It returns "true" if and only if both are "true".
+ /// It requires at least two logical (i.e., "true" or "false') values
+ /// present on stack.
+ ///
+ /// @throw EvalBadStack if there are less than 2 values on stack
+ /// @throw EvalTypeError if one of the 2 values on stack is not
+ /// "true" or "false"
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (2 arguments will be popped, 1 result
+ /// will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents logical or operator
+///
+/// For example "option[10].exists or option[11].exists"
+class TokenOr : public Token {
+public:
+ /// @brief Constructor (does nothing)
+ TokenOr() {}
+
+ /// @brief Logical or.
+ ///
+ /// Evaluation does not use packet information, but rather consumes the last
+ /// two parameters. It returns "false" if and only if both are "false".
+ /// It requires at least two logical (i.e., "true" or "false') values
+ /// present on stack.
+ ///
+ /// @throw EvalBadStack if there are less than 2 values on stack
+ /// @throw EvalTypeError if one of the 2 values on stack is not
+ /// "true" or "false"
+ ///
+ /// @param pkt (unused)
+ /// @param values - stack of values (2 arguments will be popped, 1 result
+ /// will be pushed)
+ void evaluate(Pkt& pkt, ValueStack& values);
+};
+
+/// @brief Token that represents client class membership
+///
+/// For example "not member('foo')" is the complement of class foo
+class TokenMember : public Token {
+public:
+ /// @brief Constructor
+ ///
+ /// @param client_class client class name
+ TokenMember(const std::string& client_class)
+ : client_class_(client_class){
+ }
+
+ /// @brief Token evaluation (check if client_class_ was added to
+ /// packet client classes)
+ ///
+ /// @param pkt the class name will be check from this packet's client classes
+ /// @param values true (if found) or false (if not found) will be pushed here
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+ /// @brief Returns client class name
+ ///
+ /// This method is used in testing to determine if the parser had
+ /// instantiated TokenMember with correct parameters.
+ ///
+ /// @return client class name the token expects to check membership.
+ const ClientClass& getClientClass() const {
+ return (client_class_);
+ }
+
+protected:
+ /// @brief The client class name
+ ClientClass client_class_;
+};
+
+/// @brief Token that represents vendor options in DHCPv4 and DHCPv6.
+///
+/// It covers vendor independent vendor information option (125, DHCPv4)
+/// and vendor option (17, DHCPv6). Since both of those options may have
+/// suboptions, this class is derived from TokenOption and leverages its
+/// ability to operate on sub-options. It also adds additional capabilities.
+/// In particular, it allows retrieving enterprise-id.
+///
+/// It can represent the following expressions:
+/// vendor[4491].exists - if vendor option with enterprise-id = 4491 exists
+/// vendor[*].exists - if any vendor option exists
+/// vendor.enterprise - returns enterprise-id from vendor option
+/// vendor[4491].option[1].exists - check if suboption 1 exists for vendor 4491
+/// vendor[4491].option[1].hex - return content of suboption 1 for vendor 4491
+class TokenVendor : public TokenOption {
+public:
+
+ /// @brief Specifies a field of the vendor option
+ enum FieldType {
+ SUBOPTION, ///< If this token fetches a suboption, not a field.
+ ENTERPRISE_ID, ///< enterprise-id field (vendor-info, vendor-class)
+ EXISTS, ///< vendor[123].exists
+ DATA ///< data chunk, used in derived vendor-class only
+ };
+
+ /// @brief Constructor used for accessing a field
+ ///
+ /// @param u universe (either V4 or V6)
+ /// @param vendor_id specifies enterprise-id (0 means any)
+ /// @param field specifies which field should be returned
+ TokenVendor(Option::Universe u, uint32_t vendor_id, FieldType field);
+
+
+ /// @brief Constructor used for accessing an option
+ ///
+ /// This constructor is used for accessing suboptions. In general
+ /// option_code is mandatory, except when repr is EXISTS. For
+ /// option_code = 0 and repr = EXISTS, the token will return true
+ /// if the whole option exists, not suboptions.
+ ///
+ /// @param u universe (either V4 or V6)
+ /// @param vendor_id specifies enterprise-id (0 means any)
+ /// @param repr representation type (hex or exists)
+ /// @param option_code sub-option code
+ TokenVendor(Option::Universe u, uint32_t vendor_id, RepresentationType repr,
+ uint16_t option_code = 0);
+
+ /// @brief Returns enterprise-id
+ ///
+ /// Used in tests only.
+ ///
+ /// @return enterprise-id
+ uint32_t getVendorId() const;
+
+ /// @brief Returns field.
+ ///
+ /// Used in tests only.
+ ///
+ /// @return field type.
+ FieldType getField() const;
+
+ /// @brief This is a method for evaluating a packet.
+ ///
+ /// Depending on the value of vendor_id, field type, representation and
+ /// option code, it will attempt to return specified characteristic of the
+ /// vendor option
+ ///
+ /// If vendor-id is specified, check only option with that particular
+ /// enterprise-id. If vendor-id is 0, check any vendor option, regardless
+ /// of its enterprise-id value.
+ ///
+ /// If FieldType is NONE, get specified suboption represented by option_code
+ /// and represent it as specified by repr.
+ ///
+ /// If FieldType is ENTERPRISE_ID, return value of the enterprise-id field
+ /// or "" if there's no vendor option.
+ ///
+ /// @throw EvalTypeError for any other FieldType values.
+ ///
+ /// The parameters passed are:
+ ///
+ /// @param pkt - vendor options will be searched for here.
+ /// @param values - the evaluated value will be pushed here.
+ virtual void evaluate(Pkt& pkt, ValueStack& values);
+
+protected:
+ /// @brief Attempts to get a suboption.
+ ///
+ /// This method overrides behavior of TokenOption method. It attempts to retrieve
+ /// the sub-option of the vendor option. Using derived method allows usage of
+ /// TokenOption routines.
+ ///
+ /// @param pkt vendor option will be searched here.
+ /// @return suboption of the vendor option (if exists)
+ virtual OptionPtr getOption(Pkt& pkt);
+
+ /// @brief Universe (V4 or V6)
+ ///
+ /// We need to remember it, because depending on the universe, the code needs
+ /// to retrieve either option 125 (DHCPv4) or 17 (DHCPv6).
+ Option::Universe universe_;
+
+ /// @brief Enterprise-id value
+ ///
+ /// Yeah, I know it technically should be called enterprise-id, but that's
+ /// too long and everyone calls it vendor-id.
+ uint32_t vendor_id_;
+
+ /// @brief Specifies which field should be accessed.
+ FieldType field_;
+};
+
+/// @brief Token that represents vendor class options in DHCPv4 and DHCPv6.
+///
+/// It covers vendor independent vendor information option (124, DHCPv4)
+/// and vendor option (16, DHCPv6). Contrary to vendor options, vendor class
+/// options don't have suboptions, but have data chunks (tuples) instead.
+/// Therefore they're not referenced by option codes, but by indexes.
+/// The first data chunk is data[0], the second is data[1] etc.
+///
+/// This class is derived from OptionVendor to take advantage of the
+/// enterprise handling field and field type.
+///
+/// It can represent the following expressions:
+/// vendor-class[4491].exists
+/// vendor-class[*].exists
+/// vendor-class[*].enterprise
+/// vendor-class[4491].data - content of the opaque-data of the first tuple
+/// vendor-class[4491].data[3] - content of the opaque-data of the 4th tuple
+class TokenVendorClass : public TokenVendor {
+public:
+
+ /// @brief This constructor is used to access fields.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param vendor_id value of enterprise-id field (0 means any)
+ /// @param repr representation type (EXISTS or HEX)
+ TokenVendorClass(Option::Universe u, uint32_t vendor_id, RepresentationType repr);
+
+ /// @brief This constructor is used to access data chunks.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param vendor_id value of enterprise-id field (0 means any)
+ /// @param field type of the field (usually DATA or ENTERPRISE)
+ /// @param index specifies which data chunk to retrieve
+ TokenVendorClass(Option::Universe u, uint32_t vendor_id, FieldType field,
+ uint16_t index = 0);
+
+ /// @brief Returns data index.
+ ///
+ /// Used in testing.
+ /// @return data index (specifies which data chunk to retrieve)
+ uint16_t getDataIndex() const;
+
+protected:
+
+ /// @brief This is a method for evaluating a packet.
+ ///
+ /// Depending on the value of vendor_id, field type, representation and
+ /// option code, it will attempt to return specified characteristic of the
+ /// vendor option
+ ///
+ /// If vendor-id is specified, check only option with that particular
+ /// enterprise-id. If vendor-id is 0, check any vendor option, regardless
+ /// of its enterprise-id value.
+ ///
+ /// If FieldType is ENTERPRISE_ID, return value of the enterprise-id field
+ /// or "" if there's no vendor option.
+ ///
+ /// If FieldType is DATA, get specified data chunk represented by index_.
+ ///
+ /// If FieldType is EXISTS, return true if vendor-id matches.
+ ///
+ /// @throw EvalTypeError for any other FieldType values.
+ ///
+ /// The parameters passed are:
+ ///
+ /// @param pkt - vendor options will be searched for here.
+ /// @param values - the evaluated value will be pushed here.
+ void evaluate(Pkt& pkt, ValueStack& values);
+
+ /// @brief Data chunk index.
+ uint16_t index_;
+};
+
+/// @brief Token that represents sub-options in DHCPv4 and DHCPv6.
+///
+/// It covers any options which encapsulate sub-options, for instance
+/// dhcp-agent-options (82, DHCPv4) or rsoo (66, DHCPv6).
+/// This class is derived from TokenOption and leverages its ability
+/// to operate on sub-options. It also adds additional capabilities.
+///
+/// Note: @c TokenSubOption virtually derives @c TokenOption because both
+/// classes are inherited together in more complex classes in other parts of
+/// the code. This makes the base class @c TokenOption to exist only once in
+/// such complex classes.
+///
+/// It can represent the following expressions:
+/// option[149].exists - check if option 149 exists
+/// option[149].option[1].exists - check if suboption 1 exists in the option 149
+/// option[149].option[1].hex - return content of suboption 1 for option 149
+class TokenSubOption : public virtual TokenOption {
+public:
+
+ /// @note Does not define its own representation type:
+ /// simply use the @c TokenOption::RepresentationType
+
+ /// @brief Constructor that takes an option and sub-option codes as parameter
+ ///
+ /// Note: There is no constructor that takes names.
+ ///
+ /// @param option_code code of the parent option.
+ /// @param sub_option_code code of the sub-option to be represented.
+ /// @param rep_type Token representation type.
+ TokenSubOption(const uint16_t option_code,
+ const uint16_t sub_option_code,
+ const RepresentationType& rep_type)
+ : TokenOption(option_code, rep_type), sub_option_code_(sub_option_code) {}
+
+ /// @brief This is a method for evaluating a packet.
+ ///
+ /// This token represents a value of the sub-option, so this method
+ /// attempts to extract the parent option from the packet and when
+ /// it succeeds to extract the sub-option from the option and
+ /// its value on the stack.
+ /// If the parent option or the sub-option is not there, an empty
+ /// string ("") is put on the stack.
+ ///
+ /// @param pkt specified parent option will be extracted from this packet
+ /// @param values value of the sub-option will be pushed here (or "")
+ virtual void evaluate(Pkt& pkt, ValueStack& values);
+
+ /// @brief Returns sub-option-code
+ ///
+ /// This method is used in testing to determine if the parser had
+ /// instantiated TokenSubOption with correct parameters.
+ ///
+ /// @return option-code of the sub-option this token expects to extract.
+ uint16_t getSubCode() const {
+ return (sub_option_code_);
+ }
+
+protected:
+ /// @brief Attempts to retrieve a sub-option.
+ ///
+ /// @param parent the sub-option will be retrieved from here
+ /// @return sub-option instance (or NULL if not found)
+ virtual OptionPtr getSubOption(const OptionPtr& parent);
+
+ uint16_t sub_option_code_; ///< Code of the sub-option to be extracted
+};
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif