simplemqttserverthread.cpp 21.1 KB
Newer Older
1
2
3
//================================================================================
// Name        : simplemqttserverthread.cpp
// Author      : Axel Auweter
Micha Müller's avatar
Micha Müller committed
4
// Contact     : info@dcdb.it
5
6
7
8
9
10
// Copyright   : Leibniz Supercomputing Centre
// Description : Implementation of classes for running MQTT server threads
//================================================================================

//================================================================================
// This file is part of DCDB (DataCenter DataBase)
11
// Copyright (C) 2011-2019 Leibniz Supercomputing Centre
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//================================================================================
27
28

#include "simplemqttserver.h"
29
#include "abrt.h"
30
#include "messaging.h"
31
32

using namespace std;
33
using namespace boost::system;
34
35
36
37
38

#ifdef SimpleMQTTVerbose
static boost::mutex coutMtx;
#endif

39
40
static boost::mutex launchMtx;

41
42
43
44
45
46
47
48
49
SimpleMQTTServerThread::SimpleMQTTServerThread()
{
  /*
   * Start the thread at the 'launch' function. The 'launch'
   * function will take care of calling the child-specific
   * 'run' function which contains the thread's main loop.
   *
   * Special care must be taken not to call the run function
   * before the child has gone through its constructor. Thus,
50
51
52
53
54
55
56
57
58
59
60
61
   * we lock a mutex (launchMtx) that will cause the 'launch'
   * function to  wait until the mutex is released by the
   * child's constructor.
   *
   * A second mutex (cleanupMtx) is held to allow others to
   * wait for a thread to return from its child-specific
   * 'run' function. This is necessary for cases where the
   * child class has local objects that can only be destroyed
   * after the 'run' function has finished (to avoid access
   * to these objects in the 'run'function after destroying
   * them). In such cases, the child class destructor can try
   * to obtain the mutex in the destructor.
62
   */
63
64
  launchMtx.lock();
  cleanupMtx.lock();
65
66
67

  terminate = false;
  if (pthread_create(&t, NULL, launch, this) != 0) {
Alessio Netti's avatar
Logging    
Alessio Netti committed
68
      LOG(error) << "Error creating new MQTT server thread.";
69
      abrt(EXIT_FAILURE, INTERR);
70
71
72
73
  }

#ifdef SimpleMQTTVerbose
  coutMtx.lock();
74
  cout << "Started Thread (" << hex << t << ") of class (" << this << ")...\n";
75
76
77
78
79
80
81
82
83
84
85
86
  coutMtx.unlock();
#endif
}

SimpleMQTTServerThread::~SimpleMQTTServerThread()
{
  /*
   * Terminate the thread and join it to ensure proper
   * thread termination before the class is destroyed.
   */
  terminate = true;
  if (pthread_join(t, NULL) != 0) {
Alessio Netti's avatar
Logging    
Alessio Netti committed
87
      LOG(error) << "Error joining thread.";
88
      abrt(EXIT_FAILURE, INTERR);
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
  }

#ifdef SimpleMQTTVerbose
  coutMtx.lock();
  cout << "Terminated Thread (" << t << ") of class (" << this << ")...\n";
  coutMtx.unlock();
#endif
}

void* SimpleMQTTServerThread::launch(void *selfPtr)
{
#ifdef SimpleMQTTVerbose
  coutMtx.lock();
  cout << "Running launcher for class (" << selfPtr << ")...\n";
  coutMtx.unlock();
#endif

  /*
   * The following lines guard the run function to be called
   * before the constructor of the child class has finished.
   * The lock is released at the end of the child's constructor.
   */
111
112
  launchMtx.lock();
  launchMtx.unlock();
113
114
115
116
117
118

  /*
   * Call the child's run function...
   */
  ((SimpleMQTTServerThread*)selfPtr)->run();

119
120
121
122
123
  /*
   * The thread will terminate. Unlock the cleanupMtx...
   */
  ((SimpleMQTTServerThread*)selfPtr)->cleanupMtx.unlock();

124
125
126
  return NULL;
}

