1//
2// This file is part of the aMule Project.
3//
4// Copyright (c) 2008-2011 D��vai Tam��s ( gonosztopi@amule.org )
5// Copyright (c) 2004-2011 aMule Team ( admin@amule.org / http://www.amule.org )
6// Copyright (c) 2003-2011 Barry Dunne (http://www.emule-project.net)
7//
8// Any parts of this program derived from the xMule, lMule or eMule project,
9// or contributed by third-party developers are copyrighted by their
10// respective authors.
11//
12// This program is free software; you can redistribute it and/or modify
13// it under the terms of the GNU General Public License as published by
14// the Free Software Foundation; either version 2 of the License, or
15// (at your option) any later version.
16//
17// This program is distributed in the hope that it will be useful,
18// but WITHOUT ANY WARRANTY; without even the implied warranty of
19// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20// GNU General Public License for more details.
21//
22// You should have received a copy of the GNU General Public License
23// along with this program; if not, write to the Free Software
24// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
25//
26
27// Note To Mods //
28/*
29Please do not change anything here and release it..
30There is going to be a new forum created just for the Kademlia side of the client..
31If you feel there is an error or a way to improve something, please
32post it in the forum first and let us look at it.. If it is a real improvement,
33it will be added to the offical client.. Changing something without knowing
34what all it does can cause great harm to the network if released in mass form..
35Any mod that changes anything within the Kademlia side will not be allowed to advertise
36there client on the eMule forum..
37*/
38
39#include "Entry.h"
40#include <common/Macros.h>
41#include <tags/FileTags.h>
42#include <protocol/kad/Constants.h>
43#include "Indexed.h"
44#include "../../SafeFile.h"
45#include "../../GetTickCount.h"
46#include "../../Logger.h"
47#include "../../NetworkFunctions.h"
48
49using namespace Kademlia;
50
51CKeyEntry::GlobalPublishIPMap	CKeyEntry::s_globalPublishIPs;
52
53
54/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
55////// CEntry
56CEntry::~CEntry()
57{
58	deleteTagPtrListEntries(&m_taglist);
59}
60
61CEntry* CEntry::Copy() const
62{
63	CEntry* entry = new CEntry();
64	for (FileNameList::const_iterator it = m_filenames.begin(); it != m_filenames.end(); ++it) {
65		entry->m_filenames.push_back(*it);
66	}
67	entry->m_uIP = m_uIP;
68	entry->m_uKeyID.SetValue(m_uKeyID);
69	entry->m_tLifeTime = m_tLifeTime;
70	entry->m_uSize = m_uSize;
71	entry->m_bSource = m_bSource;
72	entry->m_uSourceID.SetValue(m_uSourceID);
73	entry->m_uTCPport = m_uTCPport;
74	entry->m_uUDPport = m_uUDPport;
75	for (TagPtrList::const_iterator it = m_taglist.begin(); it != m_taglist.end(); ++it) {
76		entry->m_taglist.push_back((*it)->CloneTag());
77	}
78	return entry;
79}
80
81bool CEntry::GetIntTagValue(const wxString& tagname, uint64_t& value, bool includeVirtualTags) const
82{
83	for (TagPtrList::const_iterator it = m_taglist.begin(); it != m_taglist.end(); ++it) {
84		if ((*it)->IsInt() && ((*it)->GetName() == tagname)) {
85			value = (*it)->GetInt();
86			return true;
87		}
88	}
89
90	if (includeVirtualTags) {
91		// SizeTag is not stored anymore, but queried in some places
92		if (tagname == TAG_FILESIZE) {
93			value = m_uSize;
94			return true;
95		}
96	}
97	value = 0;
98	return false;
99}
100
101wxString CEntry::GetStrTagValue(const wxString& tagname) const
102{
103	for (TagPtrList::const_iterator it = m_taglist.begin(); it != m_taglist.end(); ++it) {
104		if (((*it)->GetName() == tagname) && (*it)->IsStr()) {
105			return (*it)->GetStr();
106		}
107	}
108	return wxEmptyString;
109}
110
111void CEntry::SetFileName(const wxString& name)
112{
113	if (!m_filenames.empty()) {
114		wxFAIL;
115		m_filenames.clear();
116	}
117	sFileNameEntry sFN = { name, 1 };
118	m_filenames.push_front(sFN);
119}
120
121wxString CEntry::GetCommonFileName() const
122{
123	// return the filename on which most publishers seem to agree on
124	// due to the counting, this doesn't has to be excact, we just want to make sure to not use a filename which just
125	// a few bad publishers used and base or search matching and answering on this, instead of the most popular name
126	// Note: The Index values are not the acutal numbers of publishers, but just a relativ number to compare to other entries
127	FileNameList::const_iterator result = m_filenames.end();
128	uint32_t highestPopularityIndex = 0;
129	for (FileNameList::const_iterator it = m_filenames.begin(); it != m_filenames.end(); ++it) {
130		if (it->m_popularityIndex > highestPopularityIndex) {
131			highestPopularityIndex = it->m_popularityIndex;
132			result = it;
133		}
134	}
135	wxString strResult(result != m_filenames.end() ? result->m_filename : wxString(wxEmptyString));
136	wxASSERT(!strResult.IsEmpty() || m_filenames.empty());
137	return strResult;
138}
139
140void CEntry::WriteTagListInc(CFileDataIO* data, uint32_t increaseTagNumber)
141{
142	// write taglist and add name + size tag
143	wxCHECK_RET(data != NULL, wxT("data must not be NULL"));
144
145	uint32_t count = GetTagCount() + increaseTagNumber;	// will include name and size tag in the count if needed
146	wxASSERT(count <= 0xFF);
147	data->WriteUInt8((uint8_t)count);
148
149	if (!GetCommonFileName().IsEmpty()){
150		wxASSERT(count > m_taglist.size());
151		data->WriteTag(CTagString(TAG_FILENAME, GetCommonFileName()));
152	}
153	if (m_uSize != 0){
154		wxASSERT(count > m_taglist.size());
155		data->WriteTag(CTagVarInt(TAG_FILESIZE, m_uSize));
156	}
157
158	for (TagPtrList::const_iterator it = m_taglist.begin(); it != m_taglist.end(); ++it) {
159		data->WriteTag(**it);
160	}
161}
162
163
164/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
165////// CKeyEntry
166CKeyEntry::CKeyEntry()
167{
168	m_publishingIPs = NULL;
169	m_trustValue = 0;
170	m_lastTrustValueCalc = 0;
171}
172
173CKeyEntry::~CKeyEntry()
174{
175	if (m_publishingIPs != NULL) {
176		for (PublishingIPList::const_iterator it = m_publishingIPs->begin(); it != m_publishingIPs->end(); ++it) {
177			AdjustGlobalPublishTracking(it->m_ip, false, wxT("instance delete"));
178		}
179		delete m_publishingIPs;
180		m_publishingIPs = NULL;
181	}
182}
183
184bool CKeyEntry::SearchTermsMatch(const SSearchTerm* searchTerm) const
185{
186	// boolean operators
187	if (searchTerm->type == SSearchTerm::AND) {
188		return SearchTermsMatch(searchTerm->left) && SearchTermsMatch(searchTerm->right);
189	}
190
191	if (searchTerm->type == SSearchTerm::OR) {
192		return SearchTermsMatch(searchTerm->left) || SearchTermsMatch(searchTerm->right);
193	}
194
195	if (searchTerm->type == SSearchTerm::NOT) {
196		return SearchTermsMatch(searchTerm->left) && !SearchTermsMatch(searchTerm->right);
197	}
198
199	// word which is to be searched in the file name (and in additional meta data as done by some ed2k servers???)
200	if (searchTerm->type == SSearchTerm::String) {
201		int strSearchTerms = searchTerm->astr->GetCount();
202		if (strSearchTerms == 0) {
203			return false;
204		}
205		// if there are more than one search strings specified (e.g. "aaa bbb ccc") the entire string is handled
206		// like "aaa AND bbb AND ccc". search all strings from the string search term in the tokenized list of
207		// the file name. all strings of string search term have to be found (AND)
208		wxString commonFileNameLower(GetCommonFileNameLowerCase());
209		for (int i = 0; i < strSearchTerms; i++) {
210			// this will not give the same results as when tokenizing the filename string, but it is 20 times faster.
211			if (commonFileNameLower.Find((*(searchTerm->astr))[i]) == -1) {
212				return false;
213			}
214		}
215		return true;
216	}
217
218	if (searchTerm->type == SSearchTerm::MetaTag) {
219		if (searchTerm->tag->GetType() == 2) {	// meta tags with string values
220			if (searchTerm->tag->GetName() == TAG_FILEFORMAT) {
221				// 21-Sep-2006 []: Special handling for TAG_FILEFORMAT which is already part
222				// of the filename and thus does not need to get published nor stored explicitly,
223				wxString commonFileName(GetCommonFileName());
224				int ext = commonFileName.Find(wxT('.'), true);
225				if (ext != wxNOT_FOUND) {
226					return commonFileName.Mid(ext + 1).CmpNoCase(searchTerm->tag->GetStr()) == 0;
227				}
228			} else {
229				for (TagPtrList::const_iterator it = m_taglist.begin(); it != m_taglist.end(); ++it) {
230					if ((*it)->IsStr() && searchTerm->tag->GetName() == (*it)->GetName()) {
231						return (*it)->GetStr().CmpNoCase(searchTerm->tag->GetStr()) == 0;
232					}
233				}
234			}
235		}
236	} else if (searchTerm->type == SSearchTerm::OpGreaterEqual) {
237		if (searchTerm->tag->IsInt()) {	// meta tags with integer values
238			uint64_t value;
239			if (GetIntTagValue(searchTerm->tag->GetName(), value, true)) {
240				return value >= searchTerm->tag->GetInt();
241			}
242		} else if (searchTerm->tag->IsFloat()) {	// meta tags with float values
243			for (TagPtrList::const_iterator it = m_taglist.begin(); it != m_taglist.end(); ++it) {
244				if ((*it)->IsFloat() && searchTerm->tag->GetName() == (*it)->GetName()) {
245					return (*it)->GetFloat() >= searchTerm->tag->GetFloat();
246				}
247			}
248		}
249	} else if (searchTerm->type == SSearchTerm::OpLessEqual) {
250		if (searchTerm->tag->IsInt()) {	// meta tags with integer values
251			uint64_t value;
252			if (GetIntTagValue(searchTerm->tag->GetName(), value, true)) {
253				return value <= searchTerm->tag->GetInt();
254			}
255		} else if (searchTerm->tag->IsFloat()) {	// meta tags with float values
256			for (TagPtrList::const_iterator it = m_taglist.begin(); it != m_taglist.end(); ++it) {
257				if ((*it)->IsFloat() && searchTerm->tag->GetName() == (*it)->GetName()) {
258					return (*it)->GetFloat() <= searchTerm->tag->GetFloat();
259				}
260			}
261		}
262	} else if (searchTerm->type == SSearchTerm::OpGreater) {
263		if (searchTerm->tag->IsInt()) {	// meta tags with integer values
264			uint64_t value;
265			if (GetIntTagValue(searchTerm->tag->GetName(), value, true)) {
266				return value > searchTerm->tag->GetInt();
267			}
268		} else if (searchTerm->tag->IsFloat()) {	// meta tags with float values
269			for (TagPtrList::const_iterator it = m_taglist.begin(); it != m_taglist.end(); ++it) {
270				if ((*it)->IsFloat() && searchTerm->tag->GetName() == (*it)->GetName()) {
271					return (*it)->GetFloat() > searchTerm->tag->GetFloat();
272				}
273			}
274		}
275	} else if (searchTerm->type == SSearchTerm::OpLess) {
276		if (searchTerm->tag->IsInt()) {	// meta tags with integer values
277			uint64_t value;
278			if (GetIntTagValue(searchTerm->tag->GetName(), value, true)) {
279				return value < searchTerm->tag->GetInt();
280			}
281		} else if (searchTerm->tag->IsFloat()) {	// meta tags with float values
282			for (TagPtrList::const_iterator it = m_taglist.begin(); it != m_taglist.end(); ++it) {
283				if ((*it)->IsFloat() && searchTerm->tag->GetName() == (*it)->GetName()) {
284					return (*it)->GetFloat() < searchTerm->tag->GetFloat();
285				}
286			}
287		}
288	} else if (searchTerm->type == SSearchTerm::OpEqual) {
289		if (searchTerm->tag->IsInt()) {	// meta tags with integer values
290			uint64_t value;
291			if (GetIntTagValue(searchTerm->tag->GetName(), value, true)) {
292				return value == searchTerm->tag->GetInt();
293			}
294		} else if (searchTerm->tag->IsFloat()) {	// meta tags with float values
295			for (TagPtrList::const_iterator it = m_taglist.begin(); it != m_taglist.end(); ++it) {
296				if ((*it)->IsFloat() && searchTerm->tag->GetName() == (*it)->GetName()) {
297					return (*it)->GetFloat() == searchTerm->tag->GetFloat();
298				}
299			}
300		}
301	} else if (searchTerm->type == SSearchTerm::OpNotEqual) {
302		if (searchTerm->tag->IsInt()) {	// meta tags with integer values
303			uint64_t value;
304			if (GetIntTagValue(searchTerm->tag->GetName(), value, true)) {
305				return value != searchTerm->tag->GetInt();
306			}
307		} else if (searchTerm->tag->IsFloat()) {	// meta tags with float values
308			for (TagPtrList::const_iterator it = m_taglist.begin(); it != m_taglist.end(); ++it) {
309				if ((*it)->IsFloat() && searchTerm->tag->GetName() == (*it)->GetName()) {
310					return (*it)->GetFloat() != searchTerm->tag->GetFloat();
311				}
312			}
313		}
314	}
315
316	return false;
317}
318
319void CKeyEntry::AdjustGlobalPublishTracking(uint32_t ip, bool increase, const wxString& DEBUG_ONLY(dbgReason))
320{
321	uint32_t count = 0;
322	bool found = false;
323	GlobalPublishIPMap::const_iterator it = s_globalPublishIPs.find(ip & 0xFFFFFF00 /* /24 netmask, take care of endian if needed */ );
324	if (it != s_globalPublishIPs.end()) {
325		count = it->second;
326		found = true;
327	}
328
329	if (increase) {
330		count++;
331	} else {
332		count--;
333	}
334
335	if (count > 0) {
336		s_globalPublishIPs[ip & 0xFFFFFF00] = count;
337	} else if (found) {
338		s_globalPublishIPs.erase(ip & 0xFFFFFF00);
339	} else {
340		wxFAIL;
341	}
342#ifdef __DEBUG__
343	if (!dbgReason.IsEmpty()) {
344		AddDebugLogLineN(logKadEntryTracking, CFormat(wxT("%s %s (%s) - (%s), new count %u"))
345			% (increase ? wxT("Adding") : wxT("Removing")) % KadIPToString(ip & 0xFFFFFF00) % KadIPToString(ip) % dbgReason % count);
346	}
347#endif
348}
349
350void CKeyEntry::MergeIPsAndFilenames(CKeyEntry* fromEntry)
351{
352	// this is called when replacing a stored entry with a refreshed one.
353	// we want to take over the tracked IPs and the different filenames from the old entry, the rest is still
354	// "overwritten" with the refreshed values. This might be not perfect for the taglist in some cases, but we can't afford
355	// to store hundreds of taglists to figure out the best one like we do for the filenames now
356	if (m_publishingIPs != NULL) { // This instance needs to be a new entry, otherwise we don't want/need to merge
357		wxASSERT(fromEntry == NULL);
358		wxASSERT(!m_publishingIPs->empty());
359		wxASSERT(!m_filenames.empty());
360		return;
361	}
362
363	bool refresh = false;
364	if (fromEntry == NULL || fromEntry->m_publishingIPs == NULL) {
365		wxASSERT(fromEntry == NULL);
366		// if called with NULL, this is a complete new entry and we need to initalize our lists
367		if (m_publishingIPs == NULL) {
368			m_publishingIPs = new PublishingIPList();
369		}
370		// update the global track map below
371	} else {
372		// merge the tracked IPs, add this one if not already on the list
373		m_publishingIPs = fromEntry->m_publishingIPs;
374		fromEntry->m_publishingIPs = NULL;
375		bool fastRefresh = false;
376		for (PublishingIPList::iterator it = m_publishingIPs->begin(); it != m_publishingIPs->end(); ++it) {
377			if (it->m_ip == m_uIP) {
378				refresh = true;
379				if ((time(NULL) - it->m_lastPublish) < (KADEMLIAREPUBLISHTIMES - HR2S(1))) {
380					AddDebugLogLineN(logKadEntryTracking, wxT("FastRefresh publish, ip: ") + KadIPToString(m_uIP));
381					fastRefresh = true; // refreshed faster than expected, will not count into filenamepopularity index
382				}
383				it->m_lastPublish = time(NULL);
384				m_publishingIPs->push_back(*it);
385				m_publishingIPs->erase(it);
386				break;
387			}
388		}
389
390		// copy over trust value, in case we don't want to recalculate
391		m_trustValue = fromEntry->m_trustValue;
392		m_lastTrustValueCalc = fromEntry->m_lastTrustValueCalc;
393
394		// copy over the different names, if they are different the one we have right now
395		wxASSERT(m_filenames.size() == 1); // we should have only one name here, since it's the entry from one single source
396		sFileNameEntry currentName = { wxEmptyString, 0 };
397		if (m_filenames.size() != 0) {
398			currentName = m_filenames.front();
399			m_filenames.pop_front();
400		}
401
402		bool duplicate = false;
403		for (FileNameList::iterator it = fromEntry->m_filenames.begin(); it != fromEntry->m_filenames.end(); ++it) {
404			sFileNameEntry nameToCopy = *it;
405			if (currentName.m_filename.CmpNoCase(nameToCopy.m_filename) == 0) {
406				// the filename of our new entry matches with our old, increase the popularity index for the old one
407				duplicate = true;
408				if (!fastRefresh) {
409					nameToCopy.m_popularityIndex++;
410				}
411			}
412			m_filenames.push_back(nameToCopy);
413		}
414		if (!duplicate) {
415			m_filenames.push_back(currentName);
416		}
417	}
418
419	// if this was a refresh done, otherwise update the global track map
420	if (!refresh) {
421		wxASSERT(m_uIP != 0);
422		sPublishingIP add = { m_uIP, time(NULL) };
423		m_publishingIPs->push_back(add);
424
425		// add the publisher to the tacking list
426		AdjustGlobalPublishTracking(m_uIP, true, wxT("new publisher"));
427
428		// we keep track of max 100 IPs, in order to avoid too much time for calculation/storing/loading.
429		if (m_publishingIPs->size() > 100) {
430			sPublishingIP curEntry = m_publishingIPs->front();
431			m_publishingIPs->pop_front();
432			AdjustGlobalPublishTracking(curEntry.m_ip, false, wxT("more than 100 publishers purge"));
433		}
434
435		// since we added a new publisher, we want to (re)calculate the trust value for this entry
436		ReCalculateTrustValue();
437	}
438	AddDebugLogLineN(logKadEntryTracking, CFormat(wxT("Indexed Keyword, Refresh: %s, Current Publisher: %s, Total Publishers: %u, Total different Names: %u, TrustValue: %.2f, file: %s"))
439		% (refresh ? wxT("Yes") : wxT("No")) % KadIPToString(m_uIP) % m_publishingIPs->size() % m_filenames.size() % m_trustValue % m_uSourceID.ToHexString());
440}
441
442void CKeyEntry::ReCalculateTrustValue()
443{
444#define PUBLISHPOINTSSPERSUBNET	10.0
445	// The trustvalue is supposed to be an indicator how trustworthy/important (or spammy) this entry is and lies between 0 and ~10000,
446	// but mostly we say everything below 1 is bad, everything above 1 is good. It is calculated by looking at how many different
447	// IPs/24 have published this entry and how many entries each of those IPs have.
448	// Each IP/24 has x (say 3) points. This means if one IP publishes 3 different entries without any other IP publishing those entries,
449	// each of those entries will have 3 / 3 = 1 Trustvalue. Thats fine. If it publishes 6 alone, each entry has 3 / 6 = 0.5 trustvalue - not so good
450	// However if there is another publisher for entry 5, which only publishes this entry then we have 3/6 + 3/1 = 3.5 trustvalue for this entry
451	//
452	// What's the point? With this rating we try to avoid getting spammed with entries for a given keyword by a small IP range, which blends out
453	// all other entries for this keyword do to its amount as well as giving an indicator for the searcher. So if we are the node to index "Knoppix", and someone
454	// from 1 IP publishes 500 times "knoppix casino 500% bonus.txt", all those entries will have a trustvalue of 0.006 and we make sure that
455	// on search requests for knoppix, those entries are only returned after all entries with a trustvalue > 1 were sent (if there is still space).
456	//
457	// Its important to note that entry with < 1 do NOT get ignored or singled out, this only comes into play if we have 300 more results for
458	// a search request rating > 1
459	wxCHECK_RET(m_publishingIPs != NULL, wxT("No publishing IPs?"));
460
461	m_lastTrustValueCalc = ::GetTickCount();
462	m_trustValue = 0;
463	wxASSERT(!m_publishingIPs->empty());
464	for (PublishingIPList::iterator it = m_publishingIPs->begin(); it != m_publishingIPs->end(); ++it) {
465		sPublishingIP curEntry = *it;
466		uint32_t count = 0;
467		GlobalPublishIPMap::const_iterator itMap = s_globalPublishIPs.find(curEntry.m_ip & 0xFFFFFF00 /* /24 netmask, take care of endian if needed*/);
468		if (itMap != s_globalPublishIPs.end()) {
469			count = itMap->second;
470		}
471		if (count > 0) {
472			m_trustValue += PUBLISHPOINTSSPERSUBNET / count;
473		} else {
474			AddDebugLogLineN(logKadEntryTracking, wxT("Inconsistency in RecalcualteTrustValue()"));
475			wxFAIL;
476		}
477	}
478}
479
480double CKeyEntry::GetTrustValue()
481{
482	// update if last calcualtion is too old, will assert if this entry is not supposed to have a trustvalue
483	if (::GetTickCount() - m_lastTrustValueCalc > MIN2MS(10)) {
484		ReCalculateTrustValue();
485	}
486	return m_trustValue;
487}
488
489void CKeyEntry::CleanUpTrackedPublishers()
490{
491	if (m_publishingIPs == NULL) {
492		return;
493	}
494
495	time_t now = time(NULL);
496	while (!m_publishingIPs->empty()) {
497		// entries are ordered, older ones first
498		sPublishingIP curEntry = m_publishingIPs->front();
499		if (now - curEntry.m_lastPublish > KADEMLIAREPUBLISHTIMEK) {
500			AdjustGlobalPublishTracking(curEntry.m_ip, false, wxT("cleanup"));
501			m_publishingIPs->pop_front();
502		} else {
503			break;
504		}
505	}
506}
507
508void CKeyEntry::WritePublishTrackingDataToFile(CFileDataIO* data)
509{
510	// format: <Names_Count 4><{<Name string><PopularityIndex 4>} Names_Count><PublisherCount 4><{<IP 4><Time 4>} PublisherCount>
511	data->WriteUInt32((uint32_t)m_filenames.size());
512	for (FileNameList::const_iterator it = m_filenames.begin(); it != m_filenames.end(); ++it) {
513		data->WriteString(it->m_filename, utf8strRaw, 2);
514		data->WriteUInt32(it->m_popularityIndex);
515	}
516
517	if (m_publishingIPs != NULL) {
518		data->WriteUInt32((uint32_t)m_publishingIPs->size());
519		for (PublishingIPList::const_iterator it = m_publishingIPs->begin(); it != m_publishingIPs->end(); ++it) {
520			wxASSERT(it->m_ip != 0);
521			data->WriteUInt32(it->m_ip);
522			data->WriteUInt32((uint32_t)it->m_lastPublish);
523		}
524	} else {
525		wxFAIL;
526		data->WriteUInt32(0);
527	}
528}
529
530void CKeyEntry::ReadPublishTrackingDataFromFile(CFileDataIO* data)
531{
532	// format: <Names_Count 4><{<Name string><PopularityIndex 4>} Names_Count><PublisherCount 4><{<IP 4><Time 4>} PublisherCount>
533	wxASSERT(m_filenames.empty());
534	uint32_t nameCount = data->ReadUInt32();
535	for (uint32_t i = 0; i < nameCount; i++) {
536		sFileNameEntry toAdd;
537		toAdd.m_filename = data->ReadString(true, 2);
538		toAdd.m_popularityIndex = data->ReadUInt32();
539		m_filenames.push_back(toAdd);
540	}
541
542	wxASSERT(m_publishingIPs == NULL);
543	m_publishingIPs = new PublishingIPList();
544	uint32_t ipCount = data->ReadUInt32();
545#ifdef __WXDEBUG__
546	uint32_t dbgLastTime = 0;
547#endif
548	for (uint32_t i = 0; i < ipCount; i++) {
549		sPublishingIP toAdd;
550		toAdd.m_ip = data->ReadUInt32();
551		wxASSERT(toAdd.m_ip != 0);
552		toAdd.m_lastPublish = data->ReadUInt32();
553#ifdef __WXDEBUG__
554		wxASSERT(dbgLastTime <= (uint32_t)toAdd.m_lastPublish); // should always be sorted oldest first
555		dbgLastTime = toAdd.m_lastPublish;
556#endif
557
558		AdjustGlobalPublishTracking(toAdd.m_ip, true, wxEmptyString);
559
560		m_publishingIPs->push_back(toAdd);
561	}
562	ReCalculateTrustValue();
563// #ifdef __DEBUG__
564// 	if (GetTrustValue() < 1.0) {
565// 		AddDebugLogLineN(logKadEntryTracking,CFormat(wxT("Loaded %u different names, %u different publishIPs (trustvalue = %.2f) for file %s"))
566// 			% nameCount % ipCount % GetTrustValue() % m_uSourceID.ToHexString());
567// 	}
568// #endif
569}
570
571void CKeyEntry::DirtyDeletePublishData()
572{
573	// instead of deleting our publishers properly in the destructor with decreasing the count in the global map
574	// we just remove them, and trust that the caller in the end also resets the global map, so the
575	// kad shutdown is speed up a bit
576	delete m_publishingIPs;
577	m_publishingIPs = NULL;
578}
579
580void CKeyEntry::WriteTagListWithPublishInfo(CFileDataIO* data)
581{
582	if (m_publishingIPs == NULL || m_publishingIPs->size() == 0) {
583		wxFAIL;
584		WriteTagList(data);
585		return;
586	}
587
588	// here we add a tag including how many publishers this entry has, the trustvalue and how many different names are known
589	// this is supposed to get used in later versions as an indicator for the user how valid this result is (of course this tag
590	// alone cannot be trusted 100%, because we could be a bad node, but it's a part of the puzzle)
591
592	WriteTagListInc(data, 1); // write the standard taglist but increase the tagcount by one
593
594	uint32_t trust = (uint16_t)(GetTrustValue() * 100);
595	uint32_t publishers = m_publishingIPs->size() & 0xFF /*% 256*/;
596	uint32_t names = m_filenames.size() & 0xFF /*% 256*/;
597	// 32 bit tag: <namecount uint8><publishers uint8><trustvalue*100 uint16>
598	uint32_t tagValue = (names << 24) | (publishers << 16) | trust;
599	data->WriteTag(CTagVarInt(TAG_PUBLISHINFO, tagValue));
600}
601