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(pConsole, 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(pConsole, pSqlite, pStatement: "SELECT ip_address, ping FROM server_pings");
61 m_pStoreStmt = SqlitePrepare(pConsole, 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 while(!Error)
76 {
77 int StepResult = SQLITE_HANDLE_ERROR(sqlite3_step(m_pLoadStmt.get()));
78 if(StepResult == SQLITE_DONE)
79 {
80 break;
81 }
82 else if(StepResult == SQLITE_ROW)
83 {
84 const char *pIpAddress = (const char *)sqlite3_column_text(m_pLoadStmt.get(), iCol: 0);
85 int Ping = sqlite3_column_int(m_pLoadStmt.get(), iCol: 1);
86 NETADDR Addr;
87 if(net_addr_from_str(addr: &Addr, string: pIpAddress))
88 {
89 if(!WarnedForBadAddress)
90 {
91 char aBuf[64];
92 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "invalid address: %s", pIpAddress);
93 pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "serverbrowse_ping_cache", pStr: aBuf);
94 WarnedForBadAddress = true;
95 }
96 continue;
97 }
98 vNewEntries.push_back(x: CEntry{.m_Addr: Addr, .m_Ping: Ping});
99 }
100 else
101 {
102 Error = true;
103 }
104 }
105 if(Error)
106 {
107 pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "serverbrowse_ping_cache", pStr: "failed to load ping cache");
108 return;
109 }
110 for(const auto &Entry : vNewEntries)
111 {
112 m_Entries[Entry.m_Addr] = Entry.m_Ping;
113 }
114 }
115}
116
117int CServerBrowserPingCache::NumEntries() const
118{
119 return m_Entries.size();
120}
121
122void CServerBrowserPingCache::CachePing(const NETADDR &Addr, int Ping)
123{
124 NETADDR StoredAddr = Addr;
125 StoredAddr.type &= ~NETTYPE_TW7;
126 StoredAddr.port = 0;
127 m_Entries[StoredAddr] = Ping;
128 if(m_pDisk)
129 {
130 sqlite3 *pSqlite = m_pDisk.get();
131 IConsole *pConsole = m_pConsole;
132 char aAddr[NETADDR_MAXSTRSIZE];
133 net_addr_str(addr: &StoredAddr, string: aAddr, max_length: sizeof(aAddr), add_port: false);
134
135 bool Error = false;
136 Error = Error || !m_pStoreStmt;
137 Error = Error || SQLITE_HANDLE_ERROR(sqlite3_reset(m_pStoreStmt.get())) != SQLITE_OK;
138 Error = Error || SQLITE_HANDLE_ERROR(sqlite3_bind_text(m_pStoreStmt.get(), 1, aAddr, -1, SQLITE_STATIC)) != SQLITE_OK;
139 Error = Error || SQLITE_HANDLE_ERROR(sqlite3_bind_int(m_pStoreStmt.get(), 2, Ping)) != SQLITE_OK;
140 Error = Error || SQLITE_HANDLE_ERROR(sqlite3_step(m_pStoreStmt.get())) != SQLITE_DONE;
141 if(Error)
142 {
143 pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "serverbrowse_ping_cache", pStr: "failed to store ping");
144 }
145 }
146}
147
148int CServerBrowserPingCache::GetPing(const NETADDR *pAddrs, int NumAddrs) const
149{
150 int Ping = -1;
151 for(int i = 0; i < NumAddrs; i++)
152 {
153 NETADDR LookupAddr = pAddrs[i];
154 LookupAddr.type &= ~NETTYPE_TW7;
155 LookupAddr.port = 0;
156 auto Entry = m_Entries.find(x: LookupAddr);
157 if(Entry == m_Entries.end())
158 {
159 continue;
160 }
161 if(Ping == -1 || Entry->second < Ping)
162 {
163 Ping = Entry->second;
164 }
165 }
166 return Ping;
167}
168
169IServerBrowserPingCache *CreateServerBrowserPingCache(IConsole *pConsole, IStorage *pStorage)
170{
171 return new CServerBrowserPingCache(pConsole, pStorage);
172}
173