1#include "serverbrowser_ping_cache.h"
2
3#include <base/net.h>
4#include <base/str.h>
5
6#include <engine/console.h>
7#include <engine/sqlite.h>
8
9#include <sqlite3.h>
10
11#include <unordered_map>
12#include <vector>
13
14class CServerBrowserPingCache : public IServerBrowserPingCache
15{
16public:
17 class CEntry
18 {
19 public:
20 NETADDR m_Addr;
21 int m_Ping;
22 };
23
24 CServerBrowserPingCache(IConsole *pConsole, IStorage *pStorage);
25 ~CServerBrowserPingCache() override = default;
26
27 void Load() override;
28
29 int NumEntries() const override;
30 void CachePing(const NETADDR &Addr, int Ping) override;
31 int GetPing(const NETADDR *pAddrs, int NumAddrs) const override;
32
33private:
34 IConsole *m_pConsole;
35
36 CSqlite m_pDisk;
37 CSqliteStmt m_pLoadStmt;
38 CSqliteStmt m_pStoreStmt;
39
40 std::unordered_map<NETADDR, int> m_Entries;
41};
42
43CServerBrowserPingCache::CServerBrowserPingCache(IConsole *pConsole, IStorage *pStorage) :
44 m_pConsole(pConsole)
45{
46 m_pDisk = SqliteOpen(pStorage, pPath: "ddnet-cache.sqlite3");
47 if(!m_pDisk)
48 {
49 pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "serverbrowse_ping_cache", pStr: "failed to open ddnet-cache.sqlite3");
50 return;
51 }
52 sqlite3 *pSqlite = m_pDisk.get();
53 static const char TABLE[] = "CREATE TABLE IF NOT EXISTS server_pings (ip_address TEXT PRIMARY KEY NOT NULL, ping INTEGER NOT NULL, utc_timestamp TEXT NOT NULL)";
54 if(SQLITE_HANDLE_ERROR(sqlite3_exec(pSqlite, TABLE, nullptr, nullptr, nullptr)))
55 {
56 m_pDisk = nullptr;
57 pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "serverbrowse_ping_cache", pStr: "failed to create server_pings table");
58 return;
59 }
60 m_pLoadStmt = SqlitePrepare(pSqlite, pStatement: "SELECT ip_address, ping FROM server_pings");
61 m_pStoreStmt = SqlitePrepare(pSqlite, pStatement: "INSERT OR REPLACE INTO server_pings (ip_address, ping, utc_timestamp) VALUES (?, ?, datetime('now'))");
62}
63
64void CServerBrowserPingCache::Load()
65{
66 if(m_pDisk)
67 {
68 std::vector<CEntry> vNewEntries;
69
70 sqlite3 *pSqlite = m_pDisk.get();
71 IConsole *pConsole = m_pConsole;
72 bool Error = false;
73 bool WarnedForBadAddress = false;
74 Error = Error || !m_pLoadStmt;
75 Error = Error || SQLITE_HANDLE_ERROR(sqlite3_reset(m_pLoadStmt.get())) != SQLITE_OK;
76 while(!Error)
77 {
78 int StepResult = SQLITE_HANDLE_ERROR(sqlite3_step(m_pLoadStmt.get()));
79 if(StepResult == SQLITE_DONE)
80 {
81 break;
82 }
83 else if(StepResult == SQLITE_ROW)
84 {
85 const char *pIpAddress = (const char *)sqlite3_column_text(m_pLoadStmt.get(), iCol: 0);
86 int Ping = sqlite3_column_int(m_pLoadStmt.get(), iCol: 1);
87 NETADDR Addr;
88 if(net_addr_from_str(addr: &Addr, string: pIpAddress))
89 {
90 if(!WarnedForBadAddress)
91 {
92 char aBuf[64];
93 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "invalid address: %s", pIpAddress);
94 pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "serverbrowse_ping_cache", pStr: aBuf);
95 WarnedForBadAddress = true;
96 }
97 continue;
98 }
99 vNewEntries.push_back(x: CEntry{.m_Addr: Addr, .m_Ping: Ping});
100 }
101 else
102 {
103 Error = true;
104 }
105 }
106 if(Error)
107 {
108 pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "serverbrowse_ping_cache", pStr: "failed to load ping cache");
109 return;
110 }
111 for(const auto &Entry : vNewEntries)
112 {
113 m_Entries[Entry.m_Addr] = Entry.m_Ping;
114 }
115 }
116}
117
118int CServerBrowserPingCache::NumEntries() const
119{
120 return m_Entries.size();
121}
122
123void CServerBrowserPingCache::CachePing(const NETADDR &Addr, int Ping)
124{
125 NETADDR StoredAddr = Addr;
126 StoredAddr.type &= ~NETTYPE_TW7;
127 StoredAddr.port = 0;
128 m_Entries[StoredAddr] = Ping;
129 if(m_pDisk)
130 {
131 sqlite3 *pSqlite = m_pDisk.get();
132 IConsole *pConsole = m_pConsole;
133 char aAddr[NETADDR_MAXSTRSIZE];
134 net_addr_str(addr: &StoredAddr, string: aAddr, max_length: sizeof(aAddr), add_port: false);
135
136 bool Error = false;
137 Error = Error || !m_pStoreStmt;
138 Error = Error || SQLITE_HANDLE_ERROR(sqlite3_reset(m_pStoreStmt.get())) != SQLITE_OK;
139 Error = Error || SQLITE_HANDLE_ERROR(sqlite3_bind_text(m_pStoreStmt.get(), 1, aAddr, -1, SQLITE_STATIC)) != SQLITE_OK;
140 Error = Error || SQLITE_HANDLE_ERROR(sqlite3_bind_int(m_pStoreStmt.get(), 2, Ping)) != SQLITE_OK;
141 Error = Error || SQLITE_HANDLE_ERROR(sqlite3_step(m_pStoreStmt.get())) != SQLITE_DONE;
142 if(Error)
143 {
144 pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "serverbrowse_ping_cache", pStr: "failed to store ping");
145 }
146 }
147}
148
149int CServerBrowserPingCache::GetPing(const NETADDR *pAddrs, int NumAddrs) const
150{
151 int Ping = -1;
152 for(int i = 0; i < NumAddrs; i++)
153 {
154 NETADDR LookupAddr = pAddrs[i];
155 LookupAddr.type &= ~NETTYPE_TW7;
156 LookupAddr.port = 0;
157 auto Entry = m_Entries.find(key: LookupAddr);
158 if(Entry == m_Entries.end())
159 {
160 continue;
161 }
162 if(Ping == -1 || Entry->second < Ping)
163 {
164 Ping = Entry->second;
165 }
166 }
167 return Ping;
168}
169
170IServerBrowserPingCache *CreateServerBrowserPingCache(IConsole *pConsole, IStorage *pStorage)
171{
172 return new CServerBrowserPingCache(pConsole, pStorage);
173}
174