EvEmu  0.8.4
11 September 2021
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
LSCDB.cpp
Go to the documentation of this file.
1 /*
2  ------------------------------------------------------------------------------------
3  LICENSE:
4  ------------------------------------------------------------------------------------
5  This file is part of EVEmu: EVE Online Server Emulator
6  Copyright 2006 - 2021 The EVEmu Team
7  For the latest information visit https://evemu.dev
8  ------------------------------------------------------------------------------------
9  This program is free software; you can redistribute it and/or modify it under
10  the terms of the GNU Lesser General Public License as published by the Free Software
11  Foundation; either version 2 of the License, or (at your option) any later
12  version.
13 
14  This program is distributed in the hope that it will be useful, but WITHOUT
15  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
17 
18  You should have received a copy of the GNU Lesser General Public License along with
19  this program; if not, write to the Free Software Foundation, Inc., 59 Temple
20  Place - Suite 330, Boston, MA 02111-1307, USA, or go to
21  http://www.gnu.org/copyleft/lesser.txt.
22  ------------------------------------------------------------------------------------
23  Author: Zhur, Aknor Jaden
24 */
25 
28 #include "eve-server.h"
29 #include "Client.h"
30 #include "chat/LSCDB.h"
31 #include "chat/LSCService.h"
32 
33 void LSCDB::GetChannelNames(uint32 charID, std::vector<std::string> & names) {
34  DBQueryResult res;
35 
36  if (!sDatabase.RunQuery(res,
37  "SELECT"
38  " ch.characterName, "
39  " cr.corporationName "
40  " FROM chrCharacters AS ch"
41  " LEFT JOIN crpCorporation AS cr USING (corporationID) "
42  " WHERE ch.characterID = %u ", charID))
43  {
44  _log(DATABASE__ERROR, "Error in query: %s", res.error.c_str());
45  return;
46  }
47 
48  DBResultRow row;
49  if (!res.GetRow(row)) {
50  _log(SERVICE__ERROR, "CharID %u isn't present in the database", charID);
51  return;
52  }
53 
54  names.push_back(row.GetText(0)); // charName
55  names.push_back(row.GetText(1)); // corpName
56 }
57 
59 {
61  DBQueryResult res;
62  if( !sDatabase.RunQuery( res,
63  "SELECT"
64  " channelID "
65  " FROM channels "
66  " WHERE channelID >= %i ", LSCService::BASE_CHANNEL_ID ))
67  {
68  _log(DATABASE__ERROR, "Error in query: %s", res.error.c_str());
69  return 0;
70  }
71 
72  int32 currentChannelID = LSCService::BASE_CHANNEL_ID;
73 
74  DBResultRow row;
75  while( res.GetRow(row) )
76  {
77  if ( ++currentChannelID < row.GetInt( 0 ) )
78  return currentChannelID;
79  }
80 
81  // Check to make sure that the next available channelID is not equal to the Maximum channel ID value
82  if( currentChannelID <= LSCService::MAX_CHANNEL_ID )
83  return currentChannelID;
84 
85  return 0; // No free channel IDs found (this should never happen as there are way too many IDs to exhaust)
86 }
87 
89  std::string new_password = "NULL";
90  if (channel->GetPassword() != "")
91  new_password = "'" + channel->GetPassword() + "'";
92 
93  DBerror err;
94  if (!sDatabase.RunQuery(err,
95  " INSERT INTO channels"
96  " (channelID, ownerID, displayName, motd, comparisonKey, memberless, password, mailingList, cspa)"
97  " VALUES (%i, %u, '%s', '%s', '%s', %u, '%s', %u, %u)"
98  " ON DUPLICATE KEY UPDATE"
99  " ownerID=VALUES(ownerID),"
100  " displayName=VALUES(displayName),"
101  " motd=VALUES(motd),"
102  " comparisonKey=VALUES(comparisonKey),"
103  " memberless=VALUES(memberless),"
104  " password=VALUES(password),"
105  " mailingList=VALUES(mailingList),"
106  " cspa=VALUES(cspa)",
107  channel->GetChannelID(),
108  channel->GetOwnerID(),
109  channel->GetDisplayName().c_str(),
110  channel->GetMOTD().c_str(),
111  channel->GetComparisonKey().c_str(),
112  (channel->GetMemberless() ? 1 : 0),
113  new_password.c_str(),
114  (channel->GetMailingList() ? 1 : 0),
115  channel->GetCSPA()))
116  {
117  _log(DATABASE__ERROR, "Error in query: %s", err.c_str());
118  }
119 }
120 
121 void LSCDB::UpdateSubscription(int32 channelID, Client* pClient) {
122  DBerror err;
123  sDatabase.RunQuery(err,
124  " INSERT INTO channelChars "
125  " (channelID, corpID, charID, allianceID, role, extra) "
126  " VALUES (%i, %i, %i, %i, %li, 0) ",
127  channelID, pClient->GetCorporationID(), pClient->GetCharacterID(), pClient->GetAllianceID(), pClient->GetAccountRole());
128 }
129 
131 {
132  DBerror err;
133  sDatabase.RunQuery(err, "DELETE FROM channels WHERE channelID=%i", channelID);
134 }
135 
136 void LSCDB::DeleteSubscription(int32 channelID, uint32 charID)
137 {
138  DBerror err;
139  sDatabase.RunQuery(err, "DELETE FROM channelChars WHERE channelID=%i AND charID=%u", channelID, charID );
140 }
141 
142 
143 // Function: Return true or false result for the check of whether or not the specified
144 // channel 'displayName' is already being used by a channel.
145 bool LSCDB::IsChannelNameAvailable(std::string name)
146 {
147  DBQueryResult res;
148 
149  // MySQL query channels table for any channel whose displayName matches "name":
150  if (!sDatabase.RunQuery(res,
151  "SELECT"
152  " displayName "
153  " FROM channels "
154  " WHERE displayName = upper('%s')", name.c_str()))
155  {
156  _log(DATABASE__ERROR, "Error in query: %s", res.error.c_str());
157  return false;
158  }
159 
160  DBResultRow row;
161 
162  // Return true (this 'displayName' not in use) if there are no rows returned by the query:
163  if (!res.GetRow(row))
164  return true;
165 
166  return false;
167 }
168 
169 
170 // Function: Return true or false result for the check of whether or not the specified
171 // channelID is available to be taken for a new channel's channelID.
173 {
174  DBQueryResult res;
175 
176  if (!sDatabase.RunQuery(res,
177  "SELECT"
178  " channelID "
179  " FROM channels "
180  " WHERE channelID = %i", channelID ))
181  {
182  _log(DATABASE__ERROR, "Error in query: %s", res.error.c_str());
183  return false;
184  }
185 
186  DBResultRow row;
187 
188  // Return true (this channelID not in use) if there are no rows returned by the query:
189  if (!(res.GetRow(row)))
190  return true;
191 
192  return false;
193 }
194 
195 
196 // Function: Return true or false result for the check of whether or not the channel
197 // specified by channelID is already subscribed to by the character specified by charID.
199 {
200  DBQueryResult res;
201 
202  if (!sDatabase.RunQuery(res,
203  "SELECT"
204  " channelID, "
205  " charID "
206  " FROM channelChars "
207  " WHERE channelID = %i AND charID = %u", channelID, charID ))
208  {
209  _log(DATABASE__ERROR, "Error in query: %s", res.error.c_str());
210  return false;
211  }
212 
213  DBResultRow row;
214 
215  // Return false (no subscription exists) if there are no rows returned by the query:
216  if (!(res.GetRow(row)))
217  return false;
218 
219  return true;
220 }
221 
222 int32 LSCDB::GetChannelID(std::string &name) {
223  DBQueryResult res;
224  if (!sDatabase.RunQuery(res, "SELECT channelID FROM channels WHERE displayName RLIKE '%s'", name.c_str())) {
225  _log(DATABASE__ERROR, "Error in query: %s", res.error.c_str());
226  return 0;
227  }
228 
229  DBResultRow row;
230  if (!res.GetRow(row)) {
231  _log(SERVICE__ERROR, "Channel named '%s' isn't present in the database", name.c_str() );
232  return 0;
233  }
234 
235  return row.GetInt(0);
236 }
237 
238 // Function: Query 'channels' table for the channel whose 'channelID' matches the ID specified,
239 // then return all parameters for that channel.
240 void LSCDB::GetChannelInformation(int32 channelID, std::string & name,
241  std::string & motd, uint32 & ownerid, std::string & compkey,
242  bool & memberless, std::string & password, bool & maillist,
243  uint32 & cspa)
244 {
245  DBQueryResult res;
246 
247  if (!sDatabase.RunQuery(res,
248  "SELECT"
249  " channelID, "
250  " displayName, "
251  " motd, "
252  " ownerID, "
253  " comparisonKey, "
254  " memberless, "
255  " password, "
256  " mailingList, "
257  " cspa "
258  " FROM channels "
259  " WHERE channelID = %i", channelID))
260  {
261  _log(DATABASE__ERROR, "Error in query: %s", res.error.c_str());
262  return;
263  }
264 
265  DBResultRow row;
266 
267  if (!(res.GetRow(row)))
268  {
269  _log(SERVICE__ERROR, "Channel %i isn't present in the database", channelID );
270  return;
271  }
272 
273  name = (row.IsNull(1) ? "" : row.GetText(1)); // empty displayName field in channels table row returns NULL, so fill this string with "" in that case
274  motd = (row.IsNull(2) ? "" : row.GetText(2)); // empty motd field in channels table row returns NULL, so fill this string with "" in that case
275  ownerid = row.GetUInt(3);
276  compkey = (row.IsNull(4) ? "" : row.GetText(4)); // empty comparisonKey field in channels table row returns NULL, so fill this string with "" in that case
277  memberless = row.GetBool(5);
278  password = (row.IsNull(6) ? "" : row.GetText(6));// empty password field in channels table row returns NULL, so fill this string with "" in that case
279  maillist = row.GetBool(7);
280  cspa = row.GetUInt(8);
281 }
282 
283 // Function: Query the 'channelChars' table for all channels subscribed to by the character specified by charID and
284 // return lists of parameters for all of those channels as well as a total channel count.
285 void LSCDB::GetChannelSubscriptions(uint32 charID, std::vector<long> & ids, std::vector<std::string> & names,
286  std::vector<std::string> & MOTDs, std::vector<unsigned long> & ownerids, std::vector<std::string> & compkeys,
287  std::vector<int> & memberless, std::vector<std::string> & passwords, std::vector<int> & maillists,
288  std::vector<int> & cspas, int & channelCount)
289 {
290  DBQueryResult res;
291 
292  // Cross-reference "channelchars" table with "channels" table using the charID
293  // The result is a two column multi-row structure where each row is a channel
294  // that the character (charID) is subscribed to where the channel ID is presented
295  // in the first column and the display name of that channel in the second column
296  if (!sDatabase.RunQuery(res,
297  "SELECT"
298  " channelID, "
299  " displayName, "
300  " motd, "
301  " ownerID, "
302  " comparisonKey, "
303  " memberless, "
304  " password, "
305  " mailingList, "
306  " cspa "
307  " FROM channels "
308  " WHERE channelID = ANY ("
309  " SELECT channelID FROM channelChars WHERE charID = %u )", charID))
310  {
311  _log(DATABASE__ERROR, "Error in query: %s", res.error.c_str());
312  return;
313  }
314 
315  DBResultRow row;
316  int rowCount = 0;
317 
318  // Traverse through all rows in the query result and copy the IDs and displayNames to the
319  // "ids" and "names" vectors for return to the calling function:
320  while(res.GetRow(row))
321  {
322  ++rowCount;
323 
324  ids.push_back(row.GetInt(0));
325  names.push_back((row.GetText(1) == NULL ? "" : row.GetText(1))); // empty displayName field in channels table row returns NULL, so fill this string with "" in that case
326  MOTDs.push_back((row.GetText(2) == NULL ? "" : row.GetText(2))); // empty motd field in channels table row returns NULL, so fill this string with "" in that case
327  ownerids.push_back(row.GetUInt(3));
328  compkeys.push_back((row.GetText(4) == NULL ? "" : row.GetText(4))); // empty comparisonKey field in channels table row returns NULL, so fill this string with "" in that case
329  memberless.push_back(row.GetUInt(5));
330  passwords.push_back((row.GetText(6) == NULL ? "" : row.GetText(6))); // empty password field in channels table row returns NULL, so fill this string with "" in that case
331  maillists.push_back(row.GetUInt(7));
332  cspas.push_back(row.GetUInt(8));
333  }
334 
335  if (rowCount == 0) {
336  //_log(SERVICE__ERROR, "CharID %u isn't present in the database", charID);
337  return;
338  }
339 
340  channelCount = rowCount;
341 }
342 
343 //TODO check these next 2 calls to solve error/warning in console...
344 bool LSCDB::GetChannelInfo(int32 channelID, std::string &name, std::string &motd)
345 {
346  DBQueryResult res;
347  if (!sDatabase.RunQuery(res, "SELECT displayName, motd FROM channels WHERE channelID = %i ", channelID)) {
348  _log(DATABASE__ERROR, "Error in query: %s", res.error.c_str());
349  return false;
350  }
351 
352  DBResultRow row;
353  if (!res.GetRow(row)) {
354  // _log(SERVICE__ERROR, "Couldn't find %u in table channels", channelID);
355  return false;
356  }
357 
358  name = row.GetText(0);
359  motd = row.GetText(1);
360 
361  return true;
362 }
363 
364 
366 {
367  DBQueryResult res;
368  if(!sDatabase.RunQuery(res, "SELECT channelID FROM channels WHERE comparisonKey RLIKE '%s'", compkey.c_str())) {
369  _log(DATABASE__ERROR, "Error in GetChannelIDFromComparisonKey query: %s", res.error.c_str());
370  return 0;
371  }
372 
373  DBResultRow row;
374  if (!res.GetRow(row)) {
375  _log(SERVICE__ERROR, "Couldn't find %s in table channels", compkey.c_str());
376  return 0;
377  }
378 
379  return row.GetInt(0);
380 }
381 
382 std::string LSCDB::GetChannelName(uint32 id, const char * table, const char * column, const char * key) {
383  DBQueryResult res;
384  if (!sDatabase.RunQuery(res,"SELECT %s FROM %s WHERE %s = %u ", column, table, key, id)) {
385  _log(DATABASE__ERROR, "Error in query: %s", res.error.c_str());
386  return "";
387  }
388 
389  DBResultRow row;
390  if (!res.GetRow(row)) {
391  _log(SERVICE__ERROR, "Couldn't find %s %u in table %s", key, id, table);
392  return "";
393  }
394 
395  return row.GetText(0);
396 }
397 
398 //temporarily relocated into ServiceDB until some things get cleaned up...
399 uint32 LSCDB::StoreMail(uint32 senderID, uint32 recipID, const char * subject, const char * message, int64 sentTime) {
400  DBQueryResult res;
401  DBerror err;
402  DBResultRow row;
403 
404  std::string escaped;
405  // Escape message header
406  sDatabase.DoEscapeString(escaped, subject);
407 
408  // Store message header
409  uint32 messageID;
410  if (!sDatabase.RunQueryLID(err, messageID,
411  " INSERT INTO eveMail "
412  " (channelID, senderID, subject, created) "
413  " VALUES (%u, %u, '%s', %li) ",
414  recipID, senderID, escaped.c_str(), sentTime ))
415  {
416  _log(DATABASE__ERROR, "Error in query, message header couldn't be saved: %s", err.c_str());
417  return (0);
418  }
419 
420  _log(SERVICE__MESSAGE, "New messageID: %u", messageID);
421 
422  // Escape message content
423  sDatabase.DoEscapeString(escaped, message);
424 
425  // Store message content
426  if (!sDatabase.RunQuery(err,
427  " INSERT INTO eveMailDetails "
428  " (messageID, mimeTypeID, attachment) VALUES (%u, 1, '%s') ",
429  messageID, escaped.c_str()
430  ))
431  {
432  _log(DATABASE__ERROR, "Error in query, message content couldn't be saved: %s", err.c_str());
433  // Delete message header
434  if (!sDatabase.RunQuery(err, "DELETE FROM `eveMail` WHERE `messageID` = %u;", messageID))
435  {
436  _log(DATABASE__ERROR, "Failed to remove invalid header data for messgae id %u: %s", messageID, err.c_str());
437  }
438  return (0);
439  }
440 
441 
442  return (messageID);
443 }
444 
445 
447  DBQueryResult res;
448 
449  if(!sDatabase.RunQuery(res,
450  "SELECT channelID, messageID, senderID, subject, created, `read` "
451  " FROM eveMail "
452  " WHERE channelID=%u", recID))
453  {
454  _log(DATABASE__ERROR, "Error in query: %s", res.error.c_str());
455  return nullptr;
456  }
457 
458  return DBResultToRowset(res);
459 }
460 
461 
462 PyRep *LSCDB::GetMailDetails(uint32 messageID, uint32 readerID) {
463  DBQueryResult result;
464  DBResultRow row;
465 
466  //we need to query out the primary message here... not sure how to properly
467  //grab the "main message" though... the text/plain clause is pretty hackish.
468  if (!sDatabase.RunQuery(result,
469  " SELECT eveMail.messageID, eveMail.senderID, eveMail.subject, " // need messageID as char*
470  " eveMailDetails.attachment, eveMailDetails.mimeTypeID, "
471  " eveMailMimeType.mimeType, eveMailMimeType.`binary`, "
472  " eveMail.created, eveMail.channelID "
473  " FROM eveMail "
474  " LEFT JOIN eveMailDetails ON eveMailDetails.messageID = eveMail.messageID "
475  " LEFT JOIN eveMailMimeType ON eveMailMimeType.mimeTypeID = eveMailDetails.mimeTypeID "
476  " WHERE eveMail.messageID=%u AND channelID=%u",
477  messageID, readerID
478  ))
479  {
480  _log(DATABASE__ERROR, "Error in query: %s", result.error.c_str());
481  return nullptr;
482  }
483 
484  if (!result.GetRow(row)) {
485  codelog(SERVICE__MESSAGE, "No message with messageID %u", messageID);
486  return nullptr;
487  }
488 
489  Rsp_GetEVEMailDetails details;
490  details.messageID = row.GetUInt(0);
491  details.senderID = row.GetUInt(1);
492  details.subject = row.GetText(2);
493  details.body = row.GetText(3);
494  details.created = row.GetInt64(7);
495  details.channelID = row.GetUInt(8);
496  details.deleted = 0; // If a message's details are sent, then it isn't deleted. If it's deleted, details cannot be sent
497  details.mimeTypeID = row.GetInt(4);
498  details.mimeType = row.GetText(5);
499  details.binary = row.GetInt(6);
500 
501  return details.Encode();
502 }
503 
504 
506  DBerror err;
507 
508  if (!sDatabase.RunQuery(err,
509  " UPDATE eveMail "
510  " SET `read` = 1 "
511  " WHERE messageID=%u", messageID
512  ))
513  {
514  _log(DATABASE__ERROR, "Error in query: %s", err.c_str());
515  return false;
516  }
517 
518  return true;
519 }
520 
521 
522 bool LSCDB::DeleteMessage(uint32 messageID, uint32 readerID) {
523  DBerror err;
524  bool ret = true;
525 
526  if (!sDatabase.RunQuery(err,
527  " DELETE FROM eveMail "
528  " WHERE messageID=%u AND channelID=%u", messageID, readerID
529  ))
530  {
531  _log(DATABASE__ERROR, "Error in query: %s", err.c_str());
532  ret = false;
533  }
534  if (!sDatabase.RunQuery(err,
535  " DELETE FROM eveMailDetails "
536  " WHERE messageID=%u", messageID
537  ))
538  {
539  _log(DATABASE__ERROR, "Error in query: %s", err.c_str());
540  ret = false;
541  }
542 
543  return ret;
544 
545 }
int64 GetAccountRole() const
Definition: Client.h:118
Base Python wire object.
Definition: PyRep.h:66
int32 GetNextAvailableChannelID()
Definition: LSCDB.cpp:58
bool MarkMessageRead(uint32 messageID)
Definition: LSCDB.cpp:505
#define sDatabase
Definition: dbcore.h:199
const char * GetText(uint32 index) const
Definition: dbcore.h:104
uint16 GetCSPA()
Definition: LSCChannel.h:132
#define _log(type, fmt,...)
Definition: logsys.h:124
bool IsChannelSubscribedByThisChar(uint32 char_ID, int32 channel_ID)
Definition: LSCDB.cpp:198
int32 GetChannelID()
Definition: LSCChannel.h:134
uint32 GetOwnerID()
Definition: LSCChannel.h:133
int32 GetInt(uint32 index) const
Definition: dbcore.cpp:635
uint32 GetUInt(uint32 index) const
Definition: dbcore.cpp:658
void DeleteChannel(int32 channelID)
Definition: LSCDB.cpp:130
void UpdateSubscription(int32 channelID, Client *pClient)
Definition: LSCDB.cpp:121
bool GetMailingList()
Definition: LSCChannel.h:128
void UpdateChannelInfo(LSCChannel *channel)
Definition: LSCDB.cpp:88
void DeleteSubscription(int32 channelID, uint32 charID)
Definition: LSCDB.cpp:136
int32 GetCharacterID() const
Definition: Client.h:113
int32 GetCorporationID() const
Definition: Client.h:123
uint32 StoreMail(uint32 senderID, uint32 recipID, const char *subject, const char *message, int64 sentTime)
Definition: LSCDB.cpp:399
bool GetMemberless()
Definition: LSCChannel.h:127
PyRep * GetMailDetails(uint32 messageID, uint32 readerID)
Definition: LSCDB.cpp:462
bool IsChannelIDAvailable(int32 channel_ID)
Definition: LSCDB.cpp:172
std::string GetPassword()
Definition: LSCChannel.h:141
int32 GetChannelIDFromComparisonKey(std::string compkey)
Definition: LSCDB.cpp:365
int32 GetAllianceID() const
Definition: Client.h:125
signed __int32 int32
Definition: eve-compat.h:49
bool GetRow(DBResultRow &into)
Definition: dbcore.cpp:552
bool GetBool(uint32 index) const
Definition: dbcore.cpp:647
void GetChannelNames(uint32 charID, std::vector< std::string > &names)
Definition: LSCDB.cpp:33
const char * c_str() const
Definition: dbcore.h:48
Python object.
Definition: PyRep.h:826
#define codelog(type, fmt,...)
Definition: logsys.h:128
PyObject * DBResultToRowset(DBQueryResult &result)
Definition: EVEDBUtils.cpp:81
std::string GetDisplayName()
Definition: LSCChannel.h:138
bool IsNull(uint32 index) const
Definition: dbcore.h:102
std::string GetMOTD()
Definition: LSCChannel.h:139
Definition: Client.h:66
unsigned __int32 uint32
Definition: eve-compat.h:50
DBerror error
Definition: dbcore.h:69
void GetChannelSubscriptions(uint32 charID, std::vector< long > &ids, std::vector< std::string > &names, std::vector< std::string > &MOTDs, std::vector< unsigned long > &ownerids, std::vector< std::string > &compkeys, std::vector< int > &memberless, std::vector< std::string > &passwords, std::vector< int > &maillists, std::vector< int > &cspas, int &channelCount)
Definition: LSCDB.cpp:285
signed __int64 int64
Definition: eve-compat.h:51
int32 GetChannelID(std::string &name)
Definition: LSCDB.cpp:222
const int cspa
Definition: LSCService.cpp:132
bool IsChannelNameAvailable(std::string name)
Definition: LSCDB.cpp:145
static const int32 BASE_CHANNEL_ID
Definition: LSCService.h:48
std::string GetComparisonKey()
Definition: LSCChannel.h:140
void GetChannelInformation(int32 channelID, std::string &name, std::string &motd, uint32 &ownerid, std::string &compkey, bool &memberless, std::string &password, bool &maillist, uint32 &cspa)
Definition: LSCDB.cpp:240
typeID Spawn an NPC with the specified type text Search for items matching the specified query() type() key(value)-Send an OnRemoteMessage" ) COMMAND( setbpattr
bool DeleteMessage(uint32 messageID, uint32 readerID)
Definition: LSCDB.cpp:522
int64 GetInt64(uint32 index) const
Definition: dbcore.cpp:670
std::string GetChannelName(uint32 id, const char *table, const char *column, const char *key)
Definition: LSCDB.cpp:382
static const uint32 MAX_CHANNEL_ID
Definition: LSCService.h:49
bool GetChannelInfo(int32 channelID, std::string &name, std::string &motd)
Definition: LSCDB.cpp:344
Definition: dbcore.h:39
PyObject * GetMailHeaders(uint32 recID)
Definition: LSCDB.cpp:446