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