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