diff --git a/Makefile b/Makefile index 22e01d57..957d2004 100644 --- a/Makefile +++ b/Makefile @@ -11,3 +11,10 @@ build/StrfryTemplates.h: $(shell find src/tmpls/ -type f -name '*.tmpl') PERL5LIB=golpe/vendor/ perl golpe/external/templar/templar.pl src/tmpls/ strfrytmpl $@ src/apps/relay/RelayWebsocket.o: build/StrfryTemplates.h + +.PHONY: test-subid +test-subid: build/subid_tests + build/subid_tests + +build/subid_tests: test/SubIdTests.cpp build/golpe.h + $(CXX) $(CXXFLAGS) $(INCS) $< -o $@ diff --git a/src/Subscription.h b/src/Subscription.h index 4ffe786a..6d440cf0 100644 --- a/src/Subscription.h +++ b/src/Subscription.h @@ -6,12 +6,11 @@ struct SubId { - char buf[72]; + char buf[MAX_SUBID_SIZE + 1]; SubId(std::string_view val) { - static_assert(MAX_SUBID_SIZE == 71, "MAX_SUBID_SIZE mismatch"); - if (val.size() > 71) throw herr("subscription id too long"); - if (val.size() == 0) throw herr("subscription id too short"); + static_assert(MAX_SUBID_SIZE <= 255, "MAX_SUBID_SIZE must fit in a byte"); + if (val.empty() || val.size() > MAX_SUBID_SIZE) throw herr("invalid subscription id length"); auto badChar = [](char c){ return c < 0x20 || c == '\\' || c == '"' || c >= 0x7F; diff --git a/src/constants.h b/src/constants.h index cc3b0041..79ae5374 100644 --- a/src/constants.h +++ b/src/constants.h @@ -1,5 +1,5 @@ #pragma once const uint64_t CURR_DB_VERSION = 3; -const size_t MAX_SUBID_SIZE = 71; // Statically allocated size in SubId +const size_t MAX_SUBID_SIZE = 64; // NIP-01: REQ subscription ids must be non-empty and <=64 bytes const size_t MAX_INDEXED_TAG_VAL_SIZE = 255; diff --git a/test/SubIdTests.cpp b/test/SubIdTests.cpp new file mode 100644 index 00000000..0f1445a5 --- /dev/null +++ b/test/SubIdTests.cpp @@ -0,0 +1,45 @@ +#include "Subscription.h" + +#include +#include +#include +#include +#include + +namespace { + +void expectSuccess(std::string_view name, std::string subId) { + try { + SubId s(subId); + if (s.sv() != std::string_view(subId)) { + std::cerr << name << ": round-trip mismatch\n"; + std::exit(EXIT_FAILURE); + } + } catch (const std::exception &e) { + std::cerr << name << ": expected success but threw: " << e.what() << "\n"; + std::exit(EXIT_FAILURE); + } +} + +void expectFailure(std::string_view name, std::string subId) { + try { + SubId s(subId); + std::cerr << name << ": expected failure but constructed successfully\n"; + std::exit(EXIT_FAILURE); + } catch (const std::exception &) { + } +} + +} // namespace + +int main() { + expectSuccess("max length", std::string(64, 'a')); + expectFailure("too long", std::string(65, 'a')); + expectFailure("empty", std::string()); + expectFailure("control char", std::string(1, '\x1F')); + expectFailure("quote char", std::string("\"")); + expectFailure("backslash", std::string("\\")); + + std::cout << "SubId tests passed\n"; + return EXIT_SUCCESS; +}