1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include "snapshot.h"
4
5#include "compression.h"
6#include "uuid_manager.h"
7
8#include <base/bytes.h>
9#include <base/dbg.h>
10#include <base/math.h>
11#include <base/mem.h>
12#include <base/str.h>
13
14#include <generated/protocol7.h>
15#include <generated/protocolglue.h>
16
17#include <cstdlib>
18#include <limits>
19
20// CSnapshot
21
22const CSnapshotItem *CSnapshot::GetItem(int Index) const
23{
24 return (const CSnapshotItem *)(DataStart() + Offsets()[Index]);
25}
26
27const CSnapshot CSnapshot::ms_EmptySnapshot;
28
29int CSnapshot::GetItemSize(int Index) const
30{
31 if(Index == m_NumItems - 1)
32 return (m_DataSize - Offsets()[Index]) - sizeof(CSnapshotItem);
33 return (Offsets()[Index + 1] - Offsets()[Index]) - sizeof(CSnapshotItem);
34}
35
36int CSnapshot::GetItemType(int Index) const
37{
38 int InternalType = GetItem(Index)->InternalType();
39 return GetExternalItemType(InternalType);
40}
41
42int CSnapshot::GetExternalItemType(int InternalType) const
43{
44 if(InternalType < OFFSET_UUID_TYPE)
45 {
46 return InternalType;
47 }
48
49 int TypeItemIndex = GetItemIndex(Key: InternalType); // NETOBJTYPE_EX
50 if(TypeItemIndex == -1 || GetItemSize(Index: TypeItemIndex) < (int)sizeof(CUuid))
51 {
52 return -1;
53 }
54 const CSnapshotItem *pTypeItem = GetItem(Index: TypeItemIndex);
55 CUuid Uuid;
56 for(size_t i = 0; i < sizeof(CUuid) / sizeof(int32_t); i++)
57 uint_to_bytes_be(bytes: &Uuid.m_aData[i * sizeof(int32_t)], value: pTypeItem->Data()[i]);
58
59 return g_UuidManager.LookupUuid(Uuid);
60}
61
62int CSnapshot::GetItemIndex(int Key) const
63{
64 // TODO: OPT: this should not be a linear search. very bad
65 for(int i = 0; i < m_NumItems; i++)
66 {
67 if(GetItem(Index: i)->Key() == Key)
68 return i;
69 }
70 return -1;
71}
72
73void CSnapshot::InvalidateItem(int Index)
74{
75 ((CSnapshotItem *)(DataStart() + Offsets()[Index]))->Invalidate();
76}
77
78const void *CSnapshot::FindItem(int Type, int Id) const
79{
80 int InternalType = Type;
81 if(Type >= OFFSET_UUID)
82 {
83 CUuid TypeUuid = g_UuidManager.GetUuid(Id: Type);
84 int aTypeUuidItem[sizeof(CUuid) / sizeof(int32_t)];
85 for(size_t i = 0; i < sizeof(CUuid) / sizeof(int32_t); i++)
86 aTypeUuidItem[i] = bytes_be_to_uint(bytes: &TypeUuid.m_aData[i * sizeof(int32_t)]);
87
88 bool Found = false;
89 for(int i = 0; i < m_NumItems; i++)
90 {
91 const CSnapshotItem *pItem = GetItem(Index: i);
92 if(pItem->InternalType() == 0 && pItem->Id() >= OFFSET_UUID_TYPE) // NETOBJTYPE_EX
93 {
94 if(mem_comp(a: pItem->Data(), b: aTypeUuidItem, size: sizeof(CUuid)) == 0)
95 {
96 InternalType = pItem->Id();
97 Found = true;
98 break;
99 }
100 }
101 }
102 if(!Found)
103 {
104 return nullptr;
105 }
106 }
107 int Index = GetItemIndex(Key: (InternalType << 16) | Id);
108 return Index < 0 ? nullptr : GetItem(Index)->Data();
109}
110
111rust::Slice<const int32_t> CSnapshot::AsSlice() const
112{
113 return rust::Slice<const int32_t>((const int32_t *)this, TotalSize() / sizeof(int32_t));
114}
115
116unsigned CSnapshot::Crc() const
117{
118 unsigned int Crc = 0;
119
120 for(int i = 0; i < m_NumItems; i++)
121 {
122 const CSnapshotItem *pItem = GetItem(Index: i);
123 int Size = GetItemSize(Index: i);
124
125 for(size_t b = 0; b < Size / sizeof(int32_t); b++)
126 Crc += pItem->Data()[b];
127 }
128 return Crc;
129}
130
131void CSnapshot::DebugDump() const
132{
133 dbg_msg(sys: "snapshot", fmt: "data_size=%d num_items=%d", m_DataSize, m_NumItems);
134 for(int i = 0; i < m_NumItems; i++)
135 {
136 const CSnapshotItem *pItem = GetItem(Index: i);
137 int Size = GetItemSize(Index: i);
138 dbg_msg(sys: "snapshot", fmt: "\ttype=%d id=%d", pItem->InternalType(), pItem->Id());
139 for(size_t b = 0; b < Size / sizeof(int32_t); b++)
140 dbg_msg(sys: "snapshot", fmt: "\t\t%3d %12d\t%08x", (int)b, pItem->Data()[b], pItem->Data()[b]);
141 }
142}
143
144bool CSnapshot::IsValid(size_t ActualSize) const
145{
146 // validate total size
147 if(ActualSize < sizeof(CSnapshot) ||
148 ActualSize > MAX_SIZE ||
149 m_NumItems < 0 ||
150 m_NumItems > MAX_ITEMS ||
151 m_DataSize < 0 ||
152 ActualSize != TotalSize())
153 {
154 return false;
155 }
156
157 // validate item offsets
158 const int *pOffsets = Offsets();
159 for(int Index = 0; Index < m_NumItems; Index++)
160 if(pOffsets[Index] < 0 || pOffsets[Index] > m_DataSize)
161 return false;
162
163 // validate item sizes
164 for(int Index = 0; Index < m_NumItems; Index++)
165 if(GetItemSize(Index) < 0) // the offsets must be validated before using this
166 return false;
167
168 return true;
169}
170
171// CSnapshotBuffer
172
173std::unique_ptr<CSnapshotBuffer> CSnapshotBuffer_New()
174{
175 return std::make_unique<CSnapshotBuffer>();
176}
177
178// CSnapshotStorage
179
180void CSnapshotStorage::Init()
181{
182 m_pFirst = nullptr;
183 m_pLast = nullptr;
184}
185
186void CSnapshotStorage::PurgeAll()
187{
188 while(m_pFirst)
189 {
190 CHolder *pNext = m_pFirst->m_pNext;
191 free(ptr: m_pFirst->m_pSnap);
192 free(ptr: m_pFirst->m_pAltSnap);
193 free(ptr: m_pFirst);
194 m_pFirst = pNext;
195 }
196 m_pLast = nullptr;
197}
198
199void CSnapshotStorage::PurgeUntil(int Tick)
200{
201 CHolder *pHolder = m_pFirst;
202
203 while(pHolder)
204 {
205 CHolder *pNext = pHolder->m_pNext;
206 if(pHolder->m_Tick >= Tick)
207 return; // no more to remove
208 free(ptr: pHolder->m_pSnap);
209 free(ptr: pHolder->m_pAltSnap);
210 free(ptr: pHolder);
211
212 // did we come to the end of the list?
213 if(!pNext)
214 break;
215
216 m_pFirst = pNext;
217 pNext->m_pPrev = nullptr;
218 pHolder = pNext;
219 }
220
221 // no more snapshots in storage
222 m_pFirst = nullptr;
223 m_pLast = nullptr;
224}
225
226void CSnapshotStorage::Add(int Tick, int64_t Tagtime, size_t DataSize, const void *pData, size_t AltDataSize, const void *pAltData)
227{
228 dbg_assert(DataSize <= (size_t)CSnapshot::MAX_SIZE, "Snapshot data size invalid");
229 dbg_assert(AltDataSize <= (size_t)CSnapshot::MAX_SIZE, "Alt snapshot data size invalid");
230
231 CHolder *pHolder = static_cast<CHolder *>(malloc(size: sizeof(CHolder)));
232 pHolder->m_Tick = Tick;
233 pHolder->m_Tagtime = Tagtime;
234
235 pHolder->m_pSnap = static_cast<CSnapshot *>(malloc(size: DataSize));
236 mem_copy(dest: pHolder->m_pSnap, source: pData, size: DataSize);
237 pHolder->m_SnapSize = DataSize;
238
239 if(AltDataSize) // create alternative if wanted
240 {
241 pHolder->m_pAltSnap = static_cast<CSnapshot *>(malloc(size: AltDataSize));
242 mem_copy(dest: pHolder->m_pAltSnap, source: pAltData, size: AltDataSize);
243 pHolder->m_AltSnapSize = AltDataSize;
244 }
245 else
246 {
247 pHolder->m_pAltSnap = nullptr;
248 pHolder->m_AltSnapSize = 0;
249 }
250
251 // link
252 pHolder->m_pNext = nullptr;
253 pHolder->m_pPrev = m_pLast;
254 if(m_pLast)
255 m_pLast->m_pNext = pHolder;
256 else
257 m_pFirst = pHolder;
258 m_pLast = pHolder;
259}
260
261int CSnapshotStorage::Get(int Tick, int64_t *pTagtime, const CSnapshot **ppData, const CSnapshot **ppAltData) const
262{
263 CHolder *pHolder = m_pFirst;
264
265 while(pHolder)
266 {
267 if(pHolder->m_Tick == Tick)
268 {
269 if(pTagtime)
270 *pTagtime = pHolder->m_Tagtime;
271 if(ppData)
272 *ppData = pHolder->m_pSnap;
273 if(ppAltData)
274 *ppAltData = pHolder->m_pAltSnap;
275 return pHolder->m_SnapSize;
276 }
277
278 pHolder = pHolder->m_pNext;
279 }
280
281 return -1;
282}
283