127
128
129
130
131
132
133
134
135
136
137
void SimpleMQTTServerThread::yield()
{
  /*
   * Yield this thread's execution to give way to other threads.
   * This is being used in the SimpleMQTTServer to allow the
   * accept thread acquiring the fdsMtx lock. Otherwise, in
   * particular on single-CPU systems (or when the acceptThread
   * and the messageThread share a core), waiting connections
   * may have to wait a long time before the lock will be given
   * to the acceptThread.
   */
138
  boost::this_thread::yield();
139
140
}

141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
void SimpleMQTTServerAcceptThread::run()
{
#ifdef SimpleMQTTVerbose
  coutMtx.lock();
  cout << "Running SimpleMQTTServerAcceptThread for socket " << socket << "...\n";
  coutMtx.unlock();
#endif

  int newsock = -1;
  struct pollfd fd;

  while (!terminate) {
      /*
       * Wait for something to happen on the socket...
       */
      fd.fd = socket;
      fd.events = POLLIN | POLLPRI;
      fd.revents = 0;
      if(poll(&fd, 1, SimpleMQTTPollTimeout) > 0 && (fd.revents & (POLLIN | POLLPRI))) {
160
161
162
163
164
165
166
167
          /*
           * Take the next incoming connection.
           */
#ifdef SimpleMQTTVerbose
          coutMtx.lock();
          cout << "Thread (" << this << ") waiting in accept()...\n";
          coutMtx.unlock();
#endif
168
169
170
          struct sockaddr_in addr;
          socklen_t socklen = sizeof(addr);
          newsock = accept(socket, (struct sockaddr*)&addr, &socklen);
171
172
173
          if (newsock != -1) {
              int opt = fcntl(newsock, F_GETFL, 0);
              if (opt == -1 || fcntl(newsock, F_SETFL, opt | O_NONBLOCK)==-1) {
174
#ifdef SimpleMQTTVerbose
175
176
177
                coutMtx.lock();
                cout << "Setting socket to non-blocking in thread (" << this << ") failed for socket " << newsock << "...\n";
                coutMtx.unlock();
178
#endif
179
180
                close(newsock);
              } else {
181
#ifdef SimpleMQTTVerbose
Alessio Netti's avatar
Alessio Netti committed
182
183
184
                    coutMtx.lock();
                    cout << "Successfully set socket " << newsock << " non-blocking in thread (" << this << ")...\n";
                    coutMtx.unlock();
185
#endif
Alessio Netti's avatar
Alessio Netti committed
186
187
188
                  if (messageThreads.size() < this->_maxThreads) {
                      // Spawning new threads, if not exceeding maximum thread pool size
                      messageThreads.push_back(new SimpleMQTTServerMessageThread(messageCallback, this->_maxConnPerThread));
189
                      messageThreads.back()->queueConnection(newsock, std::string(inet_ntoa(addr.sin_addr)) + ":" + std::to_string(addr.sin_port));
190
                  }
Alessio Netti's avatar
Alessio Netti committed
191
192
193
194
195
196
                  else {
                      // If thread pool is full, we cycle through it to find any available threads to connect to
                      unsigned int ctr=0;
                      do {
                          // Rotating the thread queue to ensure round-robin scheduling
                          threadCtr = (threadCtr + 1) % messageThreads.size();
197
                      } while(ctr++ < messageThreads.size() && messageThreads[threadCtr]->queueConnection(newsock, inet_ntoa(addr.sin_addr)));
Alessio Netti's avatar
Alessio Netti committed
198
199
200
201
202
203

                      if(ctr > messageThreads.size()) {
                          LOG(warning) << "Socket " << socket << " cannot accept more connections.";
                          // FIXME: There must be nicer ways to handle such situations...
                          close(newsock);
                      }
204
#ifdef SimpleMQTTVerbose
Alessio Netti's avatar
Alessio Netti committed
205
206
207
208
209
                      else {
                          coutMtx.lock();
                          cout << "Found a message thread with capacity: " << messageThreads.front() << "\n";
                          coutMtx.unlock();
                      }
210
#endif
Alessio Netti's avatar
Alessio Netti committed
211
                  }
212
              }
213
            }
214
#ifdef SimpleMQTTVerbose
215
216
217
218
219
            else {
              coutMtx.lock();
              cout << "Accept() in thread (" << this << ") returned -1...\n";
              coutMtx.unlock();
            }
220
#endif
221
222
223
224
      }
  }
}

