summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcp/tests/opaque_data_tuple_unittest.cc')
-rw-r--r--src/lib/dhcp/tests/opaque_data_tuple_unittest.cc523
1 files changed, 523 insertions, 0 deletions
diff --git a/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc b/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc
new file mode 100644
index 0000000..974a0bf
--- /dev/null
+++ b/src/lib/dhcp/tests/opaque_data_tuple_unittest.cc
@@ -0,0 +1,523 @@
+// Copyright (C) 2014-2021 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/.
+
+#include <config.h>
+
+#include <dhcp/opaque_data_tuple.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <sstream>
+#include <vector>
+
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+struct OpaqueDataTupleLenientParsing : ::testing::Test {
+ void SetUp() final override {
+ // Retain the current setting for future restoration.
+ previous_ = Option::lenient_parsing_;
+
+ // Enable lenient parsing.
+ Option::lenient_parsing_ = true;
+ }
+
+ void TearDown() final override {
+ // Restore.
+ Option::lenient_parsing_ = previous_;
+ }
+
+ bool previous_;
+};
+
+// This test checks that when the default constructor is called, the data buffer
+// is empty.
+TEST(OpaqueDataTuple, constructor) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // There should be no data in the tuple.
+ EXPECT_EQ(0, tuple.getLength());
+ EXPECT_TRUE(tuple.getData().empty());
+ EXPECT_TRUE(tuple.getText().empty());
+}
+
+// Test that the constructor which takes the buffer as argument parses the
+// wire data.
+TEST(OpaqueDataTuple, constructorParse1Byte) {
+ OpaqueDataTuple::Buffer wire_data = {
+ 0x0B, // Length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64 // world
+ };
+
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE, wire_data.begin(),
+ wire_data.end());
+
+ EXPECT_EQ(11, tuple.getLength());
+ EXPECT_EQ("Hello world", tuple.getText());
+}
+
+// Test that the constructor which takes the buffer as argument parses the
+// wire data.
+TEST(OpaqueDataTuple, constructorParse2Bytes) {
+ OpaqueDataTuple::Buffer wire_data = {
+ 0x00, 0x0B, // Length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64 // world
+ };
+
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES, wire_data.begin(),
+ wire_data.end());
+
+ EXPECT_EQ(11, tuple.getLength());
+ EXPECT_EQ("Hello world", tuple.getText());
+}
+
+
+// This test checks that it is possible to set the tuple data using raw buffer.
+TEST(OpaqueDataTuple, assignData) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Initially the tuple buffer should be empty.
+ OpaqueDataTuple::Buffer buf = tuple.getData();
+ ASSERT_TRUE(buf.empty());
+ // Prepare some input data and assign to the tuple.
+ OpaqueDataTuple::Buffer data1 = {
+ 0xCA, 0xFE, 0xBE, 0xEF
+ };
+ tuple.assign(data1.begin(), data1.size());
+ // Tuple should now hold the data we assigned.
+ ASSERT_EQ(data1.size(), tuple.getLength());
+ buf = tuple.getData();
+ EXPECT_EQ(buf, data1);
+
+ // Prepare the other set of data and assign to the tuple.
+ OpaqueDataTuple::Buffer data2 = {
+ 1, 2, 3, 4, 5, 6
+ };
+ tuple.assign(data2.begin(), data2.size());
+ // The new data should have replaced the old data.
+ ASSERT_EQ(data2.size(), tuple.getLength());
+ buf = tuple.getData();
+ EXPECT_EQ(buf, data2);
+}
+
+// This test checks that it is possible to append the data to the tuple using
+// raw buffer.
+TEST(OpaqueDataTuple, appendData) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Initially the tuple buffer should be empty.
+ OpaqueDataTuple::Buffer buf = tuple.getData();
+ ASSERT_TRUE(buf.empty());
+ // Prepare some input data and append to the empty tuple.
+ OpaqueDataTuple::Buffer data1 = {
+ 0xCA, 0xFE, 0xBE, 0xEF
+ };
+ tuple.append(data1.begin(), data1.size());
+ // The tuple should now hold only the data we appended.
+ ASSERT_EQ(data1.size(), tuple.getLength());
+ buf = tuple.getData();
+ EXPECT_EQ(buf, data1);
+ // Prepare the new set of data and append.
+ OpaqueDataTuple::Buffer data2 = {
+ 1, 2, 3, 4, 5, 6
+ };
+ tuple.append(data2.begin(), data2.size());
+ // We expect that the tuple now has both sets of data we appended. In order
+ // to verify that, we have to concatenate the input data1 and data2.
+ OpaqueDataTuple::Buffer data12(data1.begin(), data1.end());
+ data12.insert(data12.end(), data2.begin(), data2.end());
+ // The size of the tuple should be a sum of data1 and data2 lengths.
+ ASSERT_EQ(data1.size() + data2.size(), tuple.getLength());
+ buf = tuple.getData();
+ EXPECT_EQ(buf, data12);
+}
+
+// This test checks that it is possible to assign the string to the tuple.
+TEST(OpaqueDataTuple, assignString) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Initially, the tuple should be empty.
+ ASSERT_EQ(0, tuple.getLength());
+ // Assign some string data.
+ tuple.assign("Some string");
+ // Verify that the data has been assigned.
+ EXPECT_EQ(11, tuple.getLength());
+ EXPECT_EQ("Some string", tuple.getText());
+ // Assign some other string.
+ tuple.assign("Different string");
+ // The new string should have replaced the old string.
+ EXPECT_EQ(16, tuple.getLength());
+ EXPECT_EQ("Different string", tuple.getText());
+}
+
+// This test checks that it is possible to append the string to the tuple.
+TEST(OpaqueDataTuple, appendString) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Initially the tuple should be empty.
+ ASSERT_EQ(0, tuple.getLength());
+ // Append the string to it.
+ tuple.append("First part");
+ ASSERT_EQ(10, tuple.getLength());
+ ASSERT_EQ("First part", tuple.getText());
+ // Now append the other string.
+ tuple.append(" and second part");
+ EXPECT_EQ(26, tuple.getLength());
+ // The resulting data in the tuple should be a concatenation of both
+ // strings.
+ EXPECT_EQ("First part and second part", tuple.getText());
+}
+
+// This test verifies that equals function correctly checks that the tuple
+// holds a given string but it doesn't hold the other. This test also
+// checks the assignment operator for the tuple.
+TEST(OpaqueDataTuple, equals) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Tuple is supposed to be empty so it is not equal xyz.
+ EXPECT_FALSE(tuple.equals("xyz"));
+ // Assign xyz.
+ EXPECT_NO_THROW(tuple = "xyz");
+ // The tuple should be equal xyz, but not abc.
+ EXPECT_FALSE(tuple.equals("abc"));
+ EXPECT_TRUE(tuple.equals("xyz"));
+ // Assign abc to the tuple.
+ EXPECT_NO_THROW(tuple = "abc");
+ // It should be now opposite.
+ EXPECT_TRUE(tuple.equals("abc"));
+ EXPECT_FALSE(tuple.equals("xyz"));
+}
+
+// This test checks that the conversion of the data in the tuple to the string
+// is performed correctly.
+TEST(OpaqueDataTuple, getText) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Initially the tuple should be empty.
+ ASSERT_TRUE(tuple.getText().empty());
+ // ASCII representation of 'Hello world'.
+ const char as_ascii[] = {
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64 // world
+ };
+ // Assign it to the tuple.
+ tuple.assign(as_ascii, sizeof(as_ascii));
+ // Conversion to string should give as the original text.
+ EXPECT_EQ("Hello world", tuple.getText());
+}
+
+// This test verifies the behavior of (in)equality and assignment operators.
+TEST(OpaqueDataTuple, operators) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Tuple should be empty initially.
+ ASSERT_EQ(0, tuple.getLength());
+ // Check assignment.
+ EXPECT_NO_THROW(tuple = "Hello World");
+ EXPECT_EQ(11, tuple.getLength());
+ EXPECT_TRUE(tuple == "Hello World");
+ EXPECT_TRUE(tuple != "Something else");
+ // Assign something else to make sure it affects the tuple.
+ EXPECT_NO_THROW(tuple = "Something else");
+ EXPECT_EQ(14, tuple.getLength());
+ EXPECT_TRUE(tuple == "Something else");
+ EXPECT_TRUE(tuple != "Hello World");
+}
+
+// This test verifies that the tuple is inserted in the textual format to the
+// output stream.
+TEST(OpaqueDataTuple, operatorOutputStream) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // The tuple should be empty initially.
+ ASSERT_EQ(0, tuple.getLength());
+ // The tuple is empty, so assigning its content to the output stream should
+ // be no-op and result in the same text in the stream.
+ std::ostringstream s;
+ s << "Some text";
+ EXPECT_NO_THROW(s << tuple);
+ EXPECT_EQ("Some text", s.str());
+ // Now, let's assign some text to the tuple and call operator again.
+ // The new text should be added to the stream.
+ EXPECT_NO_THROW(tuple = " and some other text");
+ EXPECT_NO_THROW(s << tuple);
+ EXPECT_EQ(s.str(), "Some text and some other text");
+}
+
+// This test verifies that the value of the tuple can be initialized from the
+// input stream.
+TEST(OpaqueDataTuple, operatorInputStream) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // The tuple should be empty initially.
+ ASSERT_EQ(0, tuple.getLength());
+ // The input stream has some text. This text should be appended to the
+ // tuple.
+ std::istringstream s;
+ s.str("Some text");
+ EXPECT_NO_THROW(s >> tuple);
+ EXPECT_EQ("Some text", tuple.getText());
+ // Now, let's assign some other text to the stream. This new text should be
+ // assigned to the tuple.
+ s.str("And some other");
+ EXPECT_NO_THROW(s >> tuple);
+ EXPECT_EQ("And some other", tuple.getText());
+}
+
+// This test checks that the tuple is correctly encoded in the wire format when
+// the size of the length field is 1 byte.
+TEST(OpaqueDataTuple, pack1Byte) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ // Initially, the tuple should be empty.
+ ASSERT_EQ(0, tuple.getLength());
+ // It turns out that Option 124 can be sent with 0 length Opaque Data
+ // See #2021 for more details
+ OutputBuffer out_buf(10);
+ ASSERT_NO_THROW(tuple.pack(out_buf));
+ ASSERT_EQ(1, out_buf.getLength());
+ const uint8_t* zero_len = static_cast<const uint8_t*>(out_buf.getData());
+ ASSERT_EQ(0, *zero_len);
+ // Reset the output buffer for another test.
+ out_buf.clear();
+ // Set the data for tuple.
+ std::vector<uint8_t> data;
+ for (uint8_t i = 0; i < 100; ++i) {
+ data.push_back(i);
+ }
+ tuple.assign(data.begin(), data.size());
+ // Packing the data should succeed.
+ ASSERT_NO_THROW(tuple.pack(out_buf));
+ // The rendered buffer should be 101 bytes long - 1 byte for length,
+ // 100 bytes for the actual data.
+ ASSERT_EQ(101, out_buf.getLength());
+ // Get the rendered data into the vector for convenience.
+ std::vector<uint8_t>
+ render_data(static_cast<const uint8_t*>(out_buf.getData()),
+ static_cast<const uint8_t*>(out_buf.getData()) + 101);
+ // The first byte is a length byte. It should hold the length of 100.
+ EXPECT_EQ(100, render_data[0]);
+ // Verify that the rendered data is correct.
+ EXPECT_TRUE(std::equal(render_data.begin() + 1, render_data.end(),
+ data.begin()));
+ // Reset the output buffer for another test.
+ out_buf.clear();
+ // Fill in the tuple buffer so as it reaches maximum allowed length. The
+ // maximum length is 255 when the size of the length field is one byte.
+ for (uint8_t i = 100; i < 255; ++i) {
+ data.push_back(i);
+ }
+ ASSERT_EQ(255, data.size());
+ tuple.assign(data.begin(), data.size());
+ // The pack() should be successful again.
+ ASSERT_NO_THROW(tuple.pack(out_buf));
+ // The rendered buffer should be 256 bytes long. The first byte holds the
+ // opaque data length, the remaining bytes hold the actual data.
+ ASSERT_EQ(256, out_buf.getLength());
+ // Check that the data is correct.
+ render_data.assign(static_cast<const uint8_t*>(out_buf.getData()),
+ static_cast<const uint8_t*>(out_buf.getData()) + 256);
+ EXPECT_EQ(255, render_data[0]);
+ EXPECT_TRUE(std::equal(render_data.begin() + 1, render_data.end(),
+ data.begin()));
+ // Clear output buffer for another test.
+ out_buf.clear();
+ // Add one more value to the tuple. Now, the resulting buffer should exceed
+ // the maximum length. An attempt to pack() should fail.
+ data.push_back(255);
+ tuple.assign(data.begin(), data.size());
+ EXPECT_THROW(tuple.pack(out_buf), OpaqueDataTupleError);
+}
+
+// This test checks that the tuple is correctly encoded in the wire format when
+// the size of the length field is 2 bytes.
+TEST(OpaqueDataTuple, pack2Bytes) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Initially, the tuple should be empty.
+ ASSERT_EQ(0, tuple.getLength());
+ // It turns out that Option 124 can be sent with 0 length Opaque Data
+ // See #2021 for more details
+ OutputBuffer out_buf(10);
+ ASSERT_NO_THROW(tuple.pack(out_buf));
+ ASSERT_EQ(2, out_buf.getLength());
+ const uint16_t* zero_len = static_cast<const uint16_t*>(out_buf.getData());
+ ASSERT_EQ(0, *zero_len);
+ // Reset the output buffer for another test.
+ out_buf.clear();
+ // Set the data for tuple.
+ std::vector<uint8_t> data;
+ for (unsigned i = 0; i < 512; ++i) {
+ data.push_back(i & 0xff);
+ }
+ tuple.assign(data.begin(), data.size());
+ // Packing the data should succeed.
+ ASSERT_NO_THROW(tuple.pack(out_buf));
+ // The rendered buffer should be 514 bytes long - 2 bytes for length,
+ // 512 bytes for the actual data.
+ ASSERT_EQ(514, out_buf.getLength());
+ // Get the rendered data into the vector for convenience.
+ std::vector<uint8_t>
+ render_data(static_cast<const uint8_t*>(out_buf.getData()),
+ static_cast<const uint8_t*>(out_buf.getData()) + 514);
+ // The first two bytes hold the length of 512.
+ uint16_t len = (render_data[0] << 8) + render_data[1];
+ EXPECT_EQ(512, len);
+ // Verify that the rendered data is correct.
+ EXPECT_TRUE(std::equal(render_data.begin() + 2, render_data.end(),
+ data.begin()));
+
+ // Without clearing the output buffer, try to do it again. The pack should
+ // append the data to the current buffer.
+ ASSERT_NO_THROW(tuple.pack(out_buf));
+ EXPECT_EQ(1028, out_buf.getLength());
+
+ // Check that we can render the buffer of the maximal allowed size.
+ data.assign(65535, 1);
+ ASSERT_NO_THROW(tuple.assign(data.begin(), data.size()));
+ ASSERT_NO_THROW(tuple.pack(out_buf));
+
+ out_buf.clear();
+
+ // Append one additional byte. The total length of the tuple now exceeds
+ // the maximal value. An attempt to render it should throw an exception.
+ data.assign(1, 1);
+ ASSERT_NO_THROW(tuple.append(data.begin(), data.size()));
+ EXPECT_THROW(tuple.pack(out_buf), OpaqueDataTupleError);
+}
+
+// This test verifies that the tuple is decoded from the wire format.
+TEST(OpaqueDataTuple, unpack1Byte) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ OpaqueDataTuple::Buffer wire_data = {
+ 0x0B, // Length is 11
+ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, // Hello<space>
+ 0x77, 0x6F, 0x72, 0x6C, 0x64 // world
+ };
+
+ ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
+ EXPECT_EQ(11, tuple.getLength());
+ EXPECT_EQ("Hello world", tuple.getText());
+}
+
+// This test verifies that the tuple having a length of 0, is decoded from
+// the wire format.
+TEST(OpaqueDataTuple, unpack1ByteZeroLength) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ EXPECT_NO_THROW(tuple = "Hello world");
+ ASSERT_NE(tuple.getLength(), 0);
+
+ OpaqueDataTuple::Buffer wire_data = {
+ 0
+ };
+ ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
+
+ EXPECT_EQ(0, tuple.getLength());
+}
+
+// This test verifies that the tuple having a length of 0, followed by no
+// data, is decoded from the wire format.
+TEST(OpaqueDataTuple, unpack1ByteZeroLengthNoData) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ OpaqueDataTuple::Buffer wire_data = {0};
+ ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
+}
+
+// This test verifies that the tuple having a length of 0, followed by no
+// data, is decoded from the wire format.
+TEST(OpaqueDataTuple, unpack2ByteZeroLengthNoData) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ OpaqueDataTuple::Buffer wire_data = {0, 0};
+ ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
+}
+
+// This test verifies that exception is thrown if the empty buffer is being
+// parsed.
+TEST(OpaqueDataTuple, unpack1ByteEmptyBuffer) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ OpaqueDataTuple::Buffer wire_data = {};
+ EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()),
+ OpaqueDataTupleError);
+}
+
+// This test verifies that exception is thrown when parsing truncated buffer.
+TEST(OpaqueDataTuple, unpack1ByteTruncatedBuffer) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_1_BYTE);
+ OpaqueDataTuple::Buffer wire_data = {
+ 10, 2, 3
+ };
+ EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()),
+ OpaqueDataTupleError);
+}
+
+// This test verifies that the tuple is decoded from the wire format.
+TEST(OpaqueDataTuple, unpack2Byte) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ OpaqueDataTuple::Buffer wire_data;
+ // Set tuple length to 400 (0x190).
+ wire_data.push_back(1);
+ wire_data.push_back(0x90);
+ // Fill in the buffer with some data.
+ for (int i = 0; i < 400; ++i) {
+ wire_data.push_back(i);
+ }
+ // The unpack should succeed.
+ ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
+ // The decoded length should be 400.
+ ASSERT_EQ(400, tuple.getLength());
+ // And the data should match.
+ EXPECT_TRUE(std::equal(wire_data.begin() + 2, wire_data.end(),
+ tuple.getData().begin()));
+}
+
+// This test verifies that the tuple having a length of 0, is decoded from
+// the wire format.
+TEST(OpaqueDataTuple, unpack2ByteZeroLength) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Set some data for the tuple.
+ EXPECT_NO_THROW(tuple = "Hello world");
+ ASSERT_NE(tuple.getLength(), 0);
+ // The buffer holds just a length field with the value of 0.
+ OpaqueDataTuple::Buffer wire_data = {
+ 0, 0
+ };
+ // The empty tuple should be successfully decoded.
+ ASSERT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
+ // The data should be replaced with an empty buffer.
+ EXPECT_EQ(0, tuple.getLength());
+}
+
+// This test verifies that exception is thrown if the empty buffer is being
+// parsed.
+TEST(OpaqueDataTuple, unpack2ByteEmptyBuffer) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ OpaqueDataTuple::Buffer wire_data = {};
+ // Pass empty buffer (first iterator equal to second iterator).
+ // This should not be accepted.
+ EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()),
+ OpaqueDataTupleError);
+}
+
+// This test verifies that exception is thrown when parsing truncated buffer.
+TEST(OpaqueDataTuple, unpack2ByteTruncatedBuffer) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Specify the data with the length of 10, but limit the buffer size to
+ // 2 bytes.
+ OpaqueDataTuple::Buffer wire_data = {
+ 0, 10, 2, 3
+ };
+ // This should fail because the buffer is truncated.
+ EXPECT_THROW(tuple.unpack(wire_data.begin(), wire_data.end()),
+ OpaqueDataTupleError);
+}
+
+// Test that an exception is not thrown when parsing in lenient mode.
+TEST_F(OpaqueDataTupleLenientParsing, unpack) {
+ OpaqueDataTuple tuple(OpaqueDataTuple::LENGTH_2_BYTES);
+ // Specify the data with the length of 10, but limit the buffer size to 2.
+ OpaqueDataTuple::Buffer wire_data = {
+ 0, 10, 2, 3
+ };
+ EXPECT_NO_THROW(tuple.unpack(wire_data.begin(), wire_data.end()));
+ EXPECT_EQ(tuple.getData(), OpaqueDataTuple::Buffer({2, 3}));
+}
+
+} // anonymous namespace