Add code documentation

This commit is contained in:
badaix 2025-01-30 20:47:09 +01:00
parent c2bebb4bae
commit b773ccda18
9 changed files with 116 additions and 63 deletions

View file

@ -4,8 +4,8 @@
### Features ### Features
- Client: Add support for (secure-) websockets (Issue #1325) - Client: Add support for (secure-) websockets (Issue #1325, PR #1340)
- Server: Add client authentication (Issue #1334) - Server: Add client authentication (Issue #1334, PR #1340)
### Bugfixes ### Bugfixes

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2022 Johannes Pohl Copyright (C) 2014-2025 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,8 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#ifndef MESSAGE_CLIENT_INFO_HPP #pragma once
#define MESSAGE_CLIENT_INFO_HPP
// local headers // local headers
#include "json_message.hpp" #include "json_message.hpp"
@ -27,39 +26,47 @@ namespace msg
{ {
/// Client information sent from client to server /// Client information sent from client to server
/// Might also be used for sync stats and latency estimations /// Might also be used for other things in future, like
/// - sync stats
/// - latency estimations
/// - Battery status
/// - ...
class ClientInfo : public JsonMessage class ClientInfo : public JsonMessage
{ {
public: public:
/// c'tor
ClientInfo() : JsonMessage(message_type::kClientInfo) ClientInfo() : JsonMessage(message_type::kClientInfo)
{ {
setVolume(100); setVolume(100);
setMuted(false); setMuted(false);
} }
/// d'tor
~ClientInfo() override = default; ~ClientInfo() override = default;
/// @return the volume in percent
uint16_t getVolume() uint16_t getVolume()
{ {
return get("volume", static_cast<uint16_t>(100)); return get("volume", static_cast<uint16_t>(100));
} }
/// @return if muted or not
bool isMuted() bool isMuted()
{ {
return get("muted", false); return get("muted", false);
} }
/// Set the volume to @p volume percent
void setVolume(uint16_t volume) void setVolume(uint16_t volume)
{ {
msg["volume"] = volume; msg["volume"] = volume;
} }
/// Set muted to @p muted
void setMuted(bool muted) void setMuted(bool muted)
{ {
msg["muted"] = muted; msg["muted"] = muted;
} }
}; };
} // namespace msg } // namespace msg
#endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2022 Johannes Pohl Copyright (C) 2014-2025 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,8 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#ifndef MESSAGE_CODEC_HEADER_HPP #pragma once
#define MESSAGE_CODEC_HEADER_HPP
// local headers // local headers
#include "message.hpp" #include "message.hpp"
@ -31,13 +31,15 @@ namespace msg
class CodecHeader : public BaseMessage class CodecHeader : public BaseMessage
{ {
public: public:
explicit CodecHeader(const std::string& codecName = "", uint32_t size = 0) /// c'tor taking the @p codec_name and @p site of the payload
: BaseMessage(message_type::kCodecHeader), payloadSize(size), payload(nullptr), codec(codecName) explicit CodecHeader(const std::string& codec_name = "", uint32_t size = 0)
: BaseMessage(message_type::kCodecHeader), payloadSize(size), payload(nullptr), codec(codec_name)
{ {
if (size > 0) if (size > 0)
payload = static_cast<char*>(malloc(size * sizeof(char))); payload = static_cast<char*>(malloc(size * sizeof(char)));
} }
/// d'tor
~CodecHeader() override ~CodecHeader() override
{ {
free(payload); free(payload);
@ -54,8 +56,11 @@ public:
return static_cast<uint32_t>(sizeof(uint32_t) + codec.size() + sizeof(uint32_t) + payloadSize); return static_cast<uint32_t>(sizeof(uint32_t) + codec.size() + sizeof(uint32_t) + payloadSize);
} }
/// payload size
uint32_t payloadSize; uint32_t payloadSize;
/// the payload
char* payload; char* payload;
/// name of the codec
std::string codec; std::string codec;
protected: protected:
@ -65,7 +70,5 @@ protected:
writeVal(stream, payload, payloadSize); writeVal(stream, payload, payloadSize);
} }
}; };
} // namespace msg } // namespace msg
#endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2024 Johannes Pohl Copyright (C) 2014-2025 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -30,15 +30,16 @@
namespace msg namespace msg
{ {
/// Cast a BaseMessage @message to type "ToType"
/// @return castest message or nullptr, if the cast failed
template <typename ToType> template <typename ToType>
static std::unique_ptr<ToType> message_cast(std::unique_ptr<msg::BaseMessage> message) static std::unique_ptr<ToType> message_cast(std::unique_ptr<msg::BaseMessage> message)
{ {
ToType* tmp = dynamic_cast<ToType*>(message.get()); auto* tmp = dynamic_cast<ToType*>(message.get());
std::unique_ptr<ToType> result;
if (tmp != nullptr) if (tmp != nullptr)
{ {
message.release(); message.release();
result.reset(tmp); std::unique_ptr<ToType> result(tmp);
return result; return result;
} }
return nullptr; return nullptr;
@ -47,6 +48,7 @@ static std::unique_ptr<ToType> message_cast(std::unique_ptr<msg::BaseMessage> me
namespace factory namespace factory
{ {
/// Create a message of type T from @p base_message beaser and payload @p buffer
template <typename T> template <typename T>
static std::unique_ptr<T> createMessage(const BaseMessage& base_message, char* buffer) static std::unique_ptr<T> createMessage(const BaseMessage& base_message, char* buffer)
{ {
@ -57,6 +59,7 @@ static std::unique_ptr<T> createMessage(const BaseMessage& base_message, char* b
return result; return result;
} }
/// Create a BaseMessage from @p base_message header and payload @p buffer
static std::unique_ptr<BaseMessage> createMessage(const BaseMessage& base_message, char* buffer) static std::unique_ptr<BaseMessage> createMessage(const BaseMessage& base_message, char* buffer)
{ {
std::unique_ptr<BaseMessage> result; std::unique_ptr<BaseMessage> result;

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2022 Johannes Pohl Copyright (C) 2014-2025 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,8 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#ifndef MESSAGE_HELLO_HPP #pragma once
#define MESSAGE_HELLO_HPP
// local headers // local headers
#include "common/str_compat.hpp" #include "common/str_compat.hpp"
@ -31,16 +30,20 @@
namespace msg namespace msg
{ {
/// Hello message
/// Initial message, sent from client to server
class Hello : public JsonMessage class Hello : public JsonMessage
{ {
public: public:
/// c'tor
Hello() : JsonMessage(message_type::kHello) Hello() : JsonMessage(message_type::kHello)
{ {
} }
Hello(const std::string& macAddress, const std::string& id, size_t instance) : JsonMessage(message_type::kHello) /// c'tor taking @p macAddress, @p id and @p instance
Hello(const std::string& mac_address, const std::string& id, size_t instance) : JsonMessage(message_type::kHello)
{ {
msg["MAC"] = macAddress; msg["MAC"] = mac_address;
msg["HostName"] = ::getHostName(); msg["HostName"] = ::getHostName();
msg["Version"] = VERSION; msg["Version"] = VERSION;
msg["ClientName"] = "Snapclient"; msg["ClientName"] = "Snapclient";
@ -51,53 +54,64 @@ public:
msg["SnapStreamProtocolVersion"] = 2; msg["SnapStreamProtocolVersion"] = 2;
} }
/// d'tor
~Hello() override = default; ~Hello() override = default;
/// @return the MAC address
std::string getMacAddress() const std::string getMacAddress() const
{ {
return msg["MAC"]; return msg["MAC"];
} }
/// @return the host name
std::string getHostName() const std::string getHostName() const
{ {
return msg["HostName"]; return msg["HostName"];
} }
/// @return the client version
std::string getVersion() const std::string getVersion() const
{ {
return msg["Version"]; return msg["Version"];
} }
/// @return the client name (e.g. "Snapclient")
std::string getClientName() const std::string getClientName() const
{ {
return msg["ClientName"]; return msg["ClientName"];
} }
/// @return the OS name
std::string getOS() const std::string getOS() const
{ {
return msg["OS"]; return msg["OS"];
} }
/// @return the CPU architecture
std::string getArch() const std::string getArch() const
{ {
return msg["Arch"]; return msg["Arch"];
} }
/// @return the instance id
int getInstance() const int getInstance() const
{ {
return get("Instance", 1); return get("Instance", 1);
} }
/// @return the protocol version
int getProtocolVersion() const int getProtocolVersion() const
{ {
return get("SnapStreamProtocolVersion", 1); return get("SnapStreamProtocolVersion", 1);
} }
/// @return a unqiue machine ID
std::string getId() const std::string getId() const
{ {
return get("ID", getMacAddress()); return get("ID", getMacAddress());
} }
/// @return a unqiue client ID
std::string getUniqueId() const std::string getUniqueId() const
{ {
std::string id = getId(); std::string id = getId();
@ -109,7 +123,5 @@ public:
return id; return id;
} }
}; };
} // namespace msg } // namespace msg
#endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2022 Johannes Pohl Copyright (C) 2014-2025 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,8 +16,8 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#ifndef MESSAGE_JSON_HPP #pragma once
#define MESSAGE_JSON_HPP
// local headers // local headers
#include "common/json.hpp" #include "common/json.hpp"
@ -30,13 +30,16 @@ using json = nlohmann::json;
namespace msg namespace msg
{ {
/// Base class of a message with json payload
class JsonMessage : public BaseMessage class JsonMessage : public BaseMessage
{ {
public: public:
JsonMessage(message_type msgType) : BaseMessage(msgType) /// c'tor taking the @p msg_type
explicit JsonMessage(message_type msg_type) : BaseMessage(msg_type)
{ {
} }
/// d'tor
~JsonMessage() override = default; ~JsonMessage() override = default;
void read(std::istream& stream) override void read(std::istream& stream) override
@ -60,6 +63,7 @@ protected:
writeVal(stream, msg.dump()); writeVal(stream, msg.dump());
} }
/// @return value for key @p what or @p def, if not found
template <typename T> template <typename T>
T get(const std::string& what, const T& def) const T get(const std::string& what, const T& def) const
{ {
@ -75,7 +79,5 @@ protected:
} }
} }
}; };
} // namespace msg } // namespace msg
#endif

View file

@ -38,15 +38,17 @@ namespace msg
class PcmChunk : public WireChunk class PcmChunk : public WireChunk
{ {
public: public:
PcmChunk(const SampleFormat& sampleFormat, uint32_t ms) /// c'tor, construct from @p sample_format with duration @p ms
: WireChunk((sampleFormat.rate() * ms / 1000) * sampleFormat.frameSize()), format(sampleFormat), idx_(0) PcmChunk(const SampleFormat& sample_format, uint32_t ms) : WireChunk((sample_format.rate() * ms / 1000) * sample_format.frameSize()), format(sample_format)
{ {
} }
PcmChunk() : WireChunk(), idx_(0) /// c'tor
PcmChunk() : WireChunk()
{ {
} }
/// d'tor
~PcmChunk() override = default; ~PcmChunk() override = default;
#if 0 #if 0
@ -73,16 +75,18 @@ public:
// return result; // return result;
// } // }
int readFrames(void* outputBuffer, uint32_t frameCount) /// Read the next @p frame_count frames into @p output_buffer, update the internal read position
/// @return number of frames copied to @p output_buffer
int readFrames(void* output_buffer, uint32_t frame_count)
{ {
// logd << "read: " << frameCount << ", total: " << (wireChunk->length / format.frameSize()) << ", idx: " << idx;// << "\n"; // logd << "read: " << frameCount << ", total: " << (wireChunk->length / format.frameSize()) << ", idx: " << idx;// << "\n";
int result = frameCount; int result = frame_count;
if (idx_ + frameCount > (payloadSize / format.frameSize())) if (idx_ + frame_count > (payloadSize / format.frameSize()))
result = (payloadSize / format.frameSize()) - idx_; result = (payloadSize / format.frameSize()) - idx_;
// logd << ", from: " << format.frameSize()*idx << ", to: " << format.frameSize()*idx + format.frameSize()*result; // logd << ", from: " << format.frameSize()*idx << ", to: " << format.frameSize()*idx + format.frameSize()*result;
if (outputBuffer != nullptr) if (output_buffer != nullptr)
memcpy(static_cast<char*>(outputBuffer), static_cast<char*>(payload) + format.frameSize() * idx_, format.frameSize() * result); memcpy(static_cast<char*>(output_buffer), static_cast<char*>(payload) + format.frameSize() * idx_, format.frameSize() * result);
idx_ += result; idx_ += result;
// logd << ", new idx: " << idx << ", result: " << result << ", wireChunk->length: " << wireChunk->length << ", format.frameSize(): " << // logd << ", new idx: " << idx << ", result: " << result << ", wireChunk->length: " << wireChunk->length << ", format.frameSize(): " <<
@ -90,6 +94,8 @@ public:
return result; return result;
} }
/// seek @p frames forward or backward
/// @return the new read position
int seek(int frames) int seek(int frames)
{ {
if ((frames < 0) && (-frames > static_cast<int>(idx_))) if ((frames < 0) && (-frames > static_cast<int>(idx_)))
@ -102,17 +108,20 @@ public:
return idx_; return idx_;
} }
/// @return start time of the current frame
chronos::time_point_clk start() const override chronos::time_point_clk start() const override
{ {
return chronos::time_point_clk(chronos::sec(timestamp.sec) + chronos::usec(timestamp.usec) + return chronos::time_point_clk(chronos::sec(timestamp.sec) + chronos::usec(timestamp.usec) +
chronos::usec(static_cast<chronos::usec::rep>(1000000. * ((double)idx_ / (double)format.rate())))); chronos::usec(static_cast<chronos::usec::rep>(1000000. * ((double)idx_ / (double)format.rate()))));
} }
/// @return time of the last frame
inline chronos::time_point_clk end() const inline chronos::time_point_clk end() const
{ {
return start() + durationLeft<chronos::usec>(); return start() + durationLeft<chronos::usec>();
} }
/// @return duration of this chunk
template <typename T> template <typename T>
inline T duration() const inline T duration() const
{ {
@ -127,42 +136,51 @@ public:
// payloadSize = newSize; // payloadSize = newSize;
// } // }
void setFrameCount(int frameCount) /// Set the @p frame_count, reserve memory
void setFrameCount(int frame_count)
{ {
auto newSize = format.frameSize() * frameCount; auto new_size = format.frameSize() * frame_count;
payload = static_cast<char*>(realloc(payload, newSize)); payload = static_cast<char*>(realloc(payload, new_size));
payloadSize = newSize; payloadSize = new_size;
} }
/// @return duration of this chunk in [ms]
double durationMs() const double durationMs() const
{ {
return static_cast<double>(getFrameCount()) / format.msRate(); return static_cast<double>(getFrameCount()) / format.msRate();
} }
/// @return time left, starting from the read pointer
template <typename T> template <typename T>
inline T durationLeft() const inline T durationLeft() const
{ {
return std::chrono::duration_cast<T>(chronos::nsec(static_cast<chronos::nsec::rep>(1000000 * (getFrameCount() - idx_) / format.msRate()))); return std::chrono::duration_cast<T>(chronos::nsec(static_cast<chronos::nsec::rep>(1000000 * (getFrameCount() - idx_) / format.msRate())));
} }
/// @return true if the read pointer is at the end
inline bool isEndOfChunk() const inline bool isEndOfChunk() const
{ {
return idx_ >= getFrameCount(); return idx_ >= getFrameCount();
} }
/// @return number of frames
inline uint32_t getFrameCount() const inline uint32_t getFrameCount() const
{ {
return (payloadSize / format.frameSize()); return (payloadSize / format.frameSize());
} }
/// @return number of samples
inline uint32_t getSampleCount() const inline uint32_t getSampleCount() const
{ {
return (payloadSize / format.sampleSize()); return (payloadSize / format.sampleSize());
} }
/// Sample format of this chunk
SampleFormat format; SampleFormat format;
private: private:
/// current read position (frame idx)
uint32_t idx_ = 0; uint32_t idx_ = 0;
}; };
} // namespace msg } // namespace msg

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2022 Johannes Pohl Copyright (C) 2014-2025 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,8 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#ifndef MESSAGE_SERVER_SETTINGS_HPP #pragma once
#define MESSAGE_SERVER_SETTINGS_HPP
// local headers // local headers
#include "json_message.hpp" #include "json_message.hpp"
@ -26,9 +25,11 @@
namespace msg namespace msg
{ {
/// Dynamic settings that affect the client
class ServerSettings : public JsonMessage class ServerSettings : public JsonMessage
{ {
public: public:
/// c'tor
ServerSettings() : JsonMessage(message_type::kServerSettings) ServerSettings() : JsonMessage(message_type::kServerSettings)
{ {
setBufferMs(0); setBufferMs(0);
@ -37,51 +38,57 @@ public:
setMuted(false); setMuted(false);
} }
/// d'tor
~ServerSettings() override = default; ~ServerSettings() override = default;
/// @return the end to end delay in [ms]
int32_t getBufferMs() int32_t getBufferMs()
{ {
return get("bufferMs", 0); return get("bufferMs", 0);
} }
/// @return client specific additional latency in [ms]
int32_t getLatency() int32_t getLatency()
{ {
return get("latency", 0); return get("latency", 0);
} }
/// @return the volume in [%]
uint16_t getVolume() uint16_t getVolume()
{ {
return get("volume", static_cast<uint16_t>(100)); return get("volume", static_cast<uint16_t>(100));
} }
/// @return if muted
bool isMuted() bool isMuted()
{ {
return get("muted", false); return get("muted", false);
} }
/// Set the end to end delay to @p buffer_ms [ms]
void setBufferMs(int32_t bufferMs) void setBufferMs(int32_t buffer_ms)
{ {
msg["bufferMs"] = bufferMs; msg["bufferMs"] = buffer_ms;
} }
/// Set the additional client specific @p latency [ms]
void setLatency(int32_t latency) void setLatency(int32_t latency)
{ {
msg["latency"] = latency; msg["latency"] = latency;
} }
/// Set the @p volume [%]
void setVolume(uint16_t volume) void setVolume(uint16_t volume)
{ {
msg["volume"] = volume; msg["volume"] = volume;
} }
/// Set client to @p muted
void setMuted(bool muted) void setMuted(bool muted)
{ {
msg["muted"] = muted; msg["muted"] = muted;
} }
}; };
} // namespace msg } // namespace msg
#endif

View file

@ -1,6 +1,6 @@
/*** /***
This file is part of snapcast This file is part of snapcast
Copyright (C) 2014-2022 Johannes Pohl Copyright (C) 2014-2025 Johannes Pohl
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@ -16,8 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
***/ ***/
#ifndef MESSAGE_TIME_HPP #pragma once
#define MESSAGE_TIME_HPP
// local headers // local headers
#include "message.hpp" #include "message.hpp"
@ -25,13 +24,16 @@
namespace msg namespace msg
{ {
/// Time sync message, send from client to server and back
class Time : public BaseMessage class Time : public BaseMessage
{ {
public: public:
/// c'tor
Time() : BaseMessage(message_type::kTime) Time() : BaseMessage(message_type::kTime)
{ {
} }
/// d'tor
~Time() override = default; ~Time() override = default;
void read(std::istream& stream) override void read(std::istream& stream) override
@ -45,6 +47,7 @@ public:
return sizeof(tv); return sizeof(tv);
} }
/// The latency after round trip "client => server => client"
tv latency; tv latency;
protected: protected:
@ -54,7 +57,5 @@ protected:
writeVal(stream, latency.usec); writeVal(stream, latency.usec);
} }
}; };
} // namespace msg } // namespace msg
#endif