225
void SimpleMQTTServerAcceptThread::setMessageCallback(SimpleMQTTMessageCallback callback)
226
{
227
228
229
230
231
  /*
   * Set the function that will be called for each received
   * MQTT message and propagate to all message threads.
   */
  messageCallback = callback;
Alessio Netti's avatar
Alessio Netti committed
232
233
  for (std::vector<SimpleMQTTServerMessageThread*>::iterator i = messageThreads.begin(); i != messageThreads.end(); i++) {
      (*i)->setMessageCallback(callback);
234
235
236
  }
}

237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
std::vector<hostInfo_t> SimpleMQTTServerAcceptThread::collectLastSeen() {
    std::vector<hostInfo_t> hosts;
    for(const auto &m : messageThreads) {
        hostInfo_t *lastSeenVec = m->getLastSeen();
        if(lastSeenVec) {
            for(size_t idx=0; idx<this->_maxConnPerThread; idx++) {
                if(lastSeenVec[idx].lastSeen != 0) {
                    hosts.push_back(lastSeenVec[idx]);
                }
            }
        }
    }
    return hosts;
}

252
SimpleMQTTServerAcceptThread::SimpleMQTTServerAcceptThread(int listenSock, SimpleMQTTMessageCallback callback, uint64_t maxThreads, uint64_t maxConnPerThread)
253
254
255
256
{
  /*
   * Assign socket and message callback.
   */
257
258
  this->_maxThreads = maxThreads;
  this->_maxConnPerThread = maxConnPerThread;
259
  socket = listenSock;
260
  messageCallback = callback;
Alessio Netti's avatar
Alessio Netti committed
261
  threadCtr = 0;
262
263
264
265
266

  /*
   * Release the lock to indicate that the constructor has
   * finished. This causes the launcher to call the run function.
   */
267
  launchMtx.unlock();
268
269
}

Alessio Netti's avatar
Alessio Netti committed
270
271
272
273
274
275
276
277
278
279
SimpleMQTTServerAcceptThread::~SimpleMQTTServerAcceptThread() {
    terminate = true;

    cleanupMtx.lock();
    cleanupMtx.unlock();

    //De-allocating and destroying running message threads
    for(auto t : messageThreads)
        delete t;
    messageThreads.clear();
280
281
}

282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
int SimpleMQTTServerMessageThread::sendAck(int connectionId) {
  uint8_t buf[] = {0, 0, 0, 0};
  switch(msg[connectionId]->getType()) {
    case MQTT_CONNECT: {
      buf[0] = MQTT_CONNACK << 4;
      buf[1] = 2;
      break;
    }
    case MQTT_PUBLISH: {
      if (msg[connectionId]->getQoS() == 0) {
        return 1;
      } else {
        if (msg[connectionId]->getQoS() == 1) {
          buf[0] = MQTT_PUBACK << 4;
        } else {
          buf[0] = MQTT_PUBREC << 4;
        }
        buf[1] = 2;
        uint16_t msgId = htons(msg[connectionId]->getMsgId());
        buf[2] = ((uint8_t*)&msgId)[0];
        buf[3] = ((uint8_t*)&msgId)[1];
      }
      break;
    }
    case MQTT_PUBREL: {
      buf[0] = MQTT_PUBCOMP << 4;
      buf[1] = 2;
      uint16_t msgId = htons(msg[connectionId]->getMsgId());
      buf[2] = ((uint8_t*)&msgId)[0];
      buf[3] = ((uint8_t*)&msgId)[1];
      break;
    }
Alessio Netti's avatar
Alessio Netti committed
314
    case MQTT_PINGREQ: {
315
316
317
318
319
320
321
322
323
324
      buf[0] = MQTT_PINGRESP << 4;
      buf[1] = 0;
      break;
    }
    default:
      return 1;
  }
  return !(write(fds[connectionId].fd, buf, buf[1]+2) == buf[1]+2);
}

