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