| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 <pulsar/st/MessageId.h> |
| |
| #include <cstdint> |
| #include <cstring> |
| #include <exception> |
| #include <functional> |
| #include <ostream> |
| #include <string> |
| #include <utility> |
| |
| #include "MessageIdImpl.h" |
| #include "lib/LogUtils.h" |
| |
| DECLARE_LOG_OBJECT() |
| |
| namespace pulsar::st { |
| |
| namespace { |
| |
| // The serialized form is the Java client's MessageIdV5 layout (big-endian): |
| // int64 segmentId |
| // int32 v4Length, v4 bytes (classic MessageId serialization) |
| // int32 positionCount, {int64 segmentId, int32 length, bytes} entries |
| // int32 parentTopicLength (-1 = absent), utf-8 bytes |
| // int32 multiTopicCount (-1 = absent), |
| // {int32 topicLength, bytes, int32 positionCount, entries} entries |
| // The trailing sections are optional on read, so shorter (older) forms parse. |
| |
| void putInt32(std::vector<std::byte>& out, std::int32_t value) { |
| auto u = static_cast<std::uint32_t>(value); |
| for (int shift = 24; shift >= 0; shift -= 8) { |
| out.push_back(static_cast<std::byte>((u >> shift) & 0xFF)); |
| } |
| } |
| |
| void putInt64(std::vector<std::byte>& out, std::int64_t value) { |
| auto u = static_cast<std::uint64_t>(value); |
| for (int shift = 56; shift >= 0; shift -= 8) { |
| out.push_back(static_cast<std::byte>((u >> shift) & 0xFF)); |
| } |
| } |
| |
| void putBytes(std::vector<std::byte>& out, const std::string& bytes) { |
| // Append via resize + memcpy rather than insert(end, ptr, ptr+n): GCC 13's |
| // -Wstringop-overflow false-positives on vector::insert from a cast pointer range under |
| // the Alpine/musl release build (it loses the destination size through the inlined |
| // uninitialized_copy). resize gives the analyzer a known size and sidesteps that path. |
| const std::size_t offset = out.size(); |
| out.resize(offset + bytes.size()); |
| if (!bytes.empty()) { |
| std::memcpy(out.data() + offset, bytes.data(), bytes.size()); |
| } |
| } |
| |
| class Reader { |
| public: |
| explicit Reader(std::span<const std::byte> data) : data_(data) {} |
| |
| bool hasRemaining() const { return pos_ < data_.size(); } |
| |
| bool readInt32(std::int32_t& value) { |
| if (data_.size() - pos_ < 4) return false; |
| std::uint32_t u = 0; |
| for (int i = 0; i < 4; i++) { |
| u = (u << 8) | std::to_integer<std::uint32_t>(data_[pos_++]); |
| } |
| value = static_cast<std::int32_t>(u); |
| return true; |
| } |
| |
| bool readInt64(std::int64_t& value) { |
| if (data_.size() - pos_ < 8) return false; |
| std::uint64_t u = 0; |
| for (int i = 0; i < 8; i++) { |
| u = (u << 8) | std::to_integer<std::uint64_t>(data_[pos_++]); |
| } |
| value = static_cast<std::int64_t>(u); |
| return true; |
| } |
| |
| bool readBytes(std::int32_t length, std::string& out) { |
| if (length < 0 || static_cast<std::size_t>(length) > data_.size() - pos_) return false; |
| out.assign(reinterpret_cast<const char*>(data_.data() + pos_), static_cast<std::size_t>(length)); |
| pos_ += static_cast<std::size_t>(length); |
| return true; |
| } |
| |
| private: |
| std::span<const std::byte> data_; |
| std::size_t pos_ = 0; |
| }; |
| |
| void writePositionVector(std::vector<std::byte>& out, |
| const std::map<std::int64_t, pulsar::MessageId>& vector) { |
| putInt32(out, static_cast<std::int32_t>(vector.size())); |
| for (const auto& entry : vector) { |
| putInt64(out, entry.first); |
| std::string serialized; |
| entry.second.serialize(serialized); |
| putInt32(out, static_cast<std::int32_t>(serialized.size())); |
| putBytes(out, serialized); |
| } |
| } |
| |
| bool readPositionVector(Reader& reader, std::map<std::int64_t, pulsar::MessageId>& out) { |
| std::int32_t count; |
| if (!reader.readInt32(count) || count < 0) return false; |
| for (std::int32_t i = 0; i < count; i++) { |
| std::int64_t segmentId; |
| std::int32_t length; |
| std::string bytes; |
| if (!reader.readInt64(segmentId) || !reader.readInt32(length) || !reader.readBytes(length, bytes)) { |
| return false; |
| } |
| out.emplace(segmentId, pulsar::MessageId::deserialize(bytes)); |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| // An empty id: impl_ stays null, so the handle is falsy under operator bool and |
| // compares equal only to other empty ids. |
| MessageId::MessageId() = default; |
| |
| MessageId::MessageId(std::shared_ptr<MessageIdImpl> impl) : impl_(std::move(impl)) {} |
| |
| const MessageId& MessageId::earliest() { |
| static const MessageId id = |
| MessageIdFactory::create(pulsar::MessageId::earliest(), MessageIdImpl::kNoSegment); |
| return id; |
| } |
| |
| const MessageId& MessageId::latest() { |
| static const MessageId id = |
| MessageIdFactory::create(pulsar::MessageId::latest(), MessageIdImpl::kNoSegment); |
| return id; |
| } |
| |
| std::vector<std::byte> MessageId::toByteArray() const { |
| if (!impl_) { |
| return {}; |
| } |
| std::vector<std::byte> out; |
| putInt64(out, impl_->segmentId); |
| |
| std::string v4Bytes; |
| impl_->v4MessageId.serialize(v4Bytes); |
| putInt32(out, static_cast<std::int32_t>(v4Bytes.size())); |
| putBytes(out, v4Bytes); |
| |
| writePositionVector(out, impl_->positionVector); |
| |
| if (impl_->parentTopic) { |
| putInt32(out, static_cast<std::int32_t>(impl_->parentTopic->size())); |
| putBytes(out, *impl_->parentTopic); |
| } else { |
| putInt32(out, -1); |
| } |
| |
| if (impl_->multiTopicVector) { |
| putInt32(out, static_cast<std::int32_t>(impl_->multiTopicVector->size())); |
| for (const auto& entry : *impl_->multiTopicVector) { |
| putInt32(out, static_cast<std::int32_t>(entry.first.size())); |
| putBytes(out, entry.first); |
| writePositionVector(out, entry.second); |
| } |
| } else { |
| putInt32(out, -1); |
| } |
| return out; |
| } |
| |
| MessageId MessageId::fromByteArray(std::span<const std::byte> data) { |
| try { |
| Reader reader(data); |
| auto impl = std::make_shared<MessageIdImpl>(); |
| |
| std::int32_t v4Length; |
| std::string v4Bytes; |
| if (!reader.readInt64(impl->segmentId) || !reader.readInt32(v4Length) || |
| !reader.readBytes(v4Length, v4Bytes)) { |
| LOG_WARN("Discarding malformed scalable-topic MessageId: " << data.size() << " bytes"); |
| return MessageId(); |
| } |
| impl->v4MessageId = pulsar::MessageId::deserialize(v4Bytes); |
| |
| if (reader.hasRemaining() && !readPositionVector(reader, impl->positionVector)) { |
| LOG_WARN("Discarding scalable-topic MessageId with a malformed position vector"); |
| return MessageId(); |
| } |
| |
| if (reader.hasRemaining()) { |
| std::int32_t parentLength; |
| if (!reader.readInt32(parentLength)) { |
| return MessageId(); |
| } |
| if (parentLength >= 0) { |
| std::string parent; |
| if (!reader.readBytes(parentLength, parent)) { |
| return MessageId(); |
| } |
| impl->parentTopic = std::move(parent); |
| } |
| } |
| |
| if (reader.hasRemaining()) { |
| std::int32_t topicCount; |
| if (!reader.readInt32(topicCount)) { |
| return MessageId(); |
| } |
| if (topicCount >= 0) { |
| std::map<std::string, std::map<std::int64_t, pulsar::MessageId>> multi; |
| for (std::int32_t i = 0; i < topicCount; i++) { |
| std::int32_t topicLength; |
| std::string topic; |
| std::map<std::int64_t, pulsar::MessageId> inner; |
| if (!reader.readInt32(topicLength) || !reader.readBytes(topicLength, topic) || |
| !readPositionVector(reader, inner)) { |
| return MessageId(); |
| } |
| multi.emplace(std::move(topic), std::move(inner)); |
| } |
| impl->multiTopicVector = std::move(multi); |
| } |
| } |
| return MessageIdFactory::create(std::move(impl)); |
| } catch (const std::exception& e) { |
| // The classic MessageId deserialization throws on malformed proto bytes. |
| LOG_WARN("Discarding malformed scalable-topic MessageId: " << e.what()); |
| return MessageId(); |
| } |
| } |
| |
| std::strong_ordering MessageId::operator<=>(const MessageId& other) const { |
| // Empty ids sort before every real id and tie with each other. |
| if (!impl_ || !other.impl_) { |
| return static_cast<bool>(impl_) <=> static_cast<bool>(other.impl_); |
| } |
| if (auto cmp = impl_->segmentId <=> other.impl_->segmentId; cmp != 0) { |
| return cmp; |
| } |
| if (impl_->v4MessageId == other.impl_->v4MessageId) { |
| return std::strong_ordering::equal; |
| } |
| return impl_->v4MessageId < other.impl_->v4MessageId ? std::strong_ordering::less |
| : std::strong_ordering::greater; |
| } |
| |
| bool MessageId::operator==(const MessageId& other) const { |
| if (!impl_ || !other.impl_) { |
| return static_cast<bool>(impl_) == static_cast<bool>(other.impl_); |
| } |
| return impl_->segmentId == other.impl_->segmentId && impl_->v4MessageId == other.impl_->v4MessageId; |
| } |
| |
| std::ostream& operator<<(std::ostream& s, const MessageId& messageId) { |
| if (!messageId.impl_) { |
| return s << "{empty}"; |
| } |
| return s << "{segment=" << messageId.impl_->segmentId << ", id=" << messageId.impl_->v4MessageId |
| << ", positions=" << messageId.impl_->positionVector.size() << "}"; |
| } |
| |
| } // namespace pulsar::st |
| |
| std::size_t std::hash<pulsar::st::MessageId>::operator()( |
| const pulsar::st::MessageId& messageId) const noexcept { |
| const auto& impl = pulsar::st::MessageIdFactory::impl(messageId); |
| if (!impl) { |
| return 0; |
| } |
| std::size_t seed = std::hash<std::int64_t>()(impl->segmentId); |
| auto combine = [&seed](std::size_t h) { seed ^= h + 0x9e3779b9 + (seed << 6) + (seed >> 2); }; |
| combine(std::hash<std::int64_t>()(impl->v4MessageId.ledgerId())); |
| combine(std::hash<std::int64_t>()(impl->v4MessageId.entryId())); |
| combine(std::hash<std::int32_t>()(impl->v4MessageId.batchIndex())); |
| combine(std::hash<std::int32_t>()(impl->v4MessageId.partition())); |
| return seed; |
| } |