325
326
327
328
329
330
331
332
333
void SimpleMQTTServerMessageThread::run()
{
#ifdef SimpleMQTTVerbose
  coutMtx.lock();
  cout << "Running SimpleMQTTServerMessageThread (" << this << ")...\n";
  coutMtx.unlock();
#endif

  int numfds = -1;
334
  char inbuf[SimpleMQTTReadBufferSize];
335
336
337

  while (!terminate) {
      /*
338
       * Check for pending connections
339
       */
340
      assignConnections();
341

342
      /*
343
       * Check for activity on our sockets...
344
       */
345
      numfds = poll(fds, this->_maxConnPerThread, SimpleMQTTPollTimeout);
346

347
      if (numfds == -1)
348
          throw new boost::system::system_error(errno, boost::system::system_category(), "Error in poll().");
349
350
351
352
353

      /*
       * Apparently, there is work to do...
       */
      if (numfds > 0) {
Alessio Netti's avatar
Alessio Netti committed
354
          for (unsigned connectionId=0; connectionId<this->_maxConnPerThread; connectionId++) {
355
356
357
              if (fds[connectionId].fd != -1) {
#ifdef SimpleMQTTVerbose
                  coutMtx.lock();
358
                  cout << "fd(" << fds[connectionId].fd << ") revents: " << hex << fds[connectionId].revents << dec << "\n";
359
360
                  coutMtx.unlock();
#endif
361
362
363
364
                  /* Remote side hung up */
                  if (fds[connectionId].revents & POLLHUP) {
                      releaseConnection(connectionId);
                  }
365

366
                  if (fds[connectionId].revents & POLLIN) {
367
368
369
370
371
372
373
                      char* readPtr;
                      ssize_t rbytes, lbytes, bytes;

                      rbytes = read(fds[connectionId].fd, inbuf, SimpleMQTTReadBufferSize);

                      readPtr = inbuf;
                      lbytes = rbytes;
374
375

                      /*
376
                       * If read() returns 0, the connection was closed on the
377
378
                       * remote side. In this case, release it from our list.
                       */
379
                      if (rbytes == 0) {
380
                          releaseConnection(connectionId);
381
382
                      } else {
                          lastSeen[connectionId].lastSeen = getTimestamp();
383
                      }
384
385
386
387
388
389
390
391

                      while (lbytes > 0) {
                          /*
                           * Allocate new message if there is none.
                           */
                          if (!msg[connectionId]) {
                              msg[connectionId] = new SimpleMQTTMessage();
                              if (!msg[connectionId]) {
Alessio Netti's avatar
Logging    
Alessio Netti committed
392
                                  LOG(warning) << "Out of memory! Discarding network input!";
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
                                  continue;
                              }
#ifdef SimpleMQTTVerbose
                              coutMtx.lock();
                              cout << "Allocated new SimpleMQTTMessage (" << msg[connectionId] << ")...\n";
                              coutMtx.unlock();
#endif
                          }

                          /*
                           * Append received data to message.
                           */
                          bytes = msg[connectionId]->appendRawData(readPtr, lbytes);
                          readPtr += bytes;
                          lbytes -= bytes;

                          /*
                           * Check if message is complete.
                           */
                          if (msg[connectionId]->complete()) {
                              /*
414
415
416
417
                               * Forward message upstream!
                               * If there is a callback function, it is responsible
                               * for freeing the message using delete. Otherwise, we
                               * have to take care of this, here.
418
419
420
421
422
423
                               */
#ifdef SimpleMQTTVerbose
                              coutMtx.lock();
                              cout << "Completed receiving SimpleMQTTMessage (" << msg[connectionId] << ")...\n";
                              coutMtx.unlock();
#endif
424
425
                            switch(msg[connectionId]->getType()) {
                              case MQTT_CONNECT: {
426
                                lastSeen[connectionId].clientId = msg[connectionId]->getTopic();
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
                                sendAck(connectionId);
                                break;
                              }
                              case MQTT_PUBLISH: {
                                if ((messageCallback(msg[connectionId]) == 0) && (msg[connectionId]->getQoS() > 0)) {
                                  sendAck(connectionId);
                                }
                                break;
                              }
                              case MQTT_PUBREL: {
                                sendAck(connectionId);
                                break;
                              }
                              case MQTT_PINGREQ: {
                                sendAck(connectionId);
442
443
444
445
446
                                break;
                              }
                              case MQTT_DISCONNECT: {
                                releaseConnection(connectionId);
                                break;
447
                              }
448
449
450
451
452
                              default: {
                                if (messageCallback) {
                                  messageCallback(msg[connectionId]);
                                }
                                else {
Alessio Netti's avatar
Logging    
Alessio Netti committed
453
                                  LOG(trace) << "Nothing to do..";
454
455
                                }
                                break;
456
                              }
457
                            }
Alessio Netti's avatar
Alessio Netti committed
458
459
                            if(msg[connectionId])
                                msg[connectionId]->clear();
460
461
                          }
                      }
462
463
464
465
466
467
468
                  }
              }
          }
      }
  }
}

