This is an automated email from the ASF dual-hosted git repository.

zwoop pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 59ecdebff9 Adds HRW state variables, 16 flags and 4 int8s (#12015)
59ecdebff9 is described below

commit 59ecdebff9c058c0a5e118d641ba8ec5bd1851df
Author: Leif Hedstrom <[email protected]>
AuthorDate: Fri Feb 28 15:15:03 2025 -0700

    Adds HRW state variables, 16 flags and 4 int8s (#12015)
---
 doc/admin-guide/plugins/header_rewrite.en.rst |  85 ++++++++++-
 plugins/header_rewrite/conditions.cc          | 160 ++++++++++++++++++---
 plugins/header_rewrite/conditions.h           | 131 +++++++++++++++--
 plugins/header_rewrite/factory.cc             |  13 ++
 plugins/header_rewrite/header_rewrite.cc      |   6 +-
 plugins/header_rewrite/header_rewrite_test.cc |   3 +
 plugins/header_rewrite/lulu.h                 |   5 +-
 plugins/header_rewrite/operators.cc           | 198 +++++++++++++++++++++++++-
 plugins/header_rewrite/operators.h            |  90 ++++++++++++
 plugins/header_rewrite/parser.h               |   7 -
 plugins/header_rewrite/statement.cc           |  22 +++
 plugins/header_rewrite/statement.h            |  35 ++++-
 plugins/header_rewrite/value.h                |   6 +
 13 files changed, 707 insertions(+), 54 deletions(-)

diff --git a/doc/admin-guide/plugins/header_rewrite.en.rst 
b/doc/admin-guide/plugins/header_rewrite.en.rst
index 903acca773..474df01ab3 100644
--- a/doc/admin-guide/plugins/header_rewrite.en.rst
+++ b/doc/admin-guide/plugins/header_rewrite.en.rst
@@ -140,6 +140,17 @@ with the word ``else``. The following example illustrates 
this::
 The ``else`` clause is not a condition, and does not take any flags, it is
 of course optional, but when specified must be followed by at least one 
operator.
 
+State variables
+---------------
+
+A set of state variables are also available for both conditions and operators.
+There are currently 16 flag states, 4 8-bit integers and one 16-bit integer 
states.
+These states are all transactional, meaning they are usable and persistent 
across
+all hooks.
+
+The flag states are numbers 0-15, the 8-bit integer states are numbered 0-3, 
and the
+one 16-bit integer state is number 0.
+
 Conditions
 ----------
 
@@ -457,9 +468,10 @@ As a special matcher, the inbound IP addresses can be 
matched against a list of
 
    cond %{INBOUND:REMOTE-ADDR} {192.168.201.0/24,10.0.0.0/8}
 
-Note that this will not work against the non-IP based conditions, such as the 
protocol families,
-and the configuration parser will error out. The format here is very specific, 
in particular no
-white spaces are allowed between the ranges.
+.. note::
+    This will not work against the non-IP based conditions, such as the 
protocol families,
+    and the configuration parser will error out. The format here is very 
specific, in particular no
+    white spaces are allowed between the ranges.
 
 IP
 ~~
@@ -569,6 +581,36 @@ RANDOM
 
 Generates a random integer from ``0`` up to (but not including) ``<n>``. 
Mathematically, ``[0,n)`` or ``0 <= r < n``.
 
+STATE-FLAG
+~~~~~~~~~~
+::
+
+      cond %{STATE-FLAG:<n>}
+
+This condition allows you to check the state of a flag. The ``<n>`` is the
+number of the flag, from 0 to 15. This condition returns a ``true`` or
+``false`` value, depending on the state of the flag.
+
+STATE-INT8
+~~~~~~~~~~
+::
+
+      cond %{STATE-INT8:<n>}
+
+This condition allows you to check the state of an 8-bit unsigned integer.
+The ``<n>`` is the number of the integer, from 0 to 3. The current value of
+the state integer is returned, and all 4 integers are initialized to 0.
+
+STATE-INT16
+~~~~~~~~~~~
+::
+
+      cond %{STATE-INT16<:0>}
+
+This condition allows you to check the state of an 16-bit unsigned integer.
+There's only one such integer, and its value is returned from this condition.
+As such, the index, ``0``, is optional here.
+
 STATUS
 ~~~~~~
 ::
@@ -920,6 +962,38 @@ location automatically. This operator supports `String 
concatenations`_ for
 ``READ_RESPONSE_HDR_HOOK`` (the default when the plugin is global), the
 ``SEND_RESPONSE_HDR_HOOK``, or the ``REMAP_PSEUDO_HOOK``.
 
+set-state-flag
+~~~~~~~~~~~~~~
+::
+
+  set-state-flag <n> <value>
+
+This operator allows you to set the state of a flag. The ``<n>`` is the
+number of the flag, from 0 to 15. The ``<value>`` is either ``true`` or 
``false``,
+turning the flag on or off.
+
+set-state-int8
+~~~~~~~~~~~~~~
+::
+
+   set-state-int8 <n> <value>
+
+This operator allows you to set the state of an 8-bit unsigned integer.
+The ``<n>`` is the number of the integer, from 0 to 3. The ``<value>`` is an
+unsigned 8-bit integer, 0-255. It can also be a condition, in which case the
+value of the condition is used.
+
+set-state-int16
+~~~~~~~~~~~~~~~
+::
+
+   set-state-int16 0 <value>
+
+This operator allows you to set the state of a 16-bit unsigned integer.
+The ``<value>`` is an unsigned 16-bit integer as well, 0-65535. It can also
+be a condition, in which case thevalue of the condition is used. The index,
+0, is always required eventhough there is only one 16-bit integer state 
variable.
+
 set-status
 ~~~~~~~~~~
 ::
@@ -1438,6 +1512,11 @@ could each be tagged with a consistent name to make 
finding logs easier.::
 
 (Then in :file:`logging.yaml`, log ``%<{@PropertyName}cqh>``)
 
+.. note::
+    With the new ``state-flag``, ``state-int8`` and ``state-int16`` operators, 
you can
+    sometimes avoid setting internal ``@`` headers for passing information 
between hooks.
+    These internal state variables are much more efficient than setting and 
reading headers.
+
 Remove Client Query Parameters
 ------------------------------------
 
diff --git a/plugins/header_rewrite/conditions.cc 
b/plugins/header_rewrite/conditions.cc
index 747d260211..9422f5d3f9 100644
--- a/plugins/header_rewrite/conditions.cc
+++ b/plugins/header_rewrite/conditions.cc
@@ -137,10 +137,7 @@ ConditionRandom::eval(const Resources & /* res ATS_UNUSED 
*/)
 void
 ConditionRandom::append_value(std::string &s, const Resources & /* res 
ATS_UNUSED */)
 {
-  std::ostringstream oss;
-
-  oss << rand_r(&_seed) % _max;
-  s += oss.str();
+  s += std::to_string(rand_r(&_seed) % _max);
   Dbg(pi_dbg_ctl, "Appending RANDOM(%d) to evaluation value -> %s", _max, 
s.c_str());
 }
 
@@ -746,10 +743,7 @@ ConditionNow::set_qualifier(const std::string &q)
 void
 ConditionNow::append_value(std::string &s, const Resources & /* res ATS_UNUSED 
*/)
 {
-  std::ostringstream oss;
-
-  oss << get_now_qualified(_now_qual);
-  s += oss.str();
+  s += std::to_string(get_now_qualified(_now_qual));
   Dbg(pi_dbg_ctl, "Appending NOW() to evaluation value -> %s", s.c_str());
 }
 
@@ -822,14 +816,11 @@ ConditionGeo::set_qualifier(const std::string &q)
 void
 ConditionGeo::append_value(std::string &s, const Resources &res)
 {
-  std::ostringstream oss;
-
   if (is_int_type()) {
-    oss << get_geo_int(TSHttpTxnClientAddrGet(res.txnp));
+    s += std::to_string(get_geo_int(TSHttpTxnClientAddrGet(res.txnp)));
   } else {
-    oss << get_geo_string(TSHttpTxnClientAddrGet(res.txnp));
+    s += get_geo_string(TSHttpTxnClientAddrGet(res.txnp));
   }
-  s += oss.str();
   Dbg(pi_dbg_ctl, "Appending GEO() to evaluation value -> %s", s.c_str());
 }
 
@@ -899,10 +890,7 @@ ConditionId::append_value(std::string &s, const Resources 
&res ATS_UNUSED)
 {
   switch (_id_qual) {
   case ID_QUAL_REQUEST: {
-    std::ostringstream oss;
-
-    oss << TSHttpTxnIdGet(res.txnp);
-    s += oss.str();
+    s += std::to_string(TSHttpTxnIdGet(res.txnp));
   } break;
   case ID_QUAL_PROCESS: {
     TSUuid process = TSProcessUuidGet();
@@ -1428,16 +1416,146 @@ ConditionNextHop::eval(const Resources &res)
 
 // ConditionHttpCntl: request header.
 void
-ConditionHttpCntl::initialize(Parser &p)
+ConditionHttpCntl::set_qualifier(const std::string &q)
+{
+  Condition::set_qualifier(q);
+
+  Dbg(pi_dbg_ctl, "\tParsing %%{HTTP-CNTL:%s}", q.c_str());
+  _http_cntl_qual = parse_http_cntl_qualifier(q);
+}
+
+void
+ConditionHttpCntl::append_value(std::string &s, const Resources &res)
+{
+  s += TSHttpTxnCntlGet(res.txnp, _http_cntl_qual) ? "TRUE" : "FALSE";
+  Dbg(pi_dbg_ctl, "Evaluating HTTP-CNTL(%s)", _qualifier.c_str());
+}
+
+bool
+ConditionHttpCntl::eval(const Resources &res)
+{
+  Dbg(pi_dbg_ctl, "Evaluating HTTP-CNTL()");
+  return TSHttpTxnCntlGet(res.txnp, _http_cntl_qual);
+}
+
+// ConditionStateFlag
+void
+ConditionStateFlag::set_qualifier(const std::string &q)
+{
+  Condition::set_qualifier(q);
+
+  _flag_ix = strtol(q.c_str(), nullptr, 10);
+  if (_flag_ix < 0 || _flag_ix >= NUM_STATE_FLAGS) {
+    TSError("[%s] STATE-FLAG index out of range: %s", PLUGIN_NAME, q.c_str());
+  } else {
+    Dbg(pi_dbg_ctl, "\tParsing %%{STATE-FLAG:%s}", q.c_str());
+    _mask = 1ULL << _flag_ix;
+  }
+}
+
+void
+ConditionStateFlag::append_value(std::string &s, const Resources &res)
+{
+  s += eval(res) ? "TRUE" : "FALSE";
+  Dbg(pi_dbg_ctl, "Evaluating STATE-FLAG(%d)", _flag_ix);
+}
+
+bool
+ConditionStateFlag::eval(const Resources &res)
+{
+  auto data = reinterpret_cast<uint64_t>(TSUserArgGet(res.txnp, _txn_slot));
+
+  Dbg(pi_dbg_ctl, "Evaluating STATE-FLAG()");
+
+  return (data & _mask) == _mask;
+}
+
+// ConditionStateInt8
+void
+ConditionStateInt8::initialize(Parser &p)
 {
   Condition::initialize(p);
+  MatcherType *match = new MatcherType(_cond_op);
+
+  match->set(static_cast<uint8_t>(strtol(p.get_arg().c_str(), nullptr, 10)), 
mods());
+  _matcher = match;
 }
 
 void
-ConditionHttpCntl::set_qualifier(const std::string &q)
+ConditionStateInt8::set_qualifier(const std::string &q)
 {
   Condition::set_qualifier(q);
 
-  Dbg(pi_dbg_ctl, "\tParsing %%{HTTP-CNTL:%s}", q.c_str());
-  _http_cntl_qual = parse_http_cntl_qualifier(q);
+  _byte_ix = strtol(q.c_str(), nullptr, 10);
+  if (_byte_ix < 0 || _byte_ix >= NUM_STATE_INT8S) {
+    TSError("[%s] STATE-INT8 index out of range: %s", PLUGIN_NAME, q.c_str());
+  } else {
+    Dbg(pi_dbg_ctl, "\tParsing %%{STATE-INT8:%s}", q.c_str());
+  }
+}
+
+void
+ConditionStateInt8::append_value(std::string &s, const Resources &res)
+{
+  uint8_t data = _get_data(res);
+
+  s += std::to_string(data);
+
+  Dbg(pi_dbg_ctl, "Appending STATE-INT8(%d) to evaluation value -> %s", data, 
s.c_str());
+}
+
+bool
+ConditionStateInt8::eval(const Resources &res)
+{
+  uint8_t data = _get_data(res);
+
+  Dbg(pi_dbg_ctl, "Evaluating STATE-INT8()");
+
+  return static_cast<const MatcherType *>(_matcher)->test(data);
+}
+
+// ConditionStateInt16
+void
+ConditionStateInt16::initialize(Parser &p)
+{
+  Condition::initialize(p);
+  MatcherType *match = new MatcherType(_cond_op);
+
+  match->set(static_cast<uint16_t>(strtol(p.get_arg().c_str(), nullptr, 10)), 
mods());
+  _matcher = match;
+}
+
+void
+ConditionStateInt16::set_qualifier(const std::string &q)
+{
+  Condition::set_qualifier(q);
+
+  if (!q.empty()) { // This qualifier is optional, but must be 0 if there
+    long ix = strtol(q.c_str(), nullptr, 10);
+
+    if (ix != 0) {
+      TSError("[%s] STATE-INT16 index out of range: %s", PLUGIN_NAME, 
q.c_str());
+    } else {
+      Dbg(pi_dbg_ctl, "\tParsing %%{STATE-INT16:%s}", q.c_str());
+    }
+  }
+}
+
+void
+ConditionStateInt16::append_value(std::string &s, const Resources &res)
+{
+  uint16_t data = _get_data(res);
+
+  s += std::to_string(data);
+  Dbg(pi_dbg_ctl, "Appending STATE-INT16(%d) to evaluation value -> %s", data, 
s.c_str());
+}
+
+bool
+ConditionStateInt16::eval(const Resources &res)
+{
+  uint16_t data = _get_data(res);
+
+  Dbg(pi_dbg_ctl, "Evaluating STATE-INT8()");
+
+  return static_cast<const MatcherType *>(_matcher)->test(data);
 }
diff --git a/plugins/header_rewrite/conditions.h 
b/plugins/header_rewrite/conditions.h
index f49fcb3c04..40ce009b89 100644
--- a/plugins/header_rewrite/conditions.h
+++ b/plugins/header_rewrite/conditions.h
@@ -638,23 +638,11 @@ public:
   ConditionHttpCntl(const ConditionHttpCntl &) = delete;
   void operator=(const ConditionHttpCntl &)    = delete;
 
-  void initialize(Parser &p) override;
   void set_qualifier(const std::string &q) override;
-
-  void
-  append_value(std::string &s, const Resources &res) override
-  {
-    s += TSHttpTxnCntlGet(res.txnp, _http_cntl_qual) ? "TRUE" : "FALSE";
-    Dbg(pi_dbg_ctl, "Evaluating HTTP-CNTL(%s)", _qualifier.c_str());
-  }
+  void append_value(std::string &s, const Resources &res) override;
 
 protected:
-  bool
-  eval(const Resources &res) override
-  {
-    Dbg(pi_dbg_ctl, "Evaluating HTTP-CNTL()");
-    return TSHttpTxnCntlGet(res.txnp, _http_cntl_qual);
-  }
+  bool eval(const Resources &res) override;
 
 private:
   TSHttpCntlType _http_cntl_qual = TS_HTTP_CNTL_LOGGING_MODE;
@@ -725,3 +713,118 @@ private:
   Condition *_cond = nullptr; // First pre-condition (linked list)
   bool       _end  = false;
 };
+
+// State Flags
+class ConditionStateFlag : public Condition
+{
+public:
+  explicit ConditionStateFlag()
+  {
+    static_assert(sizeof(void *) == 8, "State Variables requires a 64-bit 
system.");
+    Dbg(dbg_ctl, "Calling CTOR for ConditionStateFlag");
+  }
+
+  // noncopyable
+  ConditionStateFlag(const ConditionStateFlag &) = delete;
+  void operator=(const ConditionStateFlag &)     = delete;
+
+  void set_qualifier(const std::string &q) override;
+  void append_value(std::string &s, const Resources &res) override;
+
+protected:
+  bool eval(const Resources &res) override;
+
+  bool
+  need_txn_slot() const override
+  {
+    return true;
+  }
+
+private:
+  int      _flag_ix = -1;
+  uint64_t _mask    = 0;
+};
+
+// INT8 state variables
+class ConditionStateInt8 : public Condition
+{
+  using MatcherType = Matchers<uint8_t>;
+
+public:
+  explicit ConditionStateInt8()
+  {
+    static_assert(sizeof(void *) == 8, "State Variables requires a 64-bit 
system.");
+    Dbg(dbg_ctl, "Calling CTOR for ConditionStateInt8");
+  }
+
+  // noncopyable
+  ConditionStateInt8(const ConditionStateInt8 &) = delete;
+  void operator=(const ConditionStateInt8 &)     = delete;
+
+  void initialize(Parser &p) override;
+  void set_qualifier(const std::string &q) override;
+  void append_value(std::string &s, const Resources &res) override;
+
+protected:
+  bool eval(const Resources &res) override;
+
+  bool
+  need_txn_slot() const override
+  {
+    return true;
+  }
+
+private:
+  // Little helper function to extract out the data from the TXN user pointer
+  uint8_t
+  _get_data(const Resources &res) const
+  {
+    TSAssert(_byte_ix >= 0 && _byte_ix < NUM_STATE_INT8S);
+    auto    ptr  = reinterpret_cast<uint64_t>(TSUserArgGet(res.txnp, 
_txn_slot));
+    uint8_t data = (ptr & STATE_INT8_MASKS[_byte_ix]) >> (NUM_STATE_FLAGS + 
_byte_ix * 8);
+
+    return data;
+  }
+
+  int _byte_ix = -1;
+};
+
+// INT16 state variables
+class ConditionStateInt16 : public Condition
+{
+  using MatcherType = Matchers<uint16_t>;
+
+public:
+  explicit ConditionStateInt16()
+  {
+    static_assert(sizeof(void *) == 8, "State Variables requires a 64-bit 
system.");
+    Dbg(dbg_ctl, "Calling CTOR for ConditionStateInt16");
+  }
+
+  // noncopyable
+  ConditionStateInt16(const ConditionStateInt8 &) = delete;
+  void operator=(const ConditionStateInt8 &)      = delete;
+
+  void initialize(Parser &p) override;
+  void set_qualifier(const std::string &q) override;
+  void append_value(std::string &s, const Resources &res) override;
+
+protected:
+  bool eval(const Resources &res) override;
+
+  bool
+  need_txn_slot() const override
+  {
+    return true;
+  }
+
+private:
+  // Little helper function to extract out the data from the TXN user pointer
+  uint16_t
+  _get_data(const Resources &res) const
+  {
+    auto ptr = reinterpret_cast<uint64_t>(TSUserArgGet(res.txnp, _txn_slot));
+
+    return ((ptr & STATE_INT16_MASK) >> 48);
+  }
+};
diff --git a/plugins/header_rewrite/factory.cc 
b/plugins/header_rewrite/factory.cc
index 7fe0290bdb..4aa40a4b6d 100644
--- a/plugins/header_rewrite/factory.cc
+++ b/plugins/header_rewrite/factory.cc
@@ -79,6 +79,12 @@ operator_factory(const std::string &op)
     o = new OperatorRunPlugin();
   } else if (op == "set-body-from") {
     o = new OperatorSetBodyFrom();
+  } else if (op == "set-state-flag") {
+    o = new OperatorSetStateFlag();
+  } else if (op == "set-state-int8") {
+    o = new OperatorSetStateInt8();
+  } else if (op == "set-state-int16") {
+    o = new OperatorSetStateInt16();
   } else {
     TSError("[%s] Unknown operator: %s", PLUGIN_NAME, op.c_str());
     return nullptr;
@@ -166,6 +172,13 @@ condition_factory(const std::string &cond)
     c = new ConditionHttpCntl();
   } else if (c_name == "GROUP") {
     c = new ConditionGroup();
+  } else if (c_name == "STATE-FLAG") {
+    c = new ConditionStateFlag();
+  } else if (c_name == "STATE-INT8") {
+    c = new ConditionStateInt8();
+  } else if (c_name == "STATE-INT16") {
+    c = new ConditionStateInt16();
+
   } else {
     TSError("[%s] Unknown condition %s", PLUGIN_NAME, c_name.c_str());
     return nullptr;
diff --git a/plugins/header_rewrite/header_rewrite.cc 
b/plugins/header_rewrite/header_rewrite.cc
index 7f651014f9..96a3fe31a4 100644
--- a/plugins/header_rewrite/header_rewrite.cc
+++ b/plugins/header_rewrite/header_rewrite.cc
@@ -37,10 +37,11 @@
 #include "conditions_geo.h"
 
 // Debugs
-const char PLUGIN_NAME[]     = "header_rewrite";
-const char PLUGIN_NAME_DBG[] = "dbg_header_rewrite";
 namespace header_rewrite_ns
 {
+const char PLUGIN_NAME[]     = "header_rewrite";
+const char PLUGIN_NAME_DBG[] = "dbg_header_rewrite";
+
 DbgCtl dbg_ctl{PLUGIN_NAME_DBG};
 DbgCtl pi_dbg_ctl{PLUGIN_NAME};
 
@@ -104,6 +105,7 @@ public:
   {
     return _resids[hook];
   }
+
   RuleSet *
   rule(int hook) const
   {
diff --git a/plugins/header_rewrite/header_rewrite_test.cc 
b/plugins/header_rewrite/header_rewrite_test.cc
index 355ec164b3..8e92f7ae5f 100644
--- a/plugins/header_rewrite/header_rewrite_test.cc
+++ b/plugins/header_rewrite/header_rewrite_test.cc
@@ -27,8 +27,11 @@
 
 #include "parser.h"
 
+namespace header_rewrite_ns
+{
 const char PLUGIN_NAME[]     = "TEST_header_rewrite";
 const char PLUGIN_NAME_DBG[] = "TEST_dbg_header_rewrite";
+} // namespace header_rewrite_ns
 
 void
 TSError(const char *fmt, ...)
diff --git a/plugins/header_rewrite/lulu.h b/plugins/header_rewrite/lulu.h
index f12339346e..3900713b0d 100644
--- a/plugins/header_rewrite/lulu.h
+++ b/plugins/header_rewrite/lulu.h
@@ -35,13 +35,14 @@ std::string getIP(sockaddr const *s_sockaddr);
 char       *getIP(sockaddr const *s_sockaddr, char res[INET6_ADDRSTRLEN]);
 uint16_t    getPort(sockaddr const *s_sockaddr);
 
+namespace header_rewrite_ns
+{
 extern const char PLUGIN_NAME[];
 extern const char PLUGIN_NAME_DBG[];
 
-namespace header_rewrite_ns
-{
 extern DbgCtl        dbg_ctl;
 extern DbgCtl        pi_dbg_ctl;
 extern PluginFactory plugin_factory;
 } // namespace header_rewrite_ns
+
 using namespace header_rewrite_ns;
diff --git a/plugins/header_rewrite/operators.cc 
b/plugins/header_rewrite/operators.cc
index 190a23399e..28c1af6219 100644
--- a/plugins/header_rewrite/operators.cc
+++ b/plugins/header_rewrite/operators.cc
@@ -1157,7 +1157,7 @@ OperatorSetHttpCntl::initialize(Parser &p)
   Operator::initialize(p);
   _cntl_qual = parse_http_cntl_qualifier(p.get_arg());
 
-  std::string flag = p.copy_value();
+  std::string flag = p.get_value(); // Make a copy of the value
 
   std::transform(flag.begin(), flag.end(), flag.begin(), ::tolower);
 
@@ -1321,3 +1321,199 @@ OperatorSetBodyFrom::exec(const Resources &res) const
   }
   return false;
 }
+
+void
+OperatorSetStateFlag::initialize(Parser &p)
+{
+  Operator::initialize(p);
+
+  _flag_ix = strtol(p.get_arg().c_str(), nullptr, 10);
+
+  if (_flag_ix < 0 || _flag_ix >= NUM_STATE_FLAGS) {
+    TSError("[%s] state flag with index %d is out of range", PLUGIN_NAME, 
_flag_ix);
+    return;
+  }
+
+  std::string flag = p.get_value(); // Make a copy of the value
+
+  std::transform(flag.begin(), flag.end(), flag.begin(), ::tolower);
+
+  if (flag == "1" || flag == "true" || flag == "on" || flag == "enable") {
+    _mask = 1ULL << _flag_ix;
+    _flag = true;
+  } else {
+    _mask = ~(1ULL << _flag_ix);
+    _flag = false;
+  }
+}
+
+// This operator should be allowed everywhere
+void
+OperatorSetStateFlag::initialize_hooks()
+{
+  add_allowed_hook(TS_HTTP_READ_REQUEST_HDR_HOOK);
+  add_allowed_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK);
+  add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK);
+  add_allowed_hook(TS_REMAP_PSEUDO_HOOK);
+  add_allowed_hook(TS_HTTP_PRE_REMAP_HOOK);
+  add_allowed_hook(TS_HTTP_SEND_REQUEST_HDR_HOOK);
+  add_allowed_hook(TS_HTTP_TXN_CLOSE_HOOK);
+  add_allowed_hook(TS_HTTP_TXN_START_HOOK);
+}
+
+bool
+OperatorSetStateFlag::exec(const Resources &res) const
+{
+  if (!res.txnp) {
+    TSError("[%s] OperatorSetStateFlag() failed. Transaction is null", 
PLUGIN_NAME);
+    return false;
+  }
+
+  Dbg(pi_dbg_ctl, "   Setting state flag %d to %d", _flag_ix, _flag);
+
+  auto data = reinterpret_cast<uint64_t>(TSUserArgGet(res.txnp, _txn_slot));
+
+  TSUserArgSet(res.txnp, _txn_slot, reinterpret_cast<void *>(_flag ? data | 
_mask : data & _mask));
+
+  return true;
+}
+
+void
+OperatorSetStateInt8::initialize(Parser &p)
+{
+  Operator::initialize(p);
+
+  _byte_ix = strtol(p.get_arg().c_str(), nullptr, 10);
+
+  if (_byte_ix < 0 || _byte_ix >= NUM_STATE_INT8S) {
+    TSError("[%s] state int8 with index %d is out of range", PLUGIN_NAME, 
_byte_ix);
+    return;
+  }
+
+  _value.set_value(p.get_value());
+  if (!_value.has_conds()) {
+    int v = _value.get_int_value();
+
+    if (v < 0 || v > 255) {
+      TSError("[%s] state int8 value %d is out of range", PLUGIN_NAME, v);
+      return;
+    }
+  }
+}
+
+// This operator should be allowed everywhere
+void
+OperatorSetStateInt8::initialize_hooks()
+{
+  add_allowed_hook(TS_HTTP_READ_REQUEST_HDR_HOOK);
+  add_allowed_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK);
+  add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK);
+  add_allowed_hook(TS_REMAP_PSEUDO_HOOK);
+  add_allowed_hook(TS_HTTP_PRE_REMAP_HOOK);
+  add_allowed_hook(TS_HTTP_SEND_REQUEST_HDR_HOOK);
+  add_allowed_hook(TS_HTTP_TXN_CLOSE_HOOK);
+  add_allowed_hook(TS_HTTP_TXN_START_HOOK);
+}
+
+bool
+OperatorSetStateInt8::exec(const Resources &res) const
+{
+  if (!res.txnp) {
+    TSError("[%s] OperatorSetStateInt8() failed. Transaction is null", 
PLUGIN_NAME);
+    return false;
+  }
+
+  auto ptr = reinterpret_cast<uint64_t>(TSUserArgGet(res.txnp, _txn_slot));
+  int  val = 0;
+
+  if (_value.has_conds()) { // If there are conditions, we need to evaluate 
them, which gives us a string
+    std::string v;
+
+    _value.append_value(v, res);
+    val = strtol(v.c_str(), nullptr, 10);
+    if (val < 0 || val > 255) {
+      TSWarning("[%s] state int8 value %d is out of range", PLUGIN_NAME, val);
+      return false;
+    }
+  } else {
+    // These values have already been checked at load time
+    val = _value.get_int_value();
+  }
+
+  Dbg(pi_dbg_ctl, "   Setting state int8 %d to %d", _byte_ix, val);
+  ptr &= ~STATE_INT8_MASKS[_byte_ix]; // Clear any old value
+  ptr |= (static_cast<uint64_t>(val) << (NUM_STATE_FLAGS + _byte_ix * 8));
+  TSUserArgSet(res.txnp, _txn_slot, reinterpret_cast<void *>(ptr));
+
+  return true;
+}
+
+void
+OperatorSetStateInt16::initialize(Parser &p)
+{
+  Operator::initialize(p);
+
+  int ix = strtol(p.get_arg().c_str(), nullptr, 10);
+
+  if (ix != 0) {
+    TSError("[%s] state int16 with index %d is out of range", PLUGIN_NAME, ix);
+    return;
+  }
+
+  _value.set_value(p.get_value());
+  if (!_value.has_conds()) {
+    int v = _value.get_int_value();
+
+    if (v < 0 || v > 65535) {
+      TSError("[%s] state int16 value %d is out of range", PLUGIN_NAME, v);
+      return;
+    }
+  }
+}
+
+// This operator should be allowed everywhere
+void
+OperatorSetStateInt16::initialize_hooks()
+{
+  add_allowed_hook(TS_HTTP_READ_REQUEST_HDR_HOOK);
+  add_allowed_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK);
+  add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK);
+  add_allowed_hook(TS_REMAP_PSEUDO_HOOK);
+  add_allowed_hook(TS_HTTP_PRE_REMAP_HOOK);
+  add_allowed_hook(TS_HTTP_SEND_REQUEST_HDR_HOOK);
+  add_allowed_hook(TS_HTTP_TXN_CLOSE_HOOK);
+  add_allowed_hook(TS_HTTP_TXN_START_HOOK);
+}
+
+bool
+OperatorSetStateInt16::exec(const Resources &res) const
+{
+  if (!res.txnp) {
+    TSError("[%s] OperatorSetStateInt16() failed. Transaction is null", 
PLUGIN_NAME);
+    return false;
+  }
+
+  auto ptr = reinterpret_cast<uint64_t>(TSUserArgGet(res.txnp, _txn_slot));
+  int  val = 0;
+
+  if (_value.has_conds()) { // If there are conditions, we need to evaluate 
them, which gives us a string
+    std::string v;
+
+    _value.append_value(v, res);
+    val = strtol(v.c_str(), nullptr, 10);
+    if (val < 0 || val > 65535) {
+      TSWarning("[%s] state int8 value %d is out of range", PLUGIN_NAME, val);
+      return false;
+    }
+  } else {
+    // These values have already been checked at load time
+    val = _value.get_int_value();
+  }
+
+  Dbg(pi_dbg_ctl, "   Setting state int16 to %d", val);
+  ptr &= ~STATE_INT16_MASK; // Clear any old value
+  ptr |= (static_cast<uint64_t>(val) << 48);
+  TSUserArgSet(res.txnp, _txn_slot, reinterpret_cast<void *>(ptr));
+
+  return true;
+}
diff --git a/plugins/header_rewrite/operators.h 
b/plugins/header_rewrite/operators.h
index df4e189686..12d003c3cf 100644
--- a/plugins/header_rewrite/operators.h
+++ b/plugins/header_rewrite/operators.h
@@ -505,3 +505,93 @@ protected:
 private:
   Value _value;
 };
+
+class OperatorSetStateFlag : public Operator
+{
+public:
+  OperatorSetStateFlag()
+  {
+    static_assert(sizeof(void *) == 8, "State Variables requires a 64-bit 
system.");
+    Dbg(dbg_ctl, "Calling CTOR for OperatorSetStateFlag");
+  }
+
+  // noncopyable
+  OperatorSetStateFlag(const OperatorSetStateFlag &) = delete;
+  void operator=(const OperatorSetStateFlag &)       = delete;
+
+  void initialize(Parser &p) override;
+
+protected:
+  void initialize_hooks() override;
+  bool exec(const Resources &res) const override;
+
+  bool
+  need_txn_slot() const override
+  {
+    return true;
+  }
+
+private:
+  int      _flag_ix = -1;
+  int      _flag    = false;
+  uint64_t _mask    = 0;
+};
+
+class OperatorSetStateInt8 : public Operator
+{
+public:
+  OperatorSetStateInt8()
+  {
+    static_assert(sizeof(void *) == 8, "State Variables requires a 64-bit 
system.");
+    Dbg(dbg_ctl, "Calling CTOR for OperatorSetStateInt8");
+  }
+
+  // noncopyable
+  OperatorSetStateInt8(const OperatorSetStateInt8 &) = delete;
+  void operator=(const OperatorSetStateInt8 &)       = delete;
+
+  void initialize(Parser &p) override;
+
+protected:
+  void initialize_hooks() override;
+  bool exec(const Resources &res) const override;
+
+  bool
+  need_txn_slot() const override
+  {
+    return true;
+  }
+
+private:
+  int   _byte_ix = -1;
+  Value _value;
+};
+
+class OperatorSetStateInt16 : public Operator
+{
+public:
+  OperatorSetStateInt16()
+  {
+    static_assert(sizeof(void *) == 8, "State Variables requires a 64-bit 
system.");
+    Dbg(dbg_ctl, "Calling CTOR for OperatorSetStateInt16");
+  }
+
+  // noncopyable
+  OperatorSetStateInt16(const OperatorSetStateInt16 &) = delete;
+  void operator=(const OperatorSetStateInt16 &)        = delete;
+
+  void initialize(Parser &p) override;
+
+protected:
+  void initialize_hooks() override;
+  bool exec(const Resources &res) const override;
+
+  bool
+  need_txn_slot() const override
+  {
+    return true;
+  }
+
+private:
+  Value _value;
+};
diff --git a/plugins/header_rewrite/parser.h b/plugins/header_rewrite/parser.h
index d621f07984..17777d3999 100644
--- a/plugins/header_rewrite/parser.h
+++ b/plugins/header_rewrite/parser.h
@@ -89,13 +89,6 @@ public:
     return _val;
   }
 
-  // Return a copy of the string, this implies RVO as well
-  std::string
-  copy_value() const
-  {
-    return _val;
-  }
-
   bool
   mod_exist(const std::string &m) const
   {
diff --git a/plugins/header_rewrite/statement.cc 
b/plugins/header_rewrite/statement.cc
index 92e9fb67b5..f9d9fc59b9 100644
--- a/plugins/header_rewrite/statement.cc
+++ b/plugins/header_rewrite/statement.cc
@@ -73,6 +73,28 @@ Statement::initialize_hooks()
   add_allowed_hook(TS_HTTP_TXN_CLOSE_HOOK);
 }
 
+void
+Statement::acquire_txn_slot()
+{
+  // Don't do anything if we don't need it
+  if (!need_txn_slot() || _txn_slot >= 0) {
+    return;
+  }
+
+  // Only call the index reservation once per plugin load
+  static int txn_slot_index = []() -> int {
+    int index = -1;
+
+    if (TS_ERROR == TSUserArgIndexReserve(TS_USER_ARGS_TXN, PLUGIN_NAME, "HRW 
txn variables", &index)) {
+      TSError("[%s] failed to reserve user arg index", PLUGIN_NAME);
+      return -1; // Fallback value
+    }
+    return index;
+  }();
+
+  _txn_slot = txn_slot_index;
+}
+
 // Parse NextHop qualifiers
 NextHopQualifiers
 Statement::parse_next_hop_qualifier(const std::string &q) const
diff --git a/plugins/header_rewrite/statement.h 
b/plugins/header_rewrite/statement.h
index 9055558531..92954749af 100644
--- a/plugins/header_rewrite/statement.h
+++ b/plugins/header_rewrite/statement.h
@@ -32,6 +32,22 @@
 #include "parser.h"
 #include "lulu.h"
 
+namespace header_rewrite_ns
+{
+constexpr int NUM_STATE_FLAGS = 16;
+constexpr int NUM_STATE_INT8S = 4;
+
+constexpr uint64_t STATE_INT8_MASKS[NUM_STATE_INT8S] = {
+  // These would change if the number of flag bits changes
+  0x0000000000FF0000ULL, // Bits 16-23
+  0x00000000FF000000ULL, // Bits 24-31
+  0x000000FF00000000ULL, // Bits 32-39
+  0x0000FF0000000000ULL, // Bits 40-47
+};
+
+constexpr uint64_t STATE_INT16_MASK = 0xFFFF000000000000ULL; // Bits 48-63
+} // namespace header_rewrite_ns
+
 // URL data (both client and server)
 enum UrlQualifiers {
   URL_QUAL_NONE,
@@ -138,6 +154,8 @@ public:
   {
     TSReleaseAssert(_initialized == false);
     initialize_hooks();
+    acquire_txn_slot();
+
     _initialized = true;
   }
 
@@ -160,11 +178,20 @@ protected:
     _rsrc = static_cast<ResourceIDs>(_rsrc | ids);
   }
 
-  Statement *_next = nullptr; // Linked list
+  virtual bool
+  need_txn_slot() const
+  {
+    return false;
+  }
+
+  Statement *_next     = nullptr; // Linked list
+  int        _txn_slot = -1;
 
 private:
-  ResourceIDs               _rsrc        = RSRC_NONE;
-  bool                      _initialized = false;
-  TSHttpHookID              _hook        = TS_HTTP_READ_RESPONSE_HDR_HOOK;
+  void acquire_txn_slot();
+
+  ResourceIDs               _rsrc = RSRC_NONE;
+  TSHttpHookID              _hook = TS_HTTP_READ_RESPONSE_HDR_HOOK;
   std::vector<TSHttpHookID> _allowed_hooks;
+  bool                      _initialized = false;
 };
diff --git a/plugins/header_rewrite/value.h b/plugins/header_rewrite/value.h
index df11e0c761..d27d3f8be3 100644
--- a/plugins/header_rewrite/value.h
+++ b/plugins/header_rewrite/value.h
@@ -91,6 +91,12 @@ public:
     return _value.empty();
   }
 
+  bool
+  has_conds() const
+  {
+    return !_cond_vals.empty();
+  }
+
 private:
   int                      _int_value   = 0;
   double                   _float_value = 0.0;

Reply via email to