blob: dec631152c11c5b7149e2a0b5c9edfa2a1d7a87d [file]
/**
* 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;
}