469
int SimpleMQTTServerMessageThread::queueConnection(int newsock, std::string addr) {
470
  if (numConnections >= this->_maxConnPerThread) {
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
#ifdef SimpleMQTTVerbose
      coutMtx.lock();
      cout << "Maximum number of connections reached, rejecting new connection  (" << this << ", " << newsock << ", " << numConnections << ")...\n";
      coutMtx.unlock();
#endif
    return 2;
  }

  int nextWritePos = (fdQueueWritePos + 1) % SimpleMQTTConnectionsQueueLength;
  if (nextWritePos == fdQueueReadPos) {
#ifdef SimpleMQTTVerbose
    coutMtx.lock();
    cout << "Queue is full, rejecting new connection  (" << this << ", " << newsock << ", " << fdQueueWritePos << ")...\n";
    coutMtx.unlock();
#endif
    return 1;
  }

#ifdef SimpleMQTTVerbose
  coutMtx.lock();
  cout << "Queued new connection  (" << this << ", " << newsock << ", " << fdQueueWritePos << ")...\n";
  coutMtx.unlock();
#endif
494
495
  fdQueue[fdQueueWritePos].first = newsock;
  fdQueue[fdQueueWritePos].second = addr;
496
497
  fdQueueWritePos = nextWritePos;
  return 0;
498
499
}

500
void SimpleMQTTServerMessageThread::assignConnections()
501
502
503
504
{
  /*
   * Find the first free slot in fds and assign connection.
   */
505
506
  while (fdQueueWritePos != fdQueueReadPos) {
    /* There is no free fd slot at the moment, defer.. */
507
    if (numConnections >= this->_maxConnPerThread) {
508
509
      return;
    }
510

Alessio Netti's avatar
Alessio Netti committed
511
    for (unsigned i=0; i<this->_maxConnPerThread; i++) {
512
513
514
      if (fds[i].fd == -1) {
        fds[i].events = POLLIN | POLLPRI | POLLHUP;
        fds[i].revents = 0;
515
516
517
        fds[i].fd = fdQueue[fdQueueReadPos].first;
        lastSeen[i].lastSeen = getTimestamp();
        lastSeen[i].address = fdQueue[fdQueueReadPos].second;
518
519
        numConnections++;
        fdQueueReadPos = (fdQueueReadPos + 1) % SimpleMQTTConnectionsQueueLength;
520

521
#ifdef SimpleMQTTVerbose
522
523
524
        coutMtx.lock();
        cout << "Assigned connection  (" << this << ", " << i << ", " << fds[i].fd << ")...\n";
        coutMtx.unlock();
525
#endif
526
        break;
527
      }
528
    }
529
530
531
532
533
534
535
536
  }
}

