1#include <base/system.h>
2#include <engine/favorites.h>
3#include <engine/shared/config.h>
4#include <engine/shared/protocol.h>
5
6#include <algorithm>
7#include <unordered_map>
8#include <vector>
9
10class CFavorites : public IFavorites
11{
12protected:
13 void OnConfigSave(IConfigManager *pConfigManager) override;
14
15public:
16 TRISTATE IsFavorite(const NETADDR *pAddrs, int NumAddrs) const override;
17 TRISTATE IsPingAllowed(const NETADDR *pAddrs, int NumAddrs) const override;
18 void Add(const NETADDR *pAddrs, int NumAddrs) override;
19 void AllowPing(const NETADDR *pAddrs, int NumAddrs, bool AllowPing) override;
20 void Remove(const NETADDR *pAddrs, int NumAddrs) override;
21 void AllEntries(const CEntry **ppEntries, int *pNumEntries) override;
22
23private:
24 std::vector<CEntry> m_vEntries;
25 std::unordered_map<NETADDR, int> m_ByAddr;
26
27 CEntry *Entry(const NETADDR &Addr);
28 const CEntry *Entry(const NETADDR &Addr) const;
29 // `pEntry` must come from the `m_vEntries` vector.
30 void RemoveEntry(CEntry *pEntry);
31};
32
33void CFavorites::OnConfigSave(IConfigManager *pConfigManager)
34{
35 for(const auto &Entry : m_vEntries)
36 {
37 if(Entry.m_NumAddrs > 1)
38 {
39 pConfigManager->WriteLine(pLine: "begin_favorite_group");
40 }
41 for(int i = 0; i < Entry.m_NumAddrs; i++)
42 {
43 char aAddr[NETADDR_MAXSTRSIZE];
44 char aBuffer[128];
45 net_addr_str(addr: &Entry.m_aAddrs[i], string: aAddr, max_length: sizeof(aAddr), add_port: true);
46 if(!Entry.m_AllowPing)
47 {
48 str_format(buffer: aBuffer, buffer_size: sizeof(aBuffer), format: "add_favorite %s", aAddr);
49 }
50 else
51 {
52 // Add quotes to the first parameter for backward
53 // compatibility with versions that took a `r` console
54 // parameter.
55 str_format(buffer: aBuffer, buffer_size: sizeof(aBuffer), format: "add_favorite \"%s\" allow_ping", aAddr);
56 }
57 pConfigManager->WriteLine(pLine: aBuffer);
58 }
59 if(Entry.m_NumAddrs > 1)
60 {
61 pConfigManager->WriteLine(pLine: "end_favorite_group");
62 }
63 }
64}
65
66TRISTATE CFavorites::IsFavorite(const NETADDR *pAddrs, int NumAddrs) const
67{
68 bool All = true;
69 bool None = true;
70 for(int i = 0; i < NumAddrs && (All || None); i++)
71 {
72 const CEntry *pEntry = Entry(Addr: pAddrs[i]);
73 if(pEntry)
74 {
75 None = false;
76 }
77 else
78 {
79 All = false;
80 }
81 }
82 // Return ALL if no addresses were passed.
83 if(All)
84 {
85 return TRISTATE::ALL;
86 }
87 else if(None)
88 {
89 return TRISTATE::NONE;
90 }
91 else
92 {
93 return TRISTATE::SOME;
94 }
95}
96
97TRISTATE CFavorites::IsPingAllowed(const NETADDR *pAddrs, int NumAddrs) const
98{
99 bool All = true;
100 bool None = true;
101 for(int i = 0; i < NumAddrs && (All || None); i++)
102 {
103 const CEntry *pEntry = Entry(Addr: pAddrs[i]);
104 if(pEntry == nullptr)
105 {
106 continue;
107 }
108 if(pEntry->m_AllowPing)
109 {
110 None = false;
111 }
112 else
113 {
114 All = false;
115 }
116 }
117 // Return ALL if no addresses were passed.
118 if(All)
119 {
120 return TRISTATE::ALL;
121 }
122 else if(None)
123 {
124 return TRISTATE::NONE;
125 }
126 else
127 {
128 return TRISTATE::SOME;
129 }
130}
131
132void CFavorites::Add(const NETADDR *pAddrs, int NumAddrs)
133{
134 // First make sure that all the addresses are not registered for some
135 // other favorite.
136 for(int i = 0; i < NumAddrs; i++)
137 {
138 CEntry *pEntry = Entry(Addr: pAddrs[i]);
139 if(pEntry == nullptr)
140 {
141 continue;
142 }
143 for(int j = 0; j < pEntry->m_NumAddrs; j++)
144 {
145 if(pEntry->m_aAddrs[j] == pAddrs[i])
146 {
147 pEntry->m_aAddrs[j] = pEntry->m_aAddrs[pEntry->m_NumAddrs - 1];
148 pEntry->m_NumAddrs -= 1;
149 break;
150 }
151 }
152 // If the entry has become empty due to the cleaning, remove it
153 // completely.
154 if(pEntry->m_NumAddrs == 0)
155 {
156 RemoveEntry(pEntry);
157 }
158 }
159 // Add the new entry.
160 CEntry NewEntry;
161 mem_zero(block: &NewEntry, size: sizeof(NewEntry));
162 NewEntry.m_NumAddrs = std::min(a: NumAddrs, b: (int)std::size(NewEntry.m_aAddrs));
163 for(int i = 0; i < NewEntry.m_NumAddrs; i++)
164 {
165 NewEntry.m_aAddrs[i] = pAddrs[i];
166 m_ByAddr[pAddrs[i]] = m_vEntries.size();
167 }
168 NewEntry.m_AllowPing = false;
169 m_vEntries.push_back(x: NewEntry);
170}
171
172void CFavorites::AllowPing(const NETADDR *pAddrs, int NumAddrs, bool AllowPing)
173{
174 for(int i = 0; i < NumAddrs; i++)
175 {
176 CEntry *pEntry = Entry(Addr: pAddrs[i]);
177 if(pEntry == nullptr)
178 {
179 continue;
180 }
181 pEntry->m_AllowPing = AllowPing;
182 }
183}
184
185void CFavorites::Remove(const NETADDR *pAddrs, int NumAddrs)
186{
187 for(int i = 0; i < NumAddrs; i++)
188 {
189 CEntry *pEntry = Entry(Addr: pAddrs[i]);
190 if(pEntry == nullptr)
191 {
192 continue;
193 }
194 for(int j = 0; j < pEntry->m_NumAddrs; j++)
195 {
196 m_ByAddr.erase(x: pEntry->m_aAddrs[j]);
197 }
198 RemoveEntry(pEntry);
199 }
200}
201
202void CFavorites::AllEntries(const CEntry **ppEntries, int *pNumEntries)
203{
204 *ppEntries = m_vEntries.data();
205 *pNumEntries = m_vEntries.size();
206}
207
208CFavorites::CEntry *CFavorites::Entry(const NETADDR &Addr)
209{
210 auto Entry = m_ByAddr.find(x: Addr);
211 if(Entry == m_ByAddr.end())
212 {
213 return nullptr;
214 }
215 return &m_vEntries[Entry->second];
216}
217
218const CFavorites::CEntry *CFavorites::Entry(const NETADDR &Addr) const
219{
220 auto Entry = m_ByAddr.find(x: Addr);
221 if(Entry == m_ByAddr.end())
222 {
223 return nullptr;
224 }
225 return &m_vEntries[Entry->second];
226}
227
228void CFavorites::RemoveEntry(CEntry *pEntry)
229{
230 // Replace the entry
231 int Index = pEntry - m_vEntries.data();
232 *pEntry = m_vEntries[m_vEntries.size() - 1];
233 m_vEntries.pop_back();
234 if(Index != (int)m_vEntries.size())
235 {
236 for(int i = 0; i < pEntry->m_NumAddrs; i++)
237 {
238 m_ByAddr.at(k: pEntry->m_aAddrs[i]) = Index;
239 }
240 }
241}
242
243void IFavorites::ConfigSaveCallback(IConfigManager *pConfigManager, void *pUserData)
244{
245 ((IFavorites *)pUserData)->OnConfigSave(pConfigManager);
246}
247
248std::unique_ptr<IFavorites> CreateFavorites()
249{
250 return std::make_unique<CFavorites>();
251}
252