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