blob: 6631e69b0ead6698185c7e96d632e5915278749d [file] [log] [blame]
Krzysztof Grobelnyb8cc78d2021-11-29 15:54:53 +01001#include "utils/generate_id.hpp"
2
3#include <sdbusplus/exception.hpp>
4
5#include <system_error>
6
7namespace utils
8{
9namespace details
10{
11
12static constexpr std::string_view allowedCharactersInId =
13 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_/";
14
Krzysztof Grobelnya950e422021-12-31 13:49:00 +010015size_t countDigits(size_t value)
Krzysztof Grobelnyb8cc78d2021-11-29 15:54:53 +010016{
Krzysztof Grobelnya950e422021-12-31 13:49:00 +010017 size_t result = 1;
18 while (value >= 10)
Krzysztof Grobelnyb8cc78d2021-11-29 15:54:53 +010019 {
Krzysztof Grobelnya950e422021-12-31 13:49:00 +010020 ++result;
21 value /= 10;
Krzysztof Grobelnyb8cc78d2021-11-29 15:54:53 +010022 }
Krzysztof Grobelnya950e422021-12-31 13:49:00 +010023 return result;
Krzysztof Grobelnyb8cc78d2021-11-29 15:54:53 +010024}
25
Krzysztof Grobelnya950e422021-12-31 13:49:00 +010026std::string generateId(std::string_view id, std::string_view name,
Krzysztof Grobelnyb8cc78d2021-11-29 15:54:53 +010027 const std::vector<std::string>& conflictIds,
28 size_t maxLength)
29{
Krzysztof Grobelnya950e422021-12-31 13:49:00 +010030 verifyIdCharacters(id);
Krzysztof Grobelnyb8cc78d2021-11-29 15:54:53 +010031
Krzysztof Grobelnya950e422021-12-31 13:49:00 +010032 if (id.starts_with('/'))
Krzysztof Grobelnyb8cc78d2021-11-29 15:54:53 +010033 {
Krzysztof Grobelnya950e422021-12-31 13:49:00 +010034 id = id.substr(1);
35 }
36
37 if ((id.length() > maxLength) ||
38 (id.ends_with('/') && id.length() >= maxLength))
39 {
40 throw sdbusplus::exception::SdBusError(
41 static_cast<int>(std::errc::invalid_argument), "Id too long");
42 }
43
44 if (!id.empty() && !id.ends_with('/'))
45 {
46 if (std::find(conflictIds.begin(), conflictIds.end(), id) !=
47 conflictIds.end())
48 {
49 throw sdbusplus::exception::SdBusError(
50 static_cast<int>(std::errc::file_exists), "Duplicated id");
51 }
52 return std::string(id);
Krzysztof Grobelnyb8cc78d2021-11-29 15:54:53 +010053 }
54
55 std::string strippedId(name);
56 strippedId.erase(
57 std::remove_if(strippedId.begin(), strippedId.end(),
58 [](char c) {
59 return c == '/' ||
60 details::allowedCharactersInId.find(c) ==
61 std::string_view::npos;
62 }),
63 strippedId.end());
Krzysztof Grobelnya950e422021-12-31 13:49:00 +010064 strippedId = std::string(id) + strippedId;
Krzysztof Grobelnyb8cc78d2021-11-29 15:54:53 +010065
66 size_t idx = 0;
67 std::string tmpId = strippedId.substr(0, maxLength);
68
69 while (std::find(conflictIds.begin(), conflictIds.end(), tmpId) !=
70 conflictIds.end() ||
71 tmpId.empty())
72 {
Krzysztof Grobelnya950e422021-12-31 13:49:00 +010073 size_t digitsInIdx = countDigits(idx);
74
75 if (digitsInIdx > maxLength)
76 {
77 throw sdbusplus::exception::SdBusError(
78 static_cast<int>(std::errc::file_exists),
79 "Unique indices are depleted");
80 }
81
82 tmpId =
83 strippedId.substr(0, maxLength - digitsInIdx) + std::to_string(idx);
Krzysztof Grobelnyb8cc78d2021-11-29 15:54:53 +010084 ++idx;
85 }
Krzysztof Grobelnya950e422021-12-31 13:49:00 +010086
Krzysztof Grobelnyb8cc78d2021-11-29 15:54:53 +010087 return tmpId;
88}
89
Krzysztof Grobelnya950e422021-12-31 13:49:00 +010090} // namespace details
91
92void verifyIdCharacters(std::string_view id)
93{
94 if (id.find_first_not_of(details::allowedCharactersInId) !=
95 std::string::npos)
96 {
97 throw sdbusplus::exception::SdBusError(
98 static_cast<int>(std::errc::invalid_argument),
99 "Invalid character in id");
100 }
101
102 if (auto pos = id.find_first_of("/");
103 pos != std::string::npos && pos != id.find_last_of("/"))
104 {
105 throw sdbusplus::exception::SdBusError(
106 static_cast<int>(std::errc::invalid_argument),
107 "Too many '/' in id");
108 }
109}
110
111std::pair<std::string, std::string> generateId(
112 std::string_view id, std::string_view name, std::string_view defaultName,
113 const std::vector<std::string>& conflictIds, const size_t maxLength)
114{
115 if (name.empty() && !id.ends_with('/'))
116 {
117 name = id;
118
119 if (auto pos = name.find_last_of("/"); pos != std::string::npos)
120 {
121 name = name.substr(pos + 1);
122 }
123 }
124
125 if (name.empty())
126 {
127 name = defaultName;
128 }
129
130 return std::make_pair(details::generateId(id, name, conflictIds, maxLength),
131 std::string{name});
132}
133
Krzysztof Grobelnyb8cc78d2021-11-29 15:54:53 +0100134} // namespace utils