Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Format option for buffer writers and string builders #27572

Merged
merged 10 commits into from
Jul 4, 2023
2 changes: 2 additions & 0 deletions src/lib/support/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ static_library("support") {
"SetupDiscriminator.h",
"SortUtils.h",
"StateMachine.h",
"StringBuilder.cpp",
"StringBuilder.h",
"StringSplitter.h",
"ThreadOperationalDataset.cpp",
"ThreadOperationalDataset.h",
Expand Down
6 changes: 1 addition & 5 deletions src/lib/support/BufferWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,7 @@ namespace Encoding {
BufferWriter & BufferWriter::Put(const char * s)
{
static_assert(CHAR_BIT == 8, "We're assuming char and uint8_t are the same size");
while (*s != 0)
{
Put(static_cast<uint8_t>(*s++));
}
return *this;
return Put(s, strlen(s));
}

BufferWriter & BufferWriter::Put(const void * buf, size_t len)
Expand Down
63 changes: 63 additions & 0 deletions src/lib/support/StringBuilder.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
*
* Copyright (c) 2023 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "StringBuilder.h"

namespace chip {

StringBuilderBase & StringBuilderBase::AddFormat(const char * format, ...)
{
va_list args;
va_start(args, format);

char * output = nullptr;
if (mWriter.Available() > 0)
{
output = reinterpret_cast<char *>(mWriter.Buffer() + mWriter.Needed());
}

// the + 1 size here because StringBuilder reserves one byte for final null terminator
int needed = vsnprintf(output, mWriter.Available() + 1, format, args);

// on invalid formats, printf-style methods return negative numbers
if (needed > 0)
{
mWriter.Skip(static_cast<size_t>(needed));
}

va_end(args);
NullTerminate();
return *this;
}

StringBuilderBase & StringBuilderBase::AddMarkerIfOverflow()
{
if (mWriter.Fit())
{
return *this;
}

for (unsigned i = 0; i < 3; i++)
{
if (mWriter.Size() >= i)
andy31415 marked this conversation as resolved.
Show resolved Hide resolved
{
mWriter.Buffer()[mWriter.Size() - i - 1] = '.';
}
}
return *this;
}
} // namespace chip
7 changes: 7 additions & 0 deletions src/lib/support/StringBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ class StringBuilderBase
/// Was nothing written yet?
bool Empty() const { return mWriter.Needed() == 0; }

/// Write a formatted string to the stringbuilder
StringBuilderBase & AddFormat(const char * format, ...) ENFORCE_FORMAT(2, 3);

/// For strings we often want to know when they were truncated. If the underlying writer did
/// not fit, this replaces the last 3 characters with "."
StringBuilderBase & AddMarkerIfOverflow();

/// access the underlying value
const char * c_str() const { return reinterpret_cast<const char *>(mWriter.Buffer()); }

Expand Down
151 changes: 147 additions & 4 deletions src/lib/support/tests/TestStringBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,154 @@ void TestOverflow(nlTestSuite * inSuite, void * inContext)
}
}

void TestFormat(nlTestSuite * inSuite, void * inContext)
{
{
StringBuilder<100> builder;

builder.AddFormat("Test: %d Hello %s\n", 123, "world");

NL_TEST_ASSERT(inSuite, builder.Fit());
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "Test: 123 Hello world\n") == 0);
}

{
StringBuilder<100> builder;

builder.AddFormat("Align: %-5s", "abc");

NL_TEST_ASSERT(inSuite, builder.Fit());
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "Align: abc ") == 0);
}

{
StringBuilder<100> builder;

builder.AddFormat("Multi: %d", 1234);
builder.AddFormat(", then 0x%04X", 0xab);

NL_TEST_ASSERT(inSuite, builder.Fit());
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "Multi: 1234, then 0x00AB") == 0);
}
}

void TestFormatOverflow(nlTestSuite * inSuite, void * inContext)
{
{
StringBuilder<13> builder;

builder.AddFormat("Test: %d Hello %s\n", 123, "world");

NL_TEST_ASSERT(inSuite, !builder.Fit());
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "Test: 123 He") == 0);
}

{
StringBuilder<11> builder;

builder.AddFormat("%d %d %d %d %d", 1, 2, 3, 4, 1234);

NL_TEST_ASSERT(inSuite, !builder.Fit());
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "1 2 3 4 12") == 0);

builder.AddMarkerIfOverflow();
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "1 2 3 4...") == 0);
}

{
StringBuilder<11> builder;

builder.AddFormat("%d", 1234);
NL_TEST_ASSERT(inSuite, builder.Fit());
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "1234") == 0);

builder.AddFormat("%s", "abc");
NL_TEST_ASSERT(inSuite, builder.Fit());
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "1234abc") == 0);

builder.AddMarkerIfOverflow(); // no overflow
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "1234abc") == 0);

builder.AddFormat("%08x", 0x123456);
NL_TEST_ASSERT(inSuite, !builder.Fit());
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "1234abc001") == 0);

builder.AddMarkerIfOverflow();
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "1234abc...") == 0);
}
}

void TestOverflowMarker(nlTestSuite * inSuite, void * inContext)
{
{
StringBuilder<1> builder; // useless builder, but ok

builder.Add("abc123");

NL_TEST_ASSERT(inSuite, !builder.Fit());
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "") == 0);

builder.AddMarkerIfOverflow();
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "") == 0);
}

{
StringBuilder<2> builder;

builder.Add("abc123");

NL_TEST_ASSERT(inSuite, !builder.Fit());
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "a") == 0);

builder.AddMarkerIfOverflow();
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), ".") == 0);
}

{
StringBuilder<3> builder;

builder.Add("abc123");

NL_TEST_ASSERT(inSuite, !builder.Fit());
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "ab") == 0);

builder.AddMarkerIfOverflow();
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "..") == 0);
}

{
StringBuilder<4> builder;

builder.Add("abc123");

NL_TEST_ASSERT(inSuite, !builder.Fit());
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "abc") == 0);

builder.AddMarkerIfOverflow();
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "...") == 0);
}

{
StringBuilder<5> builder;

builder.Add("abc123");

NL_TEST_ASSERT(inSuite, !builder.Fit());
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "abc1") == 0);

builder.AddMarkerIfOverflow();
NL_TEST_ASSERT(inSuite, strcmp(builder.c_str(), "a...") == 0);
}
}

const nlTest sTests[] = {
NL_TEST_DEF("TestStringBuilder", TestStringBuilder), //
NL_TEST_DEF("TestIntegerAppend", TestIntegerAppend), //
NL_TEST_DEF("TestOverflow", TestOverflow), //
NL_TEST_SENTINEL() //
NL_TEST_DEF("TestStringBuilder", TestStringBuilder), //
NL_TEST_DEF("TestIntegerAppend", TestIntegerAppend), //
NL_TEST_DEF("TestOverflow", TestOverflow), //
NL_TEST_DEF("TestFormat", TestFormat), //
NL_TEST_DEF("TestFormatOverflow", TestFormatOverflow), //
NL_TEST_DEF("TestOverflowMarker", TestOverflowMarker), //
NL_TEST_SENTINEL() //
};

} // namespace
Expand Down