// This HMAC differs from the RFC 5769 SampleRequest message. This differs // because spec uses 0x20 for the padding where as our implementation uses 0.
constexpr uint8_t kCalculatedHmac1[] = { 0x79, 0x07, 0xc2, 0xd2, // } 0xed, 0xbf, 0xea, 0x48, // } 0x0e, 0x4c, 0x76, 0xd8, // } HMAC-SHA1 fingerprint 0x29, 0x62, 0xd5, 0xc3, // } 0x74, 0x2a, 0xf9, 0xe3 // }
};
// This truncated HMAC differs from kCalculatedHmac1 // above since the sum is computed including header // and the header is different since the message is shorter // than when MESSAGE-INTEGRITY is used.
constexpr uint8_t kCalculatedHmac1_32[] = { 0xda, 0x39, 0xde, 0x5d, // }
};
// Length parameter is changed to 0x1c from 0x3c. // AddMessageIntegrity will add MI information and update the length param // accordingly.
constexpr uint8_t kRfc5769SampleResponseWithoutMI[] = { 0x01, 0x01, 0x00, 0x1c, // Response type and message length 0x21, 0x12, 0xa4, 0x42, // Magic cookie 0xb7, 0xe7, 0xa7, 0x01, // } 0xbc, 0x34, 0xd6, 0x86, // } Transaction ID 0xfa, 0x87, 0xdf, 0xae, // } 0x80, 0x22, 0x00, 0x0b, // SOFTWARE attribute header 0x74, 0x65, 0x73, 0x74, // } 0x20, 0x76, 0x65, 0x63, // } UTF-8 server name 0x74, 0x6f, 0x72, 0x20, // } 0x00, 0x20, 0x00, 0x08, // XOR-MAPPED-ADDRESS attribute header 0x00, 0x01, 0xa1, 0x47, // Address family (IPv4) and xor'd mapped port 0xe1, 0x12, 0xa6, 0x43 // Xor'd mapped IPv4 address
};
// This HMAC differs from the RFC 5769 SampleResponse message. This differs // because spec uses 0x20 for the padding where as our implementation uses 0.
constexpr uint8_t kCalculatedHmac2[] = { 0x5d, 0x6b, 0x58, 0xbe, // } 0xad, 0x94, 0xe0, 0x7e, // } 0xef, 0x0d, 0xfc, 0x12, // } HMAC-SHA1 fingerprint 0x82, 0xa2, 0xbd, 0x08, // } 0x43, 0x14, 0x10, 0x28 // }
};
// This truncated HMAC differs from kCalculatedHmac2 // above since the sum is computed including header // and the header is different since the message is shorter // than when MESSAGE-INTEGRITY is used.
constexpr uint8_t kCalculatedHmac2_32[] = { 0xe7, 0x5c, 0xd3, 0x16, // }
};
// clang-format on
// A transaction ID without the 'magic cookie' portion // pjnat's test programs use this transaction ID a lot.
constexpr auto kTestTransactionId1 = std::to_array<uint8_t>(
{0x29, 0x1f, 0xcd, 0x7c, 0xba, 0x58, 0xab, 0xd7, 0xf2, 0x41, 0x01, 0x00});
// They use this one sometimes too.
constexpr auto kTestTransactionId2 = std::to_array<uint8_t>(
{0xe3, 0xa9, 0x46, 0xe1, 0x7c, 0x00, 0xc2, 0x62, 0x54, 0x08, 0x01, 0x00});
#ifdef WEBRTC_POSIX const in_addr kIPv4TestAddress1 = {0xe64417ac}; #elifdefined WEBRTC_WIN // Windows in_addr has a union with a uchar[] array first. const in_addr kIPv4TestAddress1 = {{{0x0ac, 0x017, 0x044, 0x0e6}}}; #endif
constexpr char kTestUserName1[] = "abcdefgh";
constexpr char kTestUserName2[] = "abc";
constexpr char kTestErrorReason[] = "Unauthorized";
constexpr int kTestErrorClass = 4;
constexpr int kTestErrorNumber = 1;
constexpr int kTestErrorCode = 401;
constexpr int kTestMessagePort1 = 59977;
constexpr int kTestMessagePort2 = 47233;
constexpr int kTestMessagePort3 = 56743;
constexpr int kTestMessagePort4 = 40444;
// Test that the GetStun*Type and IsStun*Type methods work as expected.
TEST_F(StunTest, MessageTypes) {
EXPECT_EQ(STUN_BINDING_RESPONSE,
GetStunSuccessResponseType(STUN_BINDING_REQUEST));
EXPECT_EQ(STUN_BINDING_ERROR_RESPONSE,
GetStunErrorResponseType(STUN_BINDING_REQUEST));
EXPECT_EQ(-1, GetStunSuccessResponseType(STUN_BINDING_INDICATION));
EXPECT_EQ(-1, GetStunSuccessResponseType(STUN_BINDING_RESPONSE));
EXPECT_EQ(-1, GetStunSuccessResponseType(STUN_BINDING_ERROR_RESPONSE));
EXPECT_EQ(-1, GetStunErrorResponseType(STUN_BINDING_INDICATION));
EXPECT_EQ(-1, GetStunErrorResponseType(STUN_BINDING_RESPONSE));
EXPECT_EQ(-1, GetStunErrorResponseType(STUN_BINDING_ERROR_RESPONSE));
int types[] = {STUN_BINDING_REQUEST, STUN_BINDING_INDICATION,
STUN_BINDING_RESPONSE, STUN_BINDING_ERROR_RESPONSE};
for (size_t i = 0; i < std::size(types); ++i) {
EXPECT_EQ(i == 0U, IsStunRequestType(types[i]));
EXPECT_EQ(i == 1U, IsStunIndicationType(types[i]));
EXPECT_EQ(i == 2U, IsStunSuccessResponseType(types[i]));
EXPECT_EQ(i == 3U, IsStunErrorResponseType(types[i]));
EXPECT_EQ(1, types[i] & 0xFEEF);
}
}
// Actual M-I value checked in a later test.
ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != nullptr);
// Fingerprint checked in a later test, but double-check the value here. const StunUInt32Attribute* fingerprint = msg.GetUInt32(STUN_ATTR_FINGERPRINT);
ASSERT_TRUE(fingerprint != nullptr);
EXPECT_EQ(0xe57a3bcf, fingerprint->value());
}
// Actual M-I and fingerprint checked in later tests.
ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != nullptr);
ASSERT_TRUE(msg.GetUInt32(STUN_ATTR_FINGERPRINT) != nullptr);
}
// Read the RFC5389 fields from the RFC5769 sample STUN response for IPv6.
TEST_F(StunTest, ReadRfc5769ResponseMessageIPv6) {
StunMessage msg;
size_t size = ReadStunMessage(&msg, kRfc5769SampleResponseIPv6);
CheckStunHeader(msg, STUN_BINDING_RESPONSE, size);
CheckStunTransactionID(msg, kRfc5769SampleMsgTransactionId); const StunByteStringAttribute* software =
msg.GetByteString(STUN_ATTR_SOFTWARE);
ASSERT_TRUE(software != nullptr);
EXPECT_EQ(kRfc5769SampleMsgServerSoftware, software->string_view());
// Actual M-I and fingerprint checked in later tests.
ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != nullptr);
ASSERT_TRUE(msg.GetUInt32(STUN_ATTR_FINGERPRINT) != nullptr);
}
// Read the RFC5389 fields from the RFC5769 sample STUN response with auth.
TEST_F(StunTest, ReadRfc5769RequestMessageLongTermAuth) {
StunMessage msg;
size_t size = ReadStunMessage(&msg, kRfc5769SampleRequestLongTermAuth);
CheckStunHeader(msg, STUN_BINDING_REQUEST, size);
CheckStunTransactionID(msg, kRfc5769SampleMsgWithAuthTransactionId); const StunByteStringAttribute* username =
msg.GetByteString(STUN_ATTR_USERNAME);
ASSERT_TRUE(username != nullptr);
EXPECT_EQ(kRfc5769SampleMsgWithAuthUsername, username->string_view());
// No fingerprint, actual M-I checked in later tests.
ASSERT_TRUE(msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY) != nullptr);
ASSERT_TRUE(msg.GetUInt32(STUN_ATTR_FINGERPRINT) == nullptr);
}
// The RFC3489 packet in this test is the same as // kStunMessageWithIPv4MappedAddress, but with a different value where the // magic cookie was.
TEST_F(StunTest, ReadLegacyMessage) { auto rfc3489_packet = std::to_array(kStunMessageWithIPv4MappedAddress); // Overwrite the magic cookie here.
memcpy(&rfc3489_packet[4], "ABCD", 4);
// Owner with a different transaction ID.
StunMessage msg2(STUN_INVALID_MESSAGE_TYPE, "ABCDABCDABCD");
StunXorAddressAttribute addr2(STUN_ATTR_XOR_MAPPED_ADDRESS, 20, nullptr);
addr2.SetIP(addr->ipaddr());
addr2.SetPort(addr->port());
addr2.SetOwner(&msg2); // The internal IP address shouldn't change.
ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
ByteBufferWriter correct_buf;
ByteBufferWriter wrong_buf;
EXPECT_TRUE(addr->Write(&correct_buf));
EXPECT_TRUE(addr2.Write(&wrong_buf)); // But when written out, the buffers should look different.
ASSERT_NE(0,
memcmp(correct_buf.Data(), wrong_buf.Data(), wrong_buf.Length())); // And when reading a known good value, the address should be wrong.
ByteBufferReader read_buf(correct_buf);
addr2.Read(&read_buf);
ASSERT_NE(addr->ipaddr(), addr2.ipaddr());
addr2.SetIP(addr->ipaddr());
addr2.SetPort(addr->port()); // Try writing with no owner at all, should fail and write nothing.
addr2.SetOwner(nullptr);
ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
wrong_buf.Clear();
EXPECT_FALSE(addr2.Write(&wrong_buf));
ASSERT_EQ(0U, wrong_buf.Length());
}
TEST_F(StunTest, SetIPv4XorAddressAttributeOwner) { // Unlike the IPv6XorAddressAttributeOwner test, IPv4 XOR address attributes // should _not_ be affected by a change in owner. IPv4 XOR address uses the // magic cookie value which is fixed.
StunMessage msg;
size_t size = ReadStunMessage(&msg, kStunMessageWithIPv4XorMappedAddress);
// Owner with a different transaction ID.
StunMessage msg2(STUN_INVALID_MESSAGE_TYPE, "ABCDABCDABCD");
StunXorAddressAttribute addr2(STUN_ATTR_XOR_MAPPED_ADDRESS, 20, nullptr);
addr2.SetIP(addr->ipaddr());
addr2.SetPort(addr->port());
addr2.SetOwner(&msg2); // The internal IP address shouldn't change.
ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
ByteBufferWriter correct_buf;
ByteBufferWriter wrong_buf;
EXPECT_TRUE(addr->Write(&correct_buf));
EXPECT_TRUE(addr2.Write(&wrong_buf)); // The same address data should be written.
ASSERT_EQ(0,
memcmp(correct_buf.Data(), wrong_buf.Data(), wrong_buf.Length())); // And an attribute should be able to un-XOR an address belonging to a message // with a different transaction ID.
ByteBufferReader read_buf(correct_buf);
EXPECT_TRUE(addr2.Read(&read_buf));
ASSERT_EQ(addr->ipaddr(), addr2.ipaddr());
// However, no owner is still an error, should fail and write nothing.
addr2.SetOwner(nullptr);
ASSERT_EQ(addr2.ipaddr(), addr->ipaddr());
wrong_buf.Clear();
EXPECT_FALSE(addr2.Write(&wrong_buf));
}
// Test that we don't care what order we set the parts of an address
TEST_F(StunTest, CreateAddressInArbitraryOrder) { auto addr = StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS); // Port first
addr->SetPort(kTestMessagePort1);
addr->SetIP(IPAddress(kIPv4TestAddress1));
ASSERT_EQ(kTestMessagePort1, addr->port());
ASSERT_EQ(IPAddress(kIPv4TestAddress1), addr->ipaddr());
auto addr2 = StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS); // IP first
addr2->SetIP(IPAddress(kIPv4TestAddress1));
addr2->SetPort(kTestMessagePort2);
ASSERT_EQ(kTestMessagePort2, addr2->port());
ASSERT_EQ(IPAddress(kIPv4TestAddress1), addr2->ipaddr());
}
// Test that GetErrorCodeValue returns STUN_ERROR_GLOBAL_FAILURE if the message // in question doesn't have an error code attribute, rather than crashing.
TEST_F(StunTest, GetErrorCodeValueWithNoErrorAttribute) {
StunMessage msg;
ReadStunMessage(&msg, kStunMessageWithIPv6MappedAddress);
EXPECT_EQ(STUN_ERROR_GLOBAL_FAILURE, msg.GetErrorCodeValue());
}
// Parsing should have succeeded and there should be a USERNAME attribute const StunByteStringAttribute* username =
msg.GetByteString(STUN_ATTR_USERNAME);
ASSERT_TRUE(username != nullptr);
EXPECT_EQ(kTestUserName2, username->string_view());
}
StunMessage msg(STUN_BINDING_REQUEST, AsStringView(kTestTransactionId2));
CheckStunTransactionID(msg, kTestTransactionId2); auto list = StunAttribute::CreateUnknownAttributes();
list->AddType(0x1U);
list->AddType(0x1000U);
list->AddType(0xAB0CU);
msg.AddAttribute(std::move(list));
CheckStunHeader(msg, STUN_BINDING_REQUEST, (size - 20));
ByteBufferWriter out;
EXPECT_TRUE(msg.Write(&out));
ASSERT_EQ(size, out.Length()); // Check everything up to the padding.
ASSERT_EQ(0,
memcmp(out.Data(), kStunMessageWithUInt16ListAttribute, size - 2));
}
// Test that we fail to read messages with invalid lengths. void CheckFailureToRead(const uint8_t* testcase, size_t length) {
StunMessage msg;
ByteBufferReader buf(std::span(testcase, length));
ASSERT_FALSE(msg.Read(&buf));
}
// Test that we properly fail to read a non-STUN message.
TEST_F(StunTest, FailToReadRtcpPacket) {
CheckFailureToRead(kRtcpPacket, sizeof(kRtcpPacket));
}
// Check our STUN message validation code against the RFC5769 test messages.
TEST_F(StunTest, ValidateMessageIntegrity) { // Try the messages from RFC 5769.
EXPECT_TRUE(StunMessage::ValidateMessageIntegrityForTesting(
kRfc5769SampleMsgPassword, kRfc5769SampleRequest));
EXPECT_FALSE(StunMessage::ValidateMessageIntegrityForTesting( "InvalidPassword", kRfc5769SampleRequest));
// We first need to compute the key for the long-term authentication HMAC.
std::string key;
ComputeStunCredentialHash(kRfc5769SampleMsgWithAuthUsername,
kRfc5769SampleMsgWithAuthRealm,
kRfc5769SampleMsgWithAuthPassword, &key);
EXPECT_TRUE(StunMessage::ValidateMessageIntegrityForTesting(
key, kRfc5769SampleRequestLongTermAuth));
EXPECT_FALSE(StunMessage::ValidateMessageIntegrityForTesting( "InvalidPassword", kRfc5769SampleRequestLongTermAuth));
// Again, but with the lengths matching what is claimed in the headers. auto GetSubspan = [](std::span<const uint8_t> view) {
size_t claimed_len = kStunHeaderSize + GetBE16(view.subspan(2, 2)); return view.subspan(0, std::min(claimed_len, view.size()));
};
EXPECT_FALSE(StunMessage::ValidateMessageIntegrityForTesting(
kRfc5769SampleMsgPassword, GetSubspan(kZeroLenView)));
EXPECT_FALSE(StunMessage::ValidateMessageIntegrityForTesting(
kRfc5769SampleMsgPassword, GetSubspan(kExcessLenView)));
EXPECT_FALSE(StunMessage::ValidateMessageIntegrityForTesting(
kRfc5769SampleMsgPassword, GetSubspan(kSmallLenView)));
// Check that a too-short HMAC doesn't cause buffer overflow.
EXPECT_FALSE(StunMessage::ValidateMessageIntegrityForTesting(
kRfc5769SampleMsgPassword, kStunMessageWithBadHmacAtEnd));
// Test that munging a single bit anywhere in the message causes the // message-integrity check to fail, unless it is after the M-I attribute. auto buf = std::to_array(kRfc5769SampleRequest);
for (size_t i = 0; i < buf.size(); ++i) {
buf[i] ^= 0x01;
if (i > 0)
buf[i - 1] ^= 0x01;
EXPECT_EQ(i >= buf.size() - 8,
StunMessage::ValidateMessageIntegrityForTesting(
kRfc5769SampleMsgPassword, buf));
}
}
// Validate that we generate correct MESSAGE-INTEGRITY attributes. // Note the use of IceMessage instead of StunMessage; this is necessary because // the RFC5769 test messages used include attributes not found in basic STUN.
TEST_F(StunTest, AddMessageIntegrity) {
IceMessage msg;
ByteBufferReader buf(kRfc5769SampleRequestWithoutMI);
EXPECT_TRUE(msg.Read(&buf));
EXPECT_TRUE(msg.AddMessageIntegrity(kRfc5769SampleMsgPassword)); const StunByteStringAttribute* mi_attr =
msg.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY);
EXPECT_EQ(20U, mi_attr->length());
EXPECT_EQ(0, memcmp(mi_attr->array_view().data(), kCalculatedHmac1, sizeof(kCalculatedHmac1)));
// Again, but with the lengths matching what is claimed in the headers. auto GetSubspan = [](std::span<const uint8_t> view) {
size_t claimed_len = kStunHeaderSize + GetBE16(view.subspan(2, 2)); return view.subspan(0, std::min(claimed_len, view.size()));
};
EXPECT_FALSE(StunMessage::ValidateMessageIntegrity32ForTesting(
kRfc5769SampleMsgPassword, GetSubspan(kZeroLenView)));
EXPECT_FALSE(StunMessage::ValidateMessageIntegrity32ForTesting(
kRfc5769SampleMsgPassword, GetSubspan(kExcessLenView)));
EXPECT_FALSE(StunMessage::ValidateMessageIntegrity32ForTesting(
kRfc5769SampleMsgPassword, GetSubspan(kSmallLenView)));
// Check that a too-short HMAC doesn't cause buffer overflow.
EXPECT_FALSE(StunMessage::ValidateMessageIntegrity32ForTesting(
kRfc5769SampleMsgPassword, kStunMessageWithBadHmacAtEnd));
// Test that munging a single bit anywhere in the message causes the // message-integrity check to fail, unless it is after the M-I attribute. auto buf = std::to_array(kSampleRequestMI32);
for (size_t i = 0; i < buf.size(); ++i) {
buf[i] ^= 0x01;
if (i > 0)
buf[i - 1] ^= 0x01;
EXPECT_EQ(i >= buf.size() - 8,
StunMessage::ValidateMessageIntegrity32ForTesting(
kRfc5769SampleMsgPassword, buf));
}
}
// Validate that the message validates if both MESSAGE-INTEGRITY-32 and // MESSAGE-INTEGRITY are present in the message. // This is not expected to be used, but is not forbidden.
TEST_F(StunTest, AddMessageIntegrity32AndMessageIntegrity) {
IceMessage msg; auto attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
attr->CopyBytes(std::to_array<uint8_t>({'k', 'e', 's', 'o', '\0'}));
msg.AddAttribute(std::move(attr));
msg.AddMessageIntegrity32("password1");
msg.AddMessageIntegrity("password2");
// Test that munging a single bit anywhere in the message causes the // fingerprint check to fail. auto buf = std::to_array(kRfc5769SampleRequest);
for (size_t i = 0; i < buf.size(); ++i) {
buf[i] ^= 0x01;
if (i > 0)
buf[i - 1] ^= 0x01;
EXPECT_FALSE(StunMessage::ValidateFingerprint(buf));
} // Put them all back to normal and the check should pass again.
buf[buf.size() - 1] ^= 0x01;
EXPECT_TRUE(StunMessage::ValidateFingerprint(buf));
}
// Test that we can remove attribute from a message.
TEST_F(StunTest, RemoveAttribute) {
StunMessage msg;
// Removing something that does exist should return nullptr.
EXPECT_EQ(msg.RemoveAttribute(STUN_ATTR_USERNAME), nullptr);
{ auto attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
attr->CopyBytes(std::to_array<uint8_t>({'k', 'e', 's'}));
msg.AddAttribute(std::move(attr));
}
size_t len = msg.length();
{ auto attr = msg.RemoveAttribute(STUN_ATTR_USERNAME);
ASSERT_NE(attr, nullptr);
EXPECT_EQ(attr->type(), STUN_ATTR_USERNAME);
EXPECT_EQ("kes", static_cast<StunByteStringAttribute*>(attr.get())->string_view());
EXPECT_LT(msg.length(), len);
}
// Now add same attribute type twice.
{ auto attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
attr->CopyBytes(std::to_array<uint8_t>({'k', 'e', 's'}));
msg.AddAttribute(std::move(attr));
}
// Remove should remove the last added occurrence.
{ auto attr = msg.RemoveAttribute(STUN_ATTR_USERNAME);
ASSERT_NE(attr, nullptr);
EXPECT_EQ(attr->type(), STUN_ATTR_USERNAME);
EXPECT_EQ("kenta", static_cast<StunByteStringAttribute*>(attr.get())->string_view());
}
// Remove should remove the last added occurrence.
{ auto attr = msg.RemoveAttribute(STUN_ATTR_USERNAME);
ASSERT_NE(attr, nullptr);
EXPECT_EQ(attr->type(), STUN_ATTR_USERNAME);
EXPECT_EQ("kes", static_cast<StunByteStringAttribute*>(attr.get())->string_view());
}
// Removing something that does exist should return nullptr.
EXPECT_EQ(msg.RemoveAttribute(STUN_ATTR_USERNAME), nullptr);
}
// Test that we can remove attribute from a message.
TEST_F(StunTest, ClearAttributes) {
StunMessage msg;
auto attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
attr->CopyBytes(std::to_array<uint8_t>({'k', 'e', 's'}));
msg.AddAttribute(std::move(attr));
size_t len = msg.length();
// Test CopyStunAttribute
TEST_F(StunTest, CopyAttribute) {
ByteBufferWriter buf;
ByteBufferWriter* buffer_ptrs[] = {&buf, nullptr}; // Test both with and without supplied ByteBufferWriter.
for (auto buffer_ptr : buffer_ptrs) {
{ // Test StunByteStringAttribute. auto attr = StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
attr->CopyBytes(std::to_array<uint8_t>({'k', 'e', 's'}));
// The username attribute has a length which is not a multiple of 4 // so getting a uint32_t vector from it should fail. const StunByteStringAttribute* username =
message.GetByteString(STUN_ATTR_USERNAME);
ASSERT_TRUE(username);
EXPECT_FALSE(username->GetUInt32Vector());
// message-integrity is 20 bytes which makes it usable as a test case. const StunByteStringAttribute* integrity =
message.GetByteString(STUN_ATTR_MESSAGE_INTEGRITY);
ASSERT_TRUE(integrity);
std::optional<std::vector<uint32_t>> integrity_vector =
integrity->GetUInt32Vector();
ASSERT_TRUE(integrity_vector);
EXPECT_EQ(5U, integrity_vector->size()); // Taken from the RFC 5769 sample request.
std::vector<uint32_t> expected_integrity_vector = { 0x9aeaa70c, 0xbfd8cb56, 0x781ef2b5, 0xb2d3f249, 0xc1b571a2};
EXPECT_THAT(*integrity_vector, ElementsAreArray(expected_integrity_vector));
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.