void SimpleMQTTServerMessageThread::releaseConnection(int connectionId)
{
  /*
   * Close the connection an clean up.
   */
537
  shutdown(fds[connectionId].fd, SHUT_RDWR);
538
539
540
541
  close(fds[connectionId].fd);
  fds[connectionId].fd = -1;
  fds[connectionId].events = 0;
  fds[connectionId].revents = 0;
542
543
544
545
  lastSeen[connectionId].lastSeen = 0;
  lastSeen[connectionId].address = "";
  lastSeen[connectionId].clientId = "";
  
Alessio Netti's avatar
Alessio Netti committed
546
547
548
549
  if (msg[connectionId]) {
      delete msg[connectionId];
      msg[connectionId] = NULL;
  }
550
  numConnections--;
551
552
553
554
555
556

#ifdef SimpleMQTTVerbose
  coutMtx.lock();
  cout << "Released connection  (" << this << ", " << connectionId << ")...\n";
  coutMtx.unlock();
#endif
557
558
}

559
560
561
562
563
void SimpleMQTTServerMessageThread::setMessageCallback(SimpleMQTTMessageCallback callback)
{
  messageCallback = callback;
}

564
SimpleMQTTServerMessageThread::SimpleMQTTServerMessageThread(SimpleMQTTMessageCallback callback, uint64_t maxConnPerThread)
565
{
566
567
568
569
570
571
#ifdef SimpleMQTTVerbose
  coutMtx.lock();
  cout << "Created new MessageThread (" << (void*)this << ")\n";
  coutMtx.unlock();
#endif

572
573
574
575
  this->_maxConnPerThread = maxConnPerThread;
  this->fds = new struct pollfd[this->_maxConnPerThread];
  this->msg = new SimpleMQTTMessage*[this->_maxConnPerThread];

576
577
578
579
  /*
   * Clear the fds array. Warning: This will only work when the
   * size of the fds array is determined at compile time.
   */
580
  memset(fds, -1, sizeof(fds[0]) * this->_maxConnPerThread);
581

Axel Auweter's avatar
Axel Auweter committed
582
583
584
585
  /*
   * Clear the msg array. Warning: This will only work when the
   * size of the msg array is determined at compile time.
   */
586
  memset(msg, 0, sizeof(msg[0]) * this->_maxConnPerThread);
Axel Auweter's avatar
Axel Auweter committed
587

588
  /*
589
590
   * Initialize the number of active connections to 0 and set
   * the messageCallback function.
591
592
   */
  numConnections = 0;
593
  messageCallback = callback;
594
595
596
597
598
599
600
601
  
  // Initializing the list of address-value pairs for connected hosts
  this->lastSeen = new hostInfo_t[this->_maxConnPerThread];
  for(size_t idx=0; idx<this->_maxConnPerThread; idx++) {
      this->lastSeen[idx].lastSeen = 0;
      this->lastSeen[idx].address = "";
      this->lastSeen[idx].clientId = "";
  }
602

603
604
605
  /*
   * Initialize the fd queue for new connections
   */
606
  fdQueue = new(std::pair<int, std::string>[SimpleMQTTConnectionsQueueLength]);
607
608
609
  fdQueueReadPos = 0;
  fdQueueWritePos = 0;

610
611
612
613
  /*
   * Release the lock to indicate that the constructor has
   * finished. This causes the launcher to call the run function.
   */
614
  launchMtx.unlock();
615
616
617
618
}

SimpleMQTTServerMessageThread::~SimpleMQTTServerMessageThread()
{
619
620
621
622
623
624
625
626
627
628
629
  /*
   * When this destructor is called externally, we are likely
   * holding the fdsMtx mutex and waiting in poll().
   * Terminate the thread from here and acquire cleanupMtx to
   * ensure that the thread's 'run' function has terminated in
   * order to avoid fdsMtx to be accessed after destruction.
   */
  terminate = true;

  cleanupMtx.lock();
  cleanupMtx.unlock();
Alessio Netti's avatar
Alessio Netti committed
630
631
632
633
634
635

  delete[] fdQueue;
  for(unsigned i=0; i<_maxConnPerThread; i++)
      delete msg[i];
  delete[] msg;
  delete[] fds;
636
  delete[] lastSeen;
637
638
}