diff --git a/Package.swift b/Package.swift index 9712deb4..03fc2193 100644 --- a/Package.swift +++ b/Package.swift @@ -521,6 +521,7 @@ let package = Package( "internal/platform/input_stream_test.cc", "internal/platform/single_thread_executor_test.cc", "internal/platform/scheduled_executor_test.cc", + "internal/platform/stream_writer_test.cc", "internal/platform/count_down_latch_test.cc", "internal/platform/pipe_test.cc", "internal/platform/timer_impl_test.cc", diff --git a/internal/platform/BUILD b/internal/platform/BUILD index 1c53d60d..626649c4 100644 --- a/internal/platform/BUILD +++ b/internal/platform/BUILD @@ -84,10 +84,12 @@ cc_library( srcs = [ "base_input_stream.cc", "byte_utils.cc", + "stream_writer.cc", ], hdrs = [ "base_input_stream.h", "byte_utils.h", + "stream_writer.h", ], visibility = [ "//connections:__subpackages__", @@ -399,6 +401,7 @@ cc_test( srcs = [ "base_input_stream_test.cc", "byte_utils_test.cc", + "stream_writer_test.cc", ], deps = [ ":base", diff --git a/internal/platform/base_input_stream.cc b/internal/platform/base_input_stream.cc index 7392ce9b..3335ea1b 100644 --- a/internal/platform/base_input_stream.cc +++ b/internal/platform/base_input_stream.cc @@ -39,7 +39,7 @@ ExceptionOr BaseInputStream::Read(std::int64_t size) { } std::optional BaseInputStream::ReadBits(int bits) { - if (bits > 8) { + if (bits > 8 || bits <= 0) { return std::nullopt; } if (bits_unused_ == 0) { diff --git a/internal/platform/stream_writer.cc b/internal/platform/stream_writer.cc new file mode 100644 index 00000000..a6971fc8 --- /dev/null +++ b/internal/platform/stream_writer.cc @@ -0,0 +1,126 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// https://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 "internal/platform/stream_writer.h" + +#include +#include + +#include "internal/platform/exception.h" + +namespace nearby { + +std::string StreamWriter::GetData() { + if (bits_used_ != 0) { + return buffer_ + std::string(1, bits_buffer_); + } + return buffer_; +} + +Exception StreamWriter::WriteBits(uint8_t value, int bits) { + if (bits >= 8 || bits <= 0 || 8 - bits < bits_used_) { + return Exception{Exception::kInvalidProtocolBuffer}; + } + + bits_buffer_ |= (value & ((1 << bits) - 1)) << (8 - bits - bits_used_); + bits_used_ += bits; + + if (bits_used_ == 8) { + buffer_.push_back(bits_buffer_); + bits_used_ = 0; + bits_buffer_ = 0; + } + + return Exception{Exception::kSuccess}; +} + +Exception StreamWriter::WriteUint8(uint8_t value) { + if (bits_used_ != 0) { + return Exception{Exception::kInvalidProtocolBuffer}; + } + + buffer_.push_back(value); + return Exception{Exception::kSuccess}; +} + +Exception StreamWriter::WriteInt8(int8_t value) { + return WriteUint8(static_cast(value)); +} + +Exception StreamWriter::WriteUint16(uint16_t value) { + if (bits_used_ != 0) { + return Exception{Exception::kInvalidProtocolBuffer}; + } + + char bytes[2]; + bytes[0] = (value >> 8) & 0xff; + bytes[1] = value & 0xff; + buffer_.append(bytes, 2); + return Exception{Exception::kSuccess}; +} + +Exception StreamWriter::WriteInt16(int16_t value) { + return WriteUint16(static_cast(value)); +} + +Exception StreamWriter::WriteUint32(uint32_t value) { + if (bits_used_ != 0) { + return Exception{Exception::kInvalidProtocolBuffer}; + } + + char bytes[4]; + bytes[0] = (value >> 24) & 0xff; + bytes[1] = (value >> 16) & 0xff; + bytes[2] = (value >> 8) & 0xff; + bytes[3] = value & 0xff; + buffer_.append(bytes, 4); + return Exception{Exception::kSuccess}; +} + +Exception StreamWriter::WriteInt32(int32_t value) { + return WriteUint32(static_cast(value)); +} + +Exception StreamWriter::WriteUint64(uint64_t value) { + if (bits_used_ != 0) { + return Exception{Exception::kInvalidProtocolBuffer}; + } + + char bytes[8]; + bytes[0] = (value >> 56) & 0xff; + bytes[1] = (value >> 48) & 0xff; + bytes[2] = (value >> 40) & 0xff; + bytes[3] = (value >> 32) & 0xff; + bytes[4] = (value >> 24) & 0xff; + bytes[5] = (value >> 16) & 0xff; + bytes[6] = (value >> 8) & 0xff; + bytes[7] = value & 0xff; + buffer_.append(bytes, 8); + return Exception{Exception::kSuccess}; +} + +Exception StreamWriter::WriteInt64(int64_t value) { + return WriteUint64(static_cast(value)); +} + +Exception StreamWriter::WriteBytes(const std::string& data) { + if (bits_used_ != 0) { + return Exception{Exception::kInvalidProtocolBuffer}; + } + + buffer_.append(data); + return Exception{Exception::kSuccess}; +} + +} // namespace nearby diff --git a/internal/platform/stream_writer.h b/internal/platform/stream_writer.h new file mode 100644 index 00000000..1f94a405 --- /dev/null +++ b/internal/platform/stream_writer.h @@ -0,0 +1,55 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// https://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. + +#ifndef THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_STREAM_WRITER_H_ +#define THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_STREAM_WRITER_H_ + +#include +#include + +#include "internal/platform/exception.h" + +namespace nearby { + +// A base {@link StreamWriter } for writing the contents of a byte array. +// All internal data is stored in network order. +class StreamWriter { + public: + StreamWriter() = default; + ~StreamWriter() = default; + + std::string GetData(); + + // Writes less than 8 bits to the stream. The written bits cannot across a + // byte boundary. + Exception WriteBits(uint8_t value, int bits); + Exception WriteUint8(uint8_t value); + Exception WriteInt8(int8_t value); + Exception WriteUint16(uint16_t value); + Exception WriteInt16(int16_t value); + Exception WriteUint32(uint32_t value); + Exception WriteInt32(int32_t value); + Exception WriteUint64(uint64_t value); + Exception WriteInt64(int64_t value); + Exception WriteBytes(const std::string& data); + + private: + uint8_t bits_used_{0}; + uint8_t bits_buffer_{0}; + std::string buffer_; +}; + +} // namespace nearby + +#endif // THIRD_PARTY_NEARBY_INTERNAL_PLATFORM_STREAM_WRITER_H_ diff --git a/internal/platform/stream_writer_test.cc b/internal/platform/stream_writer_test.cc new file mode 100644 index 00000000..d85e877e --- /dev/null +++ b/internal/platform/stream_writer_test.cc @@ -0,0 +1,87 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// https://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 "internal/platform/stream_writer.h" + +#include + +#include "gtest/gtest.h" +#include "internal/platform/exception.h" + +namespace nearby { +namespace { + +TEST(StreamWriterTest, WriteBits) { + StreamWriter writer; + EXPECT_EQ(writer.WriteBits(0x01, 1), Exception{Exception::kSuccess}); + EXPECT_EQ(writer.WriteBits(0x01, 1), Exception{Exception::kSuccess}); + EXPECT_EQ(writer.WriteBits(0x01, 1), Exception{Exception::kSuccess}); + EXPECT_EQ(writer.WriteBits(0x00, 1), Exception{Exception::kSuccess}); + EXPECT_EQ(writer.GetData(), std::string("\xe0")); + EXPECT_EQ(writer.WriteBits(0x01, 1), Exception{Exception::kSuccess}); + EXPECT_EQ(writer.WriteBits(0x01, 1), Exception{Exception::kSuccess}); + EXPECT_EQ(writer.WriteBits(0x01, 1), Exception{Exception::kSuccess}); + EXPECT_EQ(writer.WriteBits(0x01, 1), Exception{Exception::kSuccess}); + EXPECT_EQ(writer.GetData(), std::string("\xef")); +} + +TEST(StreamWriterTest, WriteInvalidBits) { + StreamWriter writer; + EXPECT_EQ(writer.WriteBits(0x01, 1), Exception{Exception::kSuccess}); + EXPECT_EQ(writer.WriteBits(0x01, 9), + Exception{Exception::kInvalidProtocolBuffer}); + EXPECT_EQ(writer.WriteBits(0x03, 4), Exception{Exception::kSuccess}); + EXPECT_EQ(writer.WriteBits(0x01, 5), + Exception{Exception::kInvalidProtocolBuffer}); +} + +TEST(StreamWriterTest, WriteIntegerValues) { + StreamWriter writer; + EXPECT_EQ(writer.WriteUint8(0x01), Exception{Exception::kSuccess}); + EXPECT_EQ(writer.WriteInt8(0xf1), Exception{Exception::kSuccess}); + EXPECT_EQ(writer.WriteUint16(0x0102), Exception{Exception::kSuccess}); + EXPECT_EQ(writer.WriteInt16(0xf102), Exception{Exception::kSuccess}); + EXPECT_EQ(writer.GetData(), "\x01\xF1\x01\x02\xF1\x02"); + EXPECT_EQ(writer.WriteUint32(0x01020304), Exception{Exception::kSuccess}); + EXPECT_EQ(writer.WriteInt32(0xf1020304), Exception{Exception::kSuccess}); + writer = {}; + EXPECT_EQ(writer.WriteUint64(0x0102030405060708), + Exception{Exception::kSuccess}); + EXPECT_EQ(writer.WriteInt64(0xf102030405060708), + Exception{Exception::kSuccess}); + EXPECT_EQ(writer.GetData(), + "\x01\x02\x03\x04\x05\x06\x07\x08\xF1\x02\x03\x04\x05\x06\x07\x08"); +} + +TEST(StreamWriterTest, InvalidWriteIntegerValues) { + StreamWriter writer; + EXPECT_EQ(writer.WriteBits(0x01, 1), Exception{Exception::kSuccess}); + EXPECT_EQ(writer.WriteUint8(0x01), + Exception{Exception::kInvalidProtocolBuffer}); + EXPECT_EQ(writer.WriteUint16(0x0102), + Exception{Exception::kInvalidProtocolBuffer}); + EXPECT_EQ(writer.WriteUint32(0x01020304), + Exception{Exception::kInvalidProtocolBuffer}); + EXPECT_EQ(writer.WriteUint64(0x0102030405060708), + Exception{Exception::kInvalidProtocolBuffer}); +} + +TEST(StreamWriterTest, WriteBytes) { + StreamWriter writer; + EXPECT_EQ(writer.WriteBytes("test"), Exception{Exception::kSuccess}); + EXPECT_EQ(writer.GetData(), std::string("test")); +} + +} // namespace +} // namespace nearby