F´ Flight Software - C/C++ Documentation  devel
A framework for building embedded system applications to NASA flight quality standards.
FileDownlink.cpp
Go to the documentation of this file.
1 // ======================================================================
2 // \title FileDownlink.hpp
3 // \author bocchino, mstarch
4 // \brief hpp file for FileDownlink component implementation class
5 //
6 // \copyright
7 // Copyright 2009-2015, by the California Institute of Technology.
8 // ALL RIGHTS RESERVED. United States Government Sponsorship
9 // acknowledged.
10 // ======================================================================
11 
13 #include <Fw/Types/Assert.hpp>
14 #include <FpConfig.hpp>
15 #include <Fw/Types/StringUtils.hpp>
16 #include <Os/QueueString.hpp>
17 #include <limits>
18 
19 namespace Svc {
20 
21  // ----------------------------------------------------------------------
22  // Construction, initialization, and destruction
23  // ----------------------------------------------------------------------
24 
27  const char *const name
28  ) :
30  m_configured(false),
31  m_filesSent(this),
32  m_packetsSent(this),
33  m_warnings(this),
34  m_sequenceIndex(0),
35  m_curTimer(0),
36  m_bufferSize(0),
37  m_byteOffset(0),
38  m_endOffset(0),
39  m_lastCompletedType(Fw::FilePacket::T_NONE),
40  m_lastBufferId(0),
41  m_curEntry(),
42  m_cntxId(0)
43  {
44  }
45 
48  U32 timeout,
49  U32 cooldown,
50  U32 cycleTime,
51  U32 fileQueueDepth
52  )
53  {
54  this->m_timeout = timeout;
55  this->m_cooldown = cooldown;
56  this->m_cycleTime = cycleTime;
57  this->m_configured = true;
58 
59  Os::Queue::Status stat = m_fileQueue.create(
60  Os::QueueString("fileDownlinkQueue"),
61  static_cast<FwSizeType>(fileQueueDepth),
62  static_cast<FwSizeType>(sizeof(struct FileEntry))
63  );
64  FW_ASSERT(stat == Os::Queue::OP_OK, stat);
65  }
66 
68  preamble()
69  {
70  FW_ASSERT(this->m_configured == true);
71  }
72 
75  {
76 
77  }
78 
79  // ----------------------------------------------------------------------
80  // Handler implementations for user-defined typed input ports
81  // ----------------------------------------------------------------------
82 
83  void FileDownlink ::
84  Run_handler(
85  const NATIVE_INT_TYPE portNum,
86  U32 context
87  )
88  {
89  switch(this->m_mode.get())
90  {
91  case Mode::IDLE: {
92  FwSizeType real_size = 0;
93  FwQueuePriorityType prio = 0;
94  Os::Queue::Status stat = m_fileQueue.receive(
95  reinterpret_cast<U8*>(&this->m_curEntry),
96  static_cast<FwSizeType>(sizeof(this->m_curEntry)),
97  Os::Queue::BlockingType::NONBLOCKING,
98  real_size,
99  prio
100  );
101 
102  if(stat != Os::Queue::Status::OP_OK || sizeof(this->m_curEntry) != real_size) {
103  return;
104  }
105 
106  sendFile(
107  this->m_curEntry.srcFilename,
108  this->m_curEntry.destFilename,
109  this->m_curEntry.offset,
110  this->m_curEntry.length
111  );
112  break;
113  }
114  case Mode::COOLDOWN: {
115  if (this->m_curTimer >= this->m_cooldown) {
116  this->m_curTimer = 0;
117  this->m_mode.set(Mode::IDLE);
118  } else {
119  this->m_curTimer += m_cycleTime;
120  }
121  break;
122  }
123  case Mode::WAIT: {
124  //If current timeout is too-high and we are waiting for a packet, issue a timeout
125  if (this->m_curTimer >= this->m_timeout) {
126  this->m_curTimer = 0;
127  this->log_WARNING_HI_DownlinkTimeout(this->m_file.getSourceName(), this->m_file.getDestName());
128  this->enterCooldown();
130  } else { //Otherwise update the current counter
131  this->m_curTimer += m_cycleTime;
132  }
133  break;
134  }
135  default:
136  break;
137  }
138  }
139 
140  Svc::SendFileResponse FileDownlink ::
141  SendFile_handler(
142  const NATIVE_INT_TYPE portNum,
143  const Fw::StringBase& sourceFilename, // lgtm[cpp/large-parameter] dictated by command architecture
144  const Fw::StringBase& destFilename, // lgtm[cpp/large-parameter] dictated by command architecture
145  U32 offset,
146  U32 length
147  )
148  {
149  struct FileEntry entry;
150  entry.srcFilename[0] = 0;
151  entry.destFilename[0] = 0;
152  entry.offset = offset;
153  entry.length = length;
154  entry.source = FileDownlink::PORT;
155  entry.opCode = 0;
156  entry.cmdSeq = 0;
157  entry.context = m_cntxId++;
158 
159  FW_ASSERT(sourceFilename.length() < sizeof(entry.srcFilename));
160  FW_ASSERT(destFilename.length() < sizeof(entry.destFilename));
161  (void) Fw::StringUtils::string_copy(entry.srcFilename, sourceFilename.toChar(), static_cast<FwSizeType>(sizeof(entry.srcFilename)));
162  (void) Fw::StringUtils::string_copy(entry.destFilename, destFilename.toChar(), static_cast<FwSizeType>(sizeof(entry.destFilename)));
163 
164  Os::Queue::Status status = m_fileQueue.send(reinterpret_cast<U8*>(&entry), static_cast<FwSizeType>(sizeof(entry)), 0, Os::Queue::BlockingType::NONBLOCKING);
165 
166  if(status != Os::Queue::Status::OP_OK) {
167  return SendFileResponse(SendFileStatus::STATUS_ERROR, std::numeric_limits<U32>::max());
168  }
169  return SendFileResponse(SendFileStatus::STATUS_OK, entry.context);
170  }
171 
172  void FileDownlink ::
173  pingIn_handler(
174  const NATIVE_INT_TYPE portNum,
175  U32 key
176  )
177  {
178  this->pingOut_out(0,key);
179  }
180 
181  void FileDownlink ::
182  bufferReturn_handler(
183  const NATIVE_INT_TYPE portNum,
184  Fw::Buffer &fwBuffer
185  )
186  {
187  //If this is a stale buffer (old, timed-out, or both), then ignore its return.
188  //File downlink actions only respond to the return of the most-recently-sent buffer.
189  if (this->m_lastBufferId != fwBuffer.getContext() + 1 ||
190  this->m_mode.get() == Mode::IDLE) {
191  return;
192  }
193  //Non-ignored buffers cannot be returned in "DOWNLINK" and "IDLE" state. Only in "WAIT", "CANCEL" state.
194  FW_ASSERT(this->m_mode.get() == Mode::WAIT || this->m_mode.get() == Mode::CANCEL, this->m_mode.get());
195  //If the last packet has been sent (and is returning now) then finish the file
196  if (this->m_lastCompletedType == Fw::FilePacket::T_END ||
197  this->m_lastCompletedType == Fw::FilePacket::T_CANCEL) {
198  finishHelper(this->m_lastCompletedType == Fw::FilePacket::T_CANCEL);
199  return;
200  }
201  //If waiting and a buffer is in-bound, then switch to downlink mode
202  else if (this->m_mode.get() == Mode::WAIT) {
203  this->m_mode.set(Mode::DOWNLINK);
204  }
205 
206  this->downlinkPacket();
207  }
208 
209  // ----------------------------------------------------------------------
210  // Command handler implementations
211  // ----------------------------------------------------------------------
212 
213  void FileDownlink ::
214  SendFile_cmdHandler(
215  const FwOpcodeType opCode,
216  const U32 cmdSeq,
217  const Fw::CmdStringArg& sourceFilename,
218  const Fw::CmdStringArg& destFilename
219  )
220  {
221  struct FileEntry entry;
222  entry.srcFilename[0] = 0;
223  entry.destFilename[0] = 0;
224  entry.offset = 0;
225  entry.length = 0;
226  entry.source = FileDownlink::COMMAND;
227  entry.opCode = opCode;
228  entry.cmdSeq = cmdSeq;
229  entry.context = std::numeric_limits<U32>::max();
230 
231 
232  FW_ASSERT(sourceFilename.length() < sizeof(entry.srcFilename));
233  FW_ASSERT(destFilename.length() < sizeof(entry.destFilename));
234  (void) Fw::StringUtils::string_copy(entry.srcFilename, sourceFilename.toChar(), static_cast<FwSizeType>(sizeof(entry.srcFilename)));
235  (void) Fw::StringUtils::string_copy(entry.destFilename, destFilename.toChar(), static_cast<FwSizeType>(sizeof(entry.destFilename)));
236 
237  Os::Queue::Status status = m_fileQueue.send(reinterpret_cast<U8*>(&entry), static_cast<FwSizeType>(sizeof(entry)), 0, Os::Queue::BlockingType::NONBLOCKING);
238 
239  if(status != Os::Queue::Status::OP_OK) {
240  this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
241  }
242  }
243 
244  void FileDownlink ::
245  SendPartial_cmdHandler(
246  FwOpcodeType opCode,
247  U32 cmdSeq,
248  const Fw::CmdStringArg& sourceFilename,
249  const Fw::CmdStringArg& destFilename,
250  U32 startOffset,
251  U32 length
252  )
253  {
254  struct FileEntry entry;
255  entry.srcFilename[0] = 0;
256  entry.destFilename[0] = 0;
257  entry.offset = startOffset;
258  entry.length = length;
259  entry.source = FileDownlink::COMMAND;
260  entry.opCode = opCode;
261  entry.cmdSeq = cmdSeq;
262  entry.context = std::numeric_limits<U32>::max();
263 
264 
265  FW_ASSERT(sourceFilename.length() < sizeof(entry.srcFilename));
266  FW_ASSERT(destFilename.length() < sizeof(entry.destFilename));
267  (void) Fw::StringUtils::string_copy(entry.srcFilename, sourceFilename.toChar(), static_cast<FwSizeType>(sizeof(entry.srcFilename)));
268  (void) Fw::StringUtils::string_copy(entry.destFilename, destFilename.toChar(), static_cast<FwSizeType>(sizeof(entry.destFilename)));
269 
270  Os::Queue::Status status = m_fileQueue.send(reinterpret_cast<U8*>(&entry), static_cast<FwSizeType>(sizeof(entry)), 0, Os::Queue::BlockingType::NONBLOCKING);
271 
272  if(status != Os::Queue::Status::OP_OK) {
273  this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR);
274  }
275  }
276 
277  void FileDownlink ::
278  Cancel_cmdHandler(
279  const FwOpcodeType opCode,
280  const U32 cmdSeq
281  )
282  {
283  //Must be able to cancel in both downlink and waiting states
284  if (this->m_mode.get() == Mode::DOWNLINK || this->m_mode.get() == Mode::WAIT) {
285  this->m_mode.set(Mode::CANCEL);
286  }
287  this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK);
288  }
289 
290  // ----------------------------------------------------------------------
291  // Private helper methods
292  // ----------------------------------------------------------------------
293 
294  Fw::CmdResponse FileDownlink ::
295  statusToCmdResp(SendFileStatus status)
296  {
297  switch(status.e) {
299  return Fw::CmdResponse::OK;
305  return Fw::CmdResponse::BUSY;
306  default:
307  // Trigger assertion if given unknown status
308  FW_ASSERT(false);
309  }
310 
311  // It's impossible to reach this, but added to suppress gcc missing return warning
313  }
314 
315  void FileDownlink ::
316  sendResponse(SendFileStatus resp)
317  {
318  if(this->m_curEntry.source == FileDownlink::COMMAND) {
319  this->cmdResponse_out(this->m_curEntry.opCode, this->m_curEntry.cmdSeq, statusToCmdResp(resp));
320  } else {
321  for(NATIVE_INT_TYPE i = 0; i < this->getNum_FileComplete_OutputPorts(); i++) {
323  this->FileComplete_out(i, Svc::SendFileResponse(resp, this->m_curEntry.context));
324  }
325  }
326  }
327  }
328 
329  void FileDownlink ::
330  sendFile(
331  const char* sourceFilename,
332  const char* destFilename,
333  U32 startOffset,
334  U32 length
335  )
336  {
337  // Open file for downlink
338  Os::File::Status status = this->m_file.open(
339  sourceFilename,
340  destFilename
341  );
342 
343  // Reject command if error when opening file
344  if (status != Os::File::OP_OK) {
345  this->m_mode.set(Mode::IDLE);
346  this->m_warnings.fileOpenError();
348  return;
349  }
350 
351 
352  if (startOffset >= this->m_file.getSize()) {
353  this->enterCooldown();
354  this->log_WARNING_HI_DownlinkPartialFail(this->m_file.getSourceName(), this->m_file.getDestName(), startOffset, this->m_file.getSize());
356  return;
357  } else if (startOffset + length > this->m_file.getSize()) {
358  // If the amount to downlink is greater than the file size, emit a Warning and then allow
359  // the file to be downlinked anyway
360  this->log_WARNING_LO_DownlinkPartialWarning(startOffset, length, this->m_file.getSize(), this->m_file.getSourceName(), this->m_file.getDestName());
361  length = this->m_file.getSize() - startOffset;
362  }
363 
364  // Send file and switch to WAIT mode
365  this->getBuffer(this->m_buffer, FILE_PACKET);
366  this->sendStartPacket();
367  this->m_mode.set(Mode::WAIT);
368  this->m_sequenceIndex = 1;
369  this->m_curTimer = 0;
370  this->m_byteOffset = startOffset;
371  this->m_lastCompletedType = Fw::FilePacket::T_START;
372 
373  // zero length means read until end of file
374  if (length > 0) {
375  this->log_ACTIVITY_HI_SendStarted(length, this->m_file.getSourceName(), this->m_file.getDestName());
376  this->m_endOffset = startOffset + length;
377  }
378  else {
379  this->log_ACTIVITY_HI_SendStarted(this->m_file.getSize() - startOffset, this->m_file.getSourceName(), this->m_file.getDestName());
380  this->m_endOffset = this->m_file.getSize();
381  }
382  }
383 
384  Os::File::Status FileDownlink ::
385  sendDataPacket(U32 &byteOffset)
386  {
387  FW_ASSERT(byteOffset < this->m_endOffset);
389  const U32 dataSize = (byteOffset + maxDataSize > this->m_endOffset) ? (this->m_endOffset - byteOffset) : maxDataSize;
391  //This will be last data packet sent
392  if (dataSize + byteOffset == this->m_endOffset) {
393  this->m_lastCompletedType = Fw::FilePacket::T_DATA;
394  }
395 
396  const Os::File::Status status =
397  this->m_file.read(buffer, byteOffset, dataSize);
398  if (status != Os::File::OP_OK) {
399  this->m_warnings.fileRead(status);
400  return status;
401  }
402 
403  Fw::FilePacket::DataPacket dataPacket;
404  dataPacket.initialize(
405  this->m_sequenceIndex,
406  byteOffset,
407  static_cast<U16>(dataSize),
408  buffer);
409  ++this->m_sequenceIndex;
410  Fw::FilePacket filePacket;
411  filePacket.fromDataPacket(dataPacket);
412  this->sendFilePacket(filePacket);
413 
414  byteOffset += dataSize;
415 
416  return Os::File::OP_OK;
417 
418  }
419 
420  void FileDownlink ::
421  sendCancelPacket()
422  {
423  Fw::Buffer buffer;
424  Fw::FilePacket::CancelPacket cancelPacket;
425  cancelPacket.initialize(this->m_sequenceIndex);
426 
427  Fw::FilePacket filePacket;
428  filePacket.fromCancelPacket(cancelPacket);
429  this->getBuffer(buffer, CANCEL_PACKET);
430 
431  const Fw::SerializeStatus status = filePacket.toBuffer(buffer);
432  FW_ASSERT(status == Fw::FW_SERIALIZE_OK);
433  this->bufferSendOut_out(0, buffer);
434  this->m_packetsSent.packetSent();
435  }
436 
437  void FileDownlink ::
438  sendEndPacket()
439  {
440  CFDP::Checksum checksum;
441  this->m_file.getChecksum(checksum);
442 
443  Fw::FilePacket::EndPacket endPacket;
444  endPacket.initialize(this->m_sequenceIndex, checksum);
445 
446  Fw::FilePacket filePacket;
447  filePacket.fromEndPacket(endPacket);
448  this->sendFilePacket(filePacket);
449 
450  }
451 
452  void FileDownlink ::
453  sendStartPacket()
454  {
455  Fw::FilePacket::StartPacket startPacket;
456  startPacket.initialize(
457  this->m_file.getSize(),
458  this->m_file.getSourceName().toChar(),
459  this->m_file.getDestName().toChar()
460  );
461  Fw::FilePacket filePacket;
462  filePacket.fromStartPacket(startPacket);
463  this->sendFilePacket(filePacket);
464  }
465 
466  void FileDownlink ::
467  sendFilePacket(const Fw::FilePacket& filePacket)
468  {
469  const U32 bufferSize = filePacket.bufferSize();
470  FW_ASSERT(this->m_buffer.getData() != nullptr);
471  FW_ASSERT(
472  this->m_buffer.getSize() >= bufferSize,
473  static_cast<FwAssertArgType>(bufferSize),
474  static_cast<FwAssertArgType>(this->m_buffer.getSize()));
475  const Fw::SerializeStatus status = filePacket.toBuffer(this->m_buffer);
476  FW_ASSERT(status == Fw::FW_SERIALIZE_OK);
477  // set the buffer size to the packet size
478  this->m_buffer.setSize(bufferSize);
479  this->bufferSendOut_out(0, this->m_buffer);
480  // restore buffer size to max
482  this->m_packetsSent.packetSent();
483  }
484 
485  void FileDownlink ::
486  enterCooldown()
487  {
488  this->m_file.getOsFile().close();
489  this->m_mode.set(Mode::COOLDOWN);
490  this->m_lastCompletedType = Fw::FilePacket::T_NONE;
491  this->m_curTimer = 0;
492  }
493 
494  void FileDownlink ::
495  downlinkPacket()
496  {
497  FW_ASSERT(this->m_lastCompletedType != Fw::FilePacket::T_NONE, this->m_lastCompletedType);
498  FW_ASSERT(this->m_mode.get() == Mode::CANCEL || this->m_mode.get() == Mode::DOWNLINK, this->m_mode.get());
499  //If canceled mode and currently downlinking data then send a cancel packet
500  if (this->m_mode.get() == Mode::CANCEL && this->m_lastCompletedType == Fw::FilePacket::T_START) {
501  this->sendCancelPacket();
502  this->m_lastCompletedType = Fw::FilePacket::T_CANCEL;
503  }
504  //If in downlink mode and currently downlinking data then continue with the next packer
505  else if (this->m_mode.get() == Mode::DOWNLINK && this->m_lastCompletedType == Fw::FilePacket::T_START) {
506  //Send the next packet, or fail doing so
507  const Os::File::Status status = this->sendDataPacket(this->m_byteOffset);
508  if (status != Os::File::OP_OK) {
509  this->log_WARNING_HI_SendDataFail(this->m_file.getSourceName(), this->m_byteOffset);
510  this->enterCooldown();
512  //Don't go to wait state
513  return;
514  }
515  }
516  //If in downlink mode or cancel and finished downlinking data then send the last packet
517  else if (this->m_lastCompletedType == Fw::FilePacket::T_DATA) {
518  this->sendEndPacket();
519  this->m_lastCompletedType = Fw::FilePacket::T_END;
520  }
521  this->m_mode.set(Mode::WAIT);
522  this->m_curTimer = 0;
523  }
524 
525  void FileDownlink ::
526  finishHelper(bool cancel)
527  {
528  //Complete command and switch to IDLE
529  if (not cancel) {
530  this->m_filesSent.fileSent();
531  this->log_ACTIVITY_HI_FileSent(this->m_file.getSourceName(), this->m_file.getDestName());
532  } else {
533  this->log_ACTIVITY_HI_DownlinkCanceled(this->m_file.getSourceName(), this->m_file.getDestName());
534  }
535  this->enterCooldown();
536  sendResponse(SendFileStatus::STATUS_OK);
537  }
538 
539  void FileDownlink ::
540  getBuffer(Fw::Buffer& buffer, PacketType type)
541  {
542  //Check type is correct
543  FW_ASSERT(type < COUNT_PACKET_TYPE && type >= 0, type);
544  // Wrap the buffer around our indexed memory.
545  buffer.setData(this->m_memoryStore[type]);
547  //Set a known ID to look for later
548  buffer.setContext(m_lastBufferId);
549  m_lastBufferId++;
550  }
551 } // end namespace Svc
#define FW_ASSERT(...)
Definition: Assert.hpp:14
PlatformIntType NATIVE_INT_TYPE
Definition: BasicTypes.h:55
uint8_t U8
8-bit unsigned integer
Definition: BasicTypes.h:30
PlatformAssertArgType FwAssertArgType
Definition: FpConfig.h:39
U32 FwOpcodeType
Definition: FpConfig.h:91
PlatformSizeType FwSizeType
Definition: FpConfig.h:35
PlatformQueuePriorityType FwQueuePriorityType
Definition: FpConfig.h:55
C++-compatible configuration header for fprime configuration.
Class representing a 32-bit checksum as mandated by the CCSDS File Delivery Protocol.
Definition: Checksum.hpp:53
U8 * getData() const
Definition: Buffer.cpp:68
U32 getSize() const
Definition: Buffer.cpp:72
void setData(U8 *data)
Definition: Buffer.cpp:80
void setContext(U32 context)
Definition: Buffer.cpp:94
void setSize(U32 size)
Definition: Buffer.cpp:87
U32 getContext() const
Definition: Buffer.cpp:76
Enum representing a command response.
@ EXECUTION_ERROR
Command had execution error.
@ VALIDATION_ERROR
Command failed validation.
@ OK
Command successfully executed.
@ BUSY
Component busy.
const char * toChar() const
Definition: CmdString.hpp:50
The type of a cancel packet.
Definition: FilePacket.hpp:318
void initialize(const U32 sequenceIndex)
Initialize a cancel packet.
The type of a data packet.
Definition: FilePacket.hpp:197
void initialize(const U32 sequenceIndex, const U32 byteOffset, const U16 dataSize, const U8 *const data)
Initialize a data packet.
Definition: DataPacket.cpp:19
The type of an end packet.
Definition: FilePacket.hpp:269
void initialize(const U32 sequenceIndex, const CFDP::Checksum &checksum)
Initialize an end packet.
Definition: EndPacket.cpp:21
virtual const CHAR * toChar() const =0
SizeType length() const
Get length of string.
Definition: StringBase.cpp:125
@ OP_OK
Operation was successful.
Definition: File.hpp:30
Status send(const U8 *buffer, FwSizeType size, FwQueuePriorityType priority, BlockingType blockType) override
send a message into the queue through delegate
Status receive(U8 *destination, FwSizeType capacity, BlockingType blockType, FwSizeType &actualSize, FwQueuePriorityType &priority) override
receive a message from the queue through delegate
Status create(const Fw::StringBase &name, FwSizeType depth, FwSizeType messageSize) override
create queue storage through delegate
Definition: Queue.cpp:20
Status
status returned from the queue send function
Definition: Queue.hpp:30
@ OP_OK
message sent/received okay
Definition: Queue.hpp:31
char * string_copy(char *destination, const char *source, FwSizeType num)
copy string with null-termination guaranteed
Definition: StringUtils.cpp:6
SerializeStatus
forward declaration for string
@ FW_SERIALIZE_OK
Serialization/Deserialization operation was successful.
@ OP_OK
Operation succeeded.
Definition: Os.hpp:26
static const bool FILEDOWNLINK_COMMAND_FAILURES_DISABLED
static const U32 FILEDOWNLINK_INTERNAL_BUFFER_SIZE
The type of a start packet.
Definition: FilePacket.hpp:139
void initialize(const U32 fileSize, const char *const sourcePath, const char *const destinationPath)
Initialize a StartPacket with sequence number 0.
Definition: StartPacket.cpp:19
A file packet.
Definition: FilePacket.hpp:27
void fromCancelPacket(const CancelPacket &cancelPacket)
Definition: FilePacket.cpp:90
void fromEndPacket(const EndPacket &endPacket)
Definition: FilePacket.cpp:83
void fromDataPacket(const DataPacket &dataPacket)
Definition: FilePacket.cpp:76
void fromStartPacket(const StartPacket &startPacket)
Definition: FilePacket.cpp:69
U32 bufferSize() const
Definition: FilePacket.cpp:97
SerializeStatus toBuffer(Buffer &buffer) const
Definition: FilePacket.cpp:117