xref: /aosp_15_r20/external/openthread/tests/unit/test_checksum.cpp (revision cfb92d1480a9e65faed56933e9c12405f45898b4)
1 /*
2  *  Copyright (c) 2020, The OpenThread Authors.
3  *  All rights reserved.
4  *
5  *  Redistribution and use in source and binary forms, with or without
6  *  modification, are permitted provided that the following conditions are met:
7  *  1. Redistributions of source code must retain the above copyright
8  *     notice, this list of conditions and the following disclaimer.
9  *  2. Redistributions in binary form must reproduce the above copyright
10  *     notice, this list of conditions and the following disclaimer in the
11  *     documentation and/or other materials provided with the distribution.
12  *  3. Neither the name of the copyright holder nor the
13  *     names of its contributors may be used to endorse or promote products
14  *     derived from this software without specific prior written permission.
15  *
16  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *  POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include "common/encoding.hpp"
30 #include "common/message.hpp"
31 #include "common/numeric_limits.hpp"
32 #include "common/random.hpp"
33 #include "common/string.hpp"
34 #include "instance/instance.hpp"
35 #include "net/checksum.hpp"
36 #include "net/icmp6.hpp"
37 #include "net/ip4_types.hpp"
38 #include "net/udp6.hpp"
39 #include "utils/verhoeff_checksum.hpp"
40 
41 #include "test_platform.h"
42 #include "test_util.hpp"
43 
44 namespace ot {
45 
CalculateChecksum(const void * aBuffer,uint16_t aLength)46 uint16_t CalculateChecksum(const void *aBuffer, uint16_t aLength)
47 {
48     // Calculates checksum over a given buffer data. This implementation
49     // is inspired by the algorithm from RFC-1071.
50 
51     uint32_t       sum   = 0;
52     const uint8_t *bytes = reinterpret_cast<const uint8_t *>(aBuffer);
53 
54     while (aLength >= sizeof(uint16_t))
55     {
56         sum += BigEndian::ReadUint16(bytes);
57         bytes += sizeof(uint16_t);
58         aLength -= sizeof(uint16_t);
59     }
60 
61     if (aLength > 0)
62     {
63         sum += (static_cast<uint32_t>(bytes[0]) << 8);
64     }
65 
66     // Fold 32-bit sum to 16 bits.
67 
68     while (sum >> 16)
69     {
70         sum = (sum & 0xffff) + (sum >> 16);
71     }
72 
73     return static_cast<uint16_t>(sum & 0xffff);
74 }
75 
CalculateChecksum(const Ip6::Address & aSource,const Ip6::Address & aDestination,uint8_t aIpProto,const Message & aMessage)76 uint16_t CalculateChecksum(const Ip6::Address &aSource,
77                            const Ip6::Address &aDestination,
78                            uint8_t             aIpProto,
79                            const Message      &aMessage)
80 {
81     // This method calculates the checksum over an IPv6 message.
82     constexpr uint16_t kMaxPayload = 1024;
83 
84     OT_TOOL_PACKED_BEGIN
85     struct PseudoHeader
86     {
87         Ip6::Address mSource;
88         Ip6::Address mDestination;
89         uint32_t     mPayloadLength;
90         uint32_t     mProtocol;
91     } OT_TOOL_PACKED_END;
92 
93     OT_TOOL_PACKED_BEGIN
94     struct ChecksumData
95     {
96         PseudoHeader mPseudoHeader;
97         uint8_t      mPayload[kMaxPayload];
98     } OT_TOOL_PACKED_END;
99 
100     ChecksumData data;
101     uint16_t     payloadLength;
102 
103     payloadLength = aMessage.GetLength() - aMessage.GetOffset();
104 
105     data.mPseudoHeader.mSource        = aSource;
106     data.mPseudoHeader.mDestination   = aDestination;
107     data.mPseudoHeader.mProtocol      = BigEndian::HostSwap32(aIpProto);
108     data.mPseudoHeader.mPayloadLength = BigEndian::HostSwap32(payloadLength);
109 
110     SuccessOrQuit(aMessage.Read(aMessage.GetOffset(), data.mPayload, payloadLength));
111 
112     return CalculateChecksum(&data, sizeof(PseudoHeader) + payloadLength);
113 }
114 
CalculateChecksum(const Ip4::Address & aSource,const Ip4::Address & aDestination,uint8_t aIpProto,const Message & aMessage)115 uint16_t CalculateChecksum(const Ip4::Address &aSource,
116                            const Ip4::Address &aDestination,
117                            uint8_t             aIpProto,
118                            const Message      &aMessage)
119 {
120     // This method calculates the checksum over an IPv4 message.
121     constexpr uint16_t kMaxPayload = 1024;
122 
123     OT_TOOL_PACKED_BEGIN
124     struct PseudoHeader
125     {
126         Ip4::Address mSource;
127         Ip4::Address mDestination;
128         uint16_t     mPayloadLength;
129         uint16_t     mProtocol;
130     } OT_TOOL_PACKED_END;
131 
132     OT_TOOL_PACKED_BEGIN
133     struct ChecksumData
134     {
135         PseudoHeader mPseudoHeader;
136         uint8_t      mPayload[kMaxPayload];
137     } OT_TOOL_PACKED_END;
138 
139     ChecksumData data;
140     uint16_t     payloadLength;
141 
142     payloadLength = aMessage.GetLength() - aMessage.GetOffset();
143 
144     data.mPseudoHeader.mSource        = aSource;
145     data.mPseudoHeader.mDestination   = aDestination;
146     data.mPseudoHeader.mProtocol      = BigEndian::HostSwap16(aIpProto);
147     data.mPseudoHeader.mPayloadLength = BigEndian::HostSwap16(payloadLength);
148 
149     SuccessOrQuit(aMessage.Read(aMessage.GetOffset(), data.mPayload, payloadLength));
150 
151     return CalculateChecksum(&data, sizeof(PseudoHeader) + payloadLength);
152 }
153 
CorruptMessage(Message & aMessage)154 void CorruptMessage(Message &aMessage)
155 {
156     // Change a random bit in the message.
157 
158     uint16_t byteOffset;
159     uint8_t  bitOffset;
160     uint8_t  byte;
161 
162     byteOffset = Random::NonCrypto::GetUint16InRange(0, aMessage.GetLength());
163 
164     SuccessOrQuit(aMessage.Read(byteOffset, byte));
165 
166     bitOffset = Random::NonCrypto::GetUint8InRange(0, kBitsPerByte);
167 
168     byte ^= (1 << bitOffset);
169 
170     aMessage.Write(byteOffset, byte);
171 }
172 
TestUdpMessageChecksum(void)173 void TestUdpMessageChecksum(void)
174 {
175     constexpr uint16_t kMinSize = sizeof(Ip6::Udp::Header);
176     constexpr uint16_t kMaxSize = kBufferSize * 3 + 24;
177 
178     const char *kSourceAddress = "fd00:1122:3344:5566:7788:99aa:bbcc:ddee";
179     const char *kDestAddress   = "fd01:2345:6789:abcd:ef01:2345:6789:abcd";
180 
181     Instance *instance = static_cast<Instance *>(testInitInstance());
182 
183     VerifyOrQuit(instance != nullptr);
184 
185     for (uint16_t size = kMinSize; size <= kMaxSize; size++)
186     {
187         Message         *message = instance->Get<Ip6::Ip6>().NewMessage(sizeof(Ip6::Udp::Header));
188         Ip6::Udp::Header udpHeader;
189         Ip6::MessageInfo messageInfo;
190 
191         VerifyOrQuit(message != nullptr, "Ip6::NewMesssage() failed");
192         SuccessOrQuit(message->SetLength(size));
193 
194         // Write UDP header with a random payload.
195 
196         Random::NonCrypto::Fill(udpHeader);
197         udpHeader.SetChecksum(0);
198         message->Write(0, udpHeader);
199 
200         if (size > sizeof(udpHeader))
201         {
202             uint8_t  buffer[kMaxSize];
203             uint16_t payloadSize = size - sizeof(udpHeader);
204 
205             Random::NonCrypto::FillBuffer(buffer, payloadSize);
206             message->WriteBytes(sizeof(udpHeader), &buffer[0], payloadSize);
207         }
208 
209         SuccessOrQuit(messageInfo.GetSockAddr().FromString(kSourceAddress));
210         SuccessOrQuit(messageInfo.GetPeerAddr().FromString(kDestAddress));
211 
212         // Verify that the `Checksum::UpdateMessageChecksum` correctly
213         // updates the checksum field in the UDP header on the message.
214 
215         Checksum::UpdateMessageChecksum(*message, messageInfo.GetSockAddr(), messageInfo.GetPeerAddr(), Ip6::kProtoUdp);
216 
217         SuccessOrQuit(message->Read(message->GetOffset(), udpHeader));
218         VerifyOrQuit(udpHeader.GetChecksum() != 0);
219 
220         // Verify that the calculated UDP checksum is valid.
221 
222         VerifyOrQuit(CalculateChecksum(messageInfo.GetSockAddr(), messageInfo.GetPeerAddr(), Ip6::kProtoUdp,
223                                        *message) == 0xffff);
224 
225         // Verify that `Checksum::VerifyMessageChecksum()` accepts the
226         // message and its calculated checksum.
227 
228         SuccessOrQuit(Checksum::VerifyMessageChecksum(*message, messageInfo, Ip6::kProtoUdp));
229 
230         // Corrupt the message and verify that checksum is no longer accepted.
231 
232         CorruptMessage(*message);
233 
234         VerifyOrQuit(Checksum::VerifyMessageChecksum(*message, messageInfo, Ip6::kProtoUdp) != kErrorNone,
235                      "Checksum passed on corrupted message");
236 
237         message->Free();
238     }
239 }
240 
TestIcmp6MessageChecksum(void)241 void TestIcmp6MessageChecksum(void)
242 {
243     constexpr uint16_t kMinSize = sizeof(Ip6::Icmp::Header);
244     constexpr uint16_t kMaxSize = kBufferSize * 3 + 24;
245 
246     const char *kSourceAddress = "fd00:feef:dccd:baab:9889:7667:5444:3223";
247     const char *kDestAddress   = "fd01:abab:beef:cafe:1234:5678:9abc:0";
248 
249     Instance *instance = static_cast<Instance *>(testInitInstance());
250 
251     VerifyOrQuit(instance != nullptr, "Null OpenThread instance\n");
252 
253     for (uint16_t size = kMinSize; size <= kMaxSize; size++)
254     {
255         Message          *message = instance->Get<Ip6::Ip6>().NewMessage(sizeof(Ip6::Icmp::Header));
256         Ip6::Icmp::Header icmp6Header;
257         Ip6::MessageInfo  messageInfo;
258 
259         VerifyOrQuit(message != nullptr, "Ip6::NewMesssage() failed");
260         SuccessOrQuit(message->SetLength(size));
261 
262         // Write ICMP6 header with a random payload.
263 
264         Random::NonCrypto::Fill(icmp6Header);
265         icmp6Header.SetChecksum(0);
266         message->Write(0, icmp6Header);
267 
268         if (size > sizeof(icmp6Header))
269         {
270             uint8_t  buffer[kMaxSize];
271             uint16_t payloadSize = size - sizeof(icmp6Header);
272 
273             Random::NonCrypto::FillBuffer(buffer, payloadSize);
274             message->WriteBytes(sizeof(icmp6Header), &buffer[0], payloadSize);
275         }
276 
277         SuccessOrQuit(messageInfo.GetSockAddr().FromString(kSourceAddress));
278         SuccessOrQuit(messageInfo.GetPeerAddr().FromString(kDestAddress));
279 
280         // Verify that the `Checksum::UpdateMessageChecksum` correctly
281         // updates the checksum field in the ICMP6 header on the message.
282 
283         Checksum::UpdateMessageChecksum(*message, messageInfo.GetSockAddr(), messageInfo.GetPeerAddr(),
284                                         Ip6::kProtoIcmp6);
285 
286         SuccessOrQuit(message->Read(message->GetOffset(), icmp6Header));
287         VerifyOrQuit(icmp6Header.GetChecksum() != 0, "Failed to update checksum");
288 
289         // Verify that the calculated ICMP6 checksum is valid.
290 
291         VerifyOrQuit(CalculateChecksum(messageInfo.GetSockAddr(), messageInfo.GetPeerAddr(), Ip6::kProtoIcmp6,
292                                        *message) == 0xffff);
293 
294         // Verify that `Checksum::VerifyMessageChecksum()` accepts the
295         // message and its calculated checksum.
296 
297         SuccessOrQuit(Checksum::VerifyMessageChecksum(*message, messageInfo, Ip6::kProtoIcmp6));
298 
299         // Corrupt the message and verify that checksum is no longer accepted.
300 
301         CorruptMessage(*message);
302 
303         VerifyOrQuit(Checksum::VerifyMessageChecksum(*message, messageInfo, Ip6::kProtoIcmp6) != kErrorNone,
304                      "Checksum passed on corrupted message");
305 
306         message->Free();
307     }
308 }
309 
TestTcp4MessageChecksum(void)310 void TestTcp4MessageChecksum(void)
311 {
312     constexpr size_t kMinSize = sizeof(Ip4::Tcp::Header);
313     constexpr size_t kMaxSize = kBufferSize * 3 + 24;
314 
315     const char *kSourceAddress = "12.34.56.78";
316     const char *kDestAddress   = "87.65.43.21";
317 
318     Ip4::Address sourceAddress;
319     Ip4::Address destAddress;
320 
321     Instance *instance = static_cast<Instance *>(testInitInstance());
322 
323     VerifyOrQuit(instance != nullptr);
324 
325     SuccessOrQuit(sourceAddress.FromString(kSourceAddress));
326     SuccessOrQuit(destAddress.FromString(kDestAddress));
327 
328     for (uint16_t size = kMinSize; size <= kMaxSize; size++)
329     {
330         Message         *message = instance->Get<Ip6::Ip6>().NewMessage(sizeof(Ip4::Tcp::Header));
331         Ip4::Tcp::Header tcpHeader;
332 
333         VerifyOrQuit(message != nullptr, "Ip6::NewMesssage() failed");
334         SuccessOrQuit(message->SetLength(size));
335 
336         // Write TCP header with a random payload.
337 
338         Random::NonCrypto::Fill(tcpHeader);
339         message->Write(0, tcpHeader);
340 
341         if (size > sizeof(tcpHeader))
342         {
343             uint8_t  buffer[kMaxSize];
344             uint16_t payloadSize = size - sizeof(tcpHeader);
345 
346             Random::NonCrypto::FillBuffer(buffer, payloadSize);
347             message->WriteBytes(sizeof(tcpHeader), &buffer[0], payloadSize);
348         }
349 
350         // Verify that the `Checksum::UpdateMessageChecksum` correctly
351         // updates the checksum field in the UDP header on the message.
352 
353         Checksum::UpdateMessageChecksum(*message, sourceAddress, destAddress, Ip4::kProtoTcp);
354 
355         SuccessOrQuit(message->Read(message->GetOffset(), tcpHeader));
356         VerifyOrQuit(tcpHeader.GetChecksum() != 0);
357 
358         // Verify that the calculated UDP checksum is valid.
359 
360         VerifyOrQuit(CalculateChecksum(sourceAddress, destAddress, Ip4::kProtoTcp, *message) == 0xffff);
361         message->Free();
362     }
363 }
364 
TestUdp4MessageChecksum(void)365 void TestUdp4MessageChecksum(void)
366 {
367     constexpr uint16_t kMinSize = sizeof(Ip4::Udp::Header);
368     constexpr uint16_t kMaxSize = kBufferSize * 3 + 24;
369 
370     const char *kSourceAddress = "12.34.56.78";
371     const char *kDestAddress   = "87.65.43.21";
372 
373     Ip4::Address sourceAddress;
374     Ip4::Address destAddress;
375 
376     Instance *instance = static_cast<Instance *>(testInitInstance());
377 
378     SuccessOrQuit(sourceAddress.FromString(kSourceAddress));
379     SuccessOrQuit(destAddress.FromString(kDestAddress));
380 
381     VerifyOrQuit(instance != nullptr);
382 
383     for (uint16_t size = kMinSize; size <= kMaxSize; size++)
384     {
385         Message         *message = instance->Get<Ip6::Ip6>().NewMessage(sizeof(Ip4::Udp::Header));
386         Ip4::Udp::Header udpHeader;
387 
388         VerifyOrQuit(message != nullptr, "Ip6::NewMesssage() failed");
389         SuccessOrQuit(message->SetLength(size));
390 
391         // Write UDP header with a random payload.
392 
393         Random::NonCrypto::Fill(udpHeader);
394         udpHeader.SetChecksum(0);
395         message->Write(0, udpHeader);
396 
397         if (size > sizeof(udpHeader))
398         {
399             uint8_t  buffer[kMaxSize];
400             uint16_t payloadSize = size - sizeof(udpHeader);
401 
402             Random::NonCrypto::FillBuffer(buffer, payloadSize);
403             message->WriteBytes(sizeof(udpHeader), &buffer[0], payloadSize);
404         }
405 
406         // Verify that the `Checksum::UpdateMessageChecksum` correctly
407         // updates the checksum field in the UDP header on the message.
408 
409         Checksum::UpdateMessageChecksum(*message, sourceAddress, destAddress, Ip4::kProtoUdp);
410 
411         SuccessOrQuit(message->Read(message->GetOffset(), udpHeader));
412         VerifyOrQuit(udpHeader.GetChecksum() != 0);
413 
414         // Verify that the calculated UDP checksum is valid.
415 
416         VerifyOrQuit(CalculateChecksum(sourceAddress, destAddress, Ip4::kProtoUdp, *message) == 0xffff);
417         message->Free();
418     }
419 }
420 
TestIcmp4MessageChecksum(void)421 void TestIcmp4MessageChecksum(void)
422 {
423     // A captured ICMP echo request (ping) message. Checksum field is set to zero.
424     const uint8_t kExampleIcmpMessage[]      = "\x08\x00\x00\x00\x67\x2e\x00\x00\x62\xaf\xf1\x61\x00\x04\xfc\x24"
425                                                "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17"
426                                                "\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27"
427                                                "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37";
428     uint16_t      kChecksumForExampleMessage = 0x5594;
429     Instance     *instance                   = static_cast<Instance *>(testInitInstance());
430     Message      *message                    = instance->Get<Ip6::Ip6>().NewMessage(sizeof(kExampleIcmpMessage));
431 
432     Ip4::Address source;
433     Ip4::Address dest;
434 
435     uint8_t           mPayload[sizeof(kExampleIcmpMessage)];
436     Ip4::Icmp::Header icmpHeader;
437 
438     SuccessOrQuit(message->AppendBytes(kExampleIcmpMessage, sizeof(kExampleIcmpMessage)));
439 
440     // Random IPv4 address, ICMP message checksum does not include a presudo header like TCP and UDP.
441     source.mFields.m32 = 0x12345678;
442     dest.mFields.m32   = 0x87654321;
443 
444     Checksum::UpdateMessageChecksum(*message, source, dest, Ip4::kProtoIcmp);
445 
446     SuccessOrQuit(message->Read(0, icmpHeader));
447     VerifyOrQuit(icmpHeader.GetChecksum() == kChecksumForExampleMessage);
448 
449     SuccessOrQuit(message->Read(message->GetOffset(), mPayload, sizeof(mPayload)));
450     VerifyOrQuit(CalculateChecksum(mPayload, sizeof(mPayload)) == 0xffff);
451 }
452 
453 class ChecksumTester
454 {
455 public:
TestExampleVector(void)456     static void TestExampleVector(void)
457     {
458         // Example from RFC 1071
459         const uint8_t  kTestVector[]       = {0x00, 0x01, 0xf2, 0x03, 0xf4, 0xf5, 0xf6, 0xf7};
460         const uint16_t kTestVectorChecksum = 0xddf2;
461 
462         Checksum checksum;
463 
464         VerifyOrQuit(checksum.GetValue() == 0, "Incorrect initial checksum value");
465 
466         checksum.AddData(kTestVector, sizeof(kTestVector));
467         VerifyOrQuit(checksum.GetValue() == kTestVectorChecksum);
468         VerifyOrQuit(checksum.GetValue() == CalculateChecksum(kTestVector, sizeof(kTestVector)), );
469     }
470 };
471 
472 #if OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE
473 
TestVerhoeffChecksum(void)474 void TestVerhoeffChecksum(void)
475 {
476     static constexpr uint16_t kMaxStringSize = 50;
477 
478     const char *kExamples[] = {"307318421", "487300178", "123455672", "0",   "15",
479                                "999999994", "000000001", "100000000", "2363"};
480 
481     const char *kInvalidFormats[] = {
482         "307 318421",
483         "307318421 ",
484         " 307318421",
485         "ABCDE",
486     };
487 
488     char string[kMaxStringSize];
489     char checksum;
490     char expectedChecksum;
491 
492     printf("\nVerhoeffChecksum\n");
493 
494     for (const char *example : kExamples)
495     {
496         uint16_t length = StringLength(example, kMaxStringSize - 1);
497 
498         memcpy(string, example, length + 1);
499 
500         printf("- \"%s\"\n", string);
501 
502         SuccessOrQuit(Utils::VerhoeffChecksum::Validate(string));
503 
504         expectedChecksum = string[length - 1];
505 
506         string[length - 1] = (expectedChecksum == '0') ? '9' : (expectedChecksum - 1);
507         VerifyOrQuit(Utils::VerhoeffChecksum::Validate(string) == kErrorFailed);
508 
509         string[length - 1] = '\0';
510         SuccessOrQuit(Utils::VerhoeffChecksum::Calculate(string, checksum));
511         VerifyOrQuit(checksum == expectedChecksum);
512 
513         string[length - 1] = expectedChecksum == '0' ? '9' : (expectedChecksum - 1);
514     }
515 
516     printf("\nInvalid format:\n");
517 
518     for (const char *example : kInvalidFormats)
519     {
520         printf("- \"%s\"\n", example);
521         VerifyOrQuit(Utils::VerhoeffChecksum::Validate(example) == kErrorInvalidArgs);
522         VerifyOrQuit(Utils::VerhoeffChecksum::Calculate(example, checksum) == kErrorInvalidArgs);
523     }
524 }
525 
526 #endif // OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE
527 
528 } // namespace ot
529 
main(void)530 int main(void)
531 {
532     ot::ChecksumTester::TestExampleVector();
533     ot::TestUdpMessageChecksum();
534     ot::TestIcmp6MessageChecksum();
535     ot::TestTcp4MessageChecksum();
536     ot::TestUdp4MessageChecksum();
537     ot::TestIcmp4MessageChecksum();
538 #if OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE
539     ot::TestVerhoeffChecksum();
540 #endif
541 
542     printf("All tests passed\n");
543     return 0;
544 }
545