| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // ====================================================================== | ||
| 2 | // \title WasmSequencer.cpp | ||
| 3 | // \author tumbar | ||
| 4 | // \brief cpp file for WasmSequencer component implementation class | ||
| 5 | // ====================================================================== | ||
| 6 | |||
| 7 | #include "Svc/WasmSequencer/WasmSequencer.hpp" | ||
| 8 | |||
| 9 | #include "Fw/Cmd/CmdResponseEnumAc.hpp" | ||
| 10 | #include "Fw/Types/Assert.hpp" | ||
| 11 | #include "Fw/Types/LinearBufferTemplate.hpp" | ||
| 12 | #include "Fw/Types/Serializable.hpp" | ||
| 13 | #include "Fw/Types/SuccessEnumAc.hpp" | ||
| 14 | #include "Os/Mutex.hpp" | ||
| 15 | #include "Svc/Seq/BlockStateEnumAc.hpp" | ||
| 16 | #include "Svc/Seq/SeqArgsSerializableAc.hpp" | ||
| 17 | #include "Svc/WasmSequencer/WasmSequencer_CommandRequestSerializableAc.hpp" | ||
| 18 | #include "Svc/WasmSequencer/WasmSequencer_ControllerStateMachine_StateEnumAc.hpp" | ||
| 19 | #include "Svc/WasmSequencer/WasmSequencer_HostFunctionEnumAc.hpp" | ||
| 20 | #include "Svc/WasmSequencer/WasmSequencer_InvokeRequestSerializableAc.hpp" | ||
| 21 | #include "Svc/WasmSequencer/WasmSequencer_LoadRequestSerializableAc.hpp" | ||
| 22 | #include "Svc/WasmSequencer/WasmSequencer_SignalSourceEnumAc.hpp" | ||
| 23 | #include "Svc/WasmSequencer/fprime_spacewasm/include/fprime_spacewasm.h" | ||
| 24 | #include "Svc/WasmSequencer/spacewasm_include/spacewasm.h" | ||
| 25 | #include "config/CommandDispatcherImplCfg.hpp" | ||
| 26 | #include "config/FwAssertArgTypeAliasAc.h" | ||
| 27 | #include "config/FwIndexTypeAliasAc.h" | ||
| 28 | #include "config/FwSizeTypeAliasAc.h" | ||
| 29 | |||
| 30 | namespace Svc { | ||
| 31 | |||
| 32 | 329 | U8* WasmSequencer ::globalAllocCallback(void* userdata, size_t size, size_t align) { | |
| 33 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 329 times.
|
329 | if (userdata == nullptr) { |
| 34 | ✗ | return nullptr; | |
| 35 | } | ||
| 36 | 329 | return static_cast<WasmSequencer*>(userdata)->globalAlloc(static_cast<U32>(size), static_cast<U32>(align)); | |
| 37 | } | ||
| 38 | |||
| 39 | 329 | void WasmSequencer ::globalDeallocCallback(void* userdata, U8* ptr, size_t size, size_t align) { | |
| 40 | (void)size; | ||
| 41 | (void)align; | ||
| 42 |
1/2✓ Branch 0 taken 329 times.
✗ Branch 1 not taken.
|
329 | if (userdata != nullptr) { |
| 43 | 329 | static_cast<WasmSequencer*>(userdata)->globalDealloc(ptr); | |
| 44 | } | ||
| 45 | 329 | } | |
| 46 | |||
| 47 | // ---------------------------------------------------------------------- | ||
| 48 | // Component construction and destruction | ||
| 49 | // ---------------------------------------------------------------------- | ||
| 50 | |||
| 51 | 218 | WasmSequencer ::WasmSequencer(const char* const compName) | |
| 52 | : WasmSequencerComponentBase(compName), | ||
| 53 | 218 | m_heapPages(nullptr), | |
| 54 | 218 | m_heapPagesUsed(0), | |
| 55 | 218 | m_heapPoisoned(false), | |
| 56 | 218 | m_guestPool(nullptr), | |
| 57 | 218 | m_guestPoolOffset(0), | |
| 58 | 218 | m_wasm(nullptr), | |
| 59 | 218 | m_hasExecutingContext(false), | |
| 60 |
1/1✓ Branch 5 taken 218 times.
|
218 | m_pendingTimer(), |
| 61 | 218 | m_hasPendingTimer(false), | |
| 62 |
1/1✓ Branch 5 taken 218 times.
|
218 | m_hostFunctionStart(), |
| 63 | 218 | m_hasHostFunctionStart(false), | |
| 64 | 218 | m_dequeueSucceeded(false), | |
| 65 | 218 | m_invokeStatus(SPACEWASM_OK), | |
| 66 | 218 | m_pendingPause(false), | |
| 67 | 218 | m_cancelRequested(false), | |
| 68 | 218 | m_writeTelemetryQueued(false), | |
| 69 |
11/11✓ Branch 17 taken 218 times.
✓ Branch 23 taken 218 times.
✓ Branch 29 taken 218 times.
✓ Branch 35 taken 218 times.
✓ Branch 45 taken 218 times.
✓ Branch 51 taken 218 times.
✓ Branch 57 taken 1090 times.
✓ Branch 60 taken 1090 times.
✓ Branch 61 taken 218 times.
✓ Branch 67 taken 218 times.
✓ Branch 73 taken 218 times.
|
1962 | m_sequencesStarted(0) {} |
| 70 | |||
| 71 | 216 | void WasmSequencer ::configure(const Config& cfg, Fw::MemAllocator& mallocator) { | |
| 72 | 216 | FW_ASSERT(this->m_wasm == nullptr); | |
| 73 | 216 | FW_ASSERT(this->m_allocator == nullptr); | |
| 74 | |||
| 75 | 216 | getGlobalAllocatorLock()->lock(); | |
| 76 | 216 | const auto status = spacewasm_fprime_register_global_allocator(&globalAllocCallback, &globalDeallocCallback, this); | |
| 77 | 216 | getGlobalAllocatorLock()->unlock(); | |
| 78 | |||
| 79 | 216 | FW_ASSERT(status == SPACEWASM_OK, status); | |
| 80 | |||
| 81 | 216 | this->m_config = cfg; | |
| 82 | |||
| 83 | // Allocate the heap memory pool | ||
| 84 | { | ||
| 85 | 216 | FW_ASSERT( | |
| 86 | this->m_config.heapPages > 0 && this->m_config.heapPages <= Svc::WasmSequencerConfig::SPACEWASM_MAX_PAGES, | ||
| 87 | static_cast<FwAssertArgType>(this->m_config.heapPages), Svc::WasmSequencerConfig::SPACEWASM_MAX_PAGES); | ||
| 88 | |||
| 89 | // Allocate the heap page list | ||
| 90 | { | ||
| 91 | 216 | FwSizeType actualSize = sizeof(U8*) * this->m_config.heapPages; | |
| 92 |
1/1✓ Branch 4 taken 216 times.
|
216 | auto ptr = mallocator.checkedAllocate(0, actualSize); |
| 93 | 216 | this->m_heapPages = reinterpret_cast<U8**>(ptr); | |
| 94 | } | ||
| 95 | |||
| 96 | // Allocate each heap page | ||
| 97 |
2/2✓ Branch 4 taken 864 times.
✓ Branch 5 taken 216 times.
|
1080 | for (FwSizeType i = 0; i < this->m_config.heapPages; i++) { |
| 98 | 864 | auto actualSize = Svc::WasmSequencerConfig::SPACEWASM_PAGE_SIZE; | |
| 99 | auto ptr = | ||
| 100 |
1/1✓ Branch 4 taken 864 times.
|
864 | mallocator.checkedAllocate(static_cast<FwIndexType>(i) + 1, actualSize, SPACEWASM_MEMORY_ALIGNMENT); |
| 101 | 864 | this->m_heapPages[i] = reinterpret_cast<U8*>(ptr); | |
| 102 | } | ||
| 103 | 216 | this->m_heapPagesUsed = 0; | |
| 104 | } | ||
| 105 | |||
| 106 | // Allocate the guest memory pool | ||
| 107 | { | ||
| 108 | 216 | auto actualSize = this->m_config.guestMemorySize; | |
| 109 |
1/1✓ Branch 8 taken 216 times.
|
216 | auto ptr = mallocator.checkedAllocate(static_cast<FwIndexType>(this->m_config.heapPages) + 1, actualSize, |
| 110 | SPACEWASM_MEMORY_ALIGNMENT); | ||
| 111 | 216 | this->m_guestPoolOffset = 0; | |
| 112 | 216 | this->m_guestPool = reinterpret_cast<U8*>(ptr); | |
| 113 | } | ||
| 114 | |||
| 115 | // Allocate the serialOut buffer | ||
| 116 |
2/2✓ Branch 4 taken 215 times.
✓ Branch 5 taken 1 times.
|
216 | if (this->m_config.serialOutMax > 0) { |
| 117 | 215 | auto actualSize = this->m_config.serialOutMax; | |
| 118 |
1/1✓ Branch 8 taken 215 times.
|
215 | auto ptr = mallocator.checkedAllocate(static_cast<FwIndexType>(this->m_config.heapPages) + 2, actualSize); |
| 119 |
1/1✓ Branch 6 taken 215 times.
|
215 | this->m_serialOutBuffer.setExtBuffer(reinterpret_cast<U8*>(ptr), actualSize); |
| 120 | } | ||
| 121 | |||
| 122 | // Allocate the serialIn queues | ||
| 123 | { | ||
| 124 |
1/1✓ Branch 5 taken 216 times.
|
216 | Os::ScopeLock scopeLock(this->m_serialInMutex); |
| 125 |
2/2✓ Branch 0 taken 1080 times.
✓ Branch 1 taken 216 times.
|
1296 | for (FwIndexType i = 0; i < NUM_SERIALIN_INPUT_PORTS; i++) { |
| 126 |
2/2✓ Branch 5 taken 1079 times.
✓ Branch 6 taken 1 times.
|
1080 | if (this->m_config.serialIn[i].size > 0) { |
| 127 | 1079 | auto actualSize = this->m_config.serialIn[i].size; | |
| 128 | auto ptr = | ||
| 129 |
1/1✓ Branch 8 taken 1079 times.
|
1079 | mallocator.checkedAllocate(static_cast<FwIndexType>(this->m_config.heapPages) + 3 + i, actualSize); |
| 130 |
1/1✓ Branch 5 taken 1079 times.
|
1079 | this->m_serialInQueue[i].setup(reinterpret_cast<U8*>(ptr), actualSize); |
| 131 | } | ||
| 132 | } | ||
| 133 | 216 | } | |
| 134 | |||
| 135 | // Allocate the initial store | ||
| 136 | 216 | this->m_allocator = &mallocator; | |
| 137 | 216 | this->createStore(); | |
| 138 | 216 | } | |
| 139 | |||
| 140 | 218 | void WasmSequencer ::deinit() { | |
| 141 |
2/2✓ Branch 4 taken 216 times.
✓ Branch 5 taken 2 times.
|
218 | if (this->m_wasm != nullptr) { |
| 142 | 216 | this->destroyStore(); | |
| 143 | } | ||
| 144 | |||
| 145 |
2/2✓ Branch 4 taken 216 times.
✓ Branch 5 taken 2 times.
|
218 | if (this->m_allocator != nullptr) { |
| 146 | // Deallocate each heap page | ||
| 147 |
2/2✓ Branch 4 taken 864 times.
✓ Branch 5 taken 216 times.
|
1080 | for (FwSizeType i = 0; i < this->m_config.heapPages; i++) { |
| 148 | 864 | this->m_allocator->deallocate(static_cast<FwIndexType>(i) + 1, this->m_heapPages[i]); | |
| 149 | 864 | this->m_heapPages[i] = nullptr; | |
| 150 | } | ||
| 151 | |||
| 152 | // Deallocate the heap page pool | ||
| 153 | 216 | this->m_allocator->deallocate(0, this->m_heapPages); | |
| 154 | |||
| 155 | // Deallocate the guest memory pool | ||
| 156 |
1/2✓ Branch 4 taken 216 times.
✗ Branch 5 not taken.
|
216 | if (this->m_guestPool != nullptr) { |
| 157 | 216 | this->m_allocator->deallocate(static_cast<FwIndexType>(this->m_config.heapPages) + 1, this->m_guestPool); | |
| 158 | } | ||
| 159 | |||
| 160 | // Deallocate the serialOut buffer | ||
| 161 |
2/2✓ Branch 6 taken 215 times.
✓ Branch 7 taken 1 times.
|
216 | if (this->m_serialOutBuffer.getBuffAddr() != nullptr) { |
| 162 | 430 | this->m_allocator->deallocate(static_cast<FwIndexType>(this->m_config.heapPages) + 2, | |
| 163 | 215 | this->m_serialOutBuffer.getBuffAddr()); | |
| 164 | } | ||
| 165 | |||
| 166 | // Deallocate the serialIn queues | ||
| 167 |
2/2✓ Branch 0 taken 1080 times.
✓ Branch 1 taken 216 times.
|
1296 | for (FwIndexType i = 0; i < NUM_SERIALIN_INPUT_PORTS; i++) { |
| 168 |
2/2✓ Branch 5 taken 1078 times.
✓ Branch 6 taken 2 times.
|
1080 | if (this->m_serialInQueue[i].get_capacity() > 0) { |
| 169 | 2156 | this->m_allocator->deallocate(static_cast<FwIndexType>(this->m_config.heapPages) + 3 + i, | |
| 170 | 1078 | this->m_serialInQueue[i].get_buffer()); | |
| 171 | } | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | // Release our slot in the process-wide global-allocator registry so it can | ||
| 176 | // be reused by a later sequencer instance. | ||
| 177 | 218 | getGlobalAllocatorLock()->lock(); | |
| 178 | 218 | (void)spacewasm_fprime_deregister_global_allocator(this); | |
| 179 | 218 | getGlobalAllocatorLock()->unlock(); | |
| 180 | |||
| 181 | // Clean up dangling pointers | ||
| 182 | 218 | this->m_allocator = nullptr; | |
| 183 | 218 | this->m_wasm = nullptr; | |
| 184 | |||
| 185 | 218 | WasmSequencerComponentBase::deinit(); | |
| 186 | 218 | } | |
| 187 | |||
| 188 | // ---------------------------------------------------------------------- | ||
| 189 | // Handler implementations for typed input ports | ||
| 190 | // ---------------------------------------------------------------------- | ||
| 191 | |||
| 192 | 11 | void WasmSequencer ::checkTimers_handler(FwIndexType portNum, U32 context) { | |
| 193 | // Drive the sleep-wake / host-function-timeout checks in the state machine. | ||
| 194 | 11 | this->interpreter_sendSignal_checkTimers(); | |
| 195 | 11 | } | |
| 196 | |||
| 197 | 18 | void WasmSequencer ::cmdResponseIn_handler(FwIndexType portNum, | |
| 198 | FwOpcodeType opCode, | ||
| 199 | U32 cmdSeq, | ||
| 200 | const Fw::CmdResponse& response) { | ||
| 201 | 18 | FW_ASSERT(this->m_wasm != nullptr); | |
| 202 | |||
| 203 | // The CmdDisp echoes back the context we sent, not a real cmdSeq. We packed | ||
| 204 | // our cmdUid into that context (see makeCmdUid); rename for clarity. | ||
| 205 | 18 | const U32 cmdUid = cmdSeq; | |
| 206 | 18 | const U16 sequenceIndex = static_cast<U16>((cmdUid & 0xFFFF0000) >> 16); | |
| 207 | 18 | const U16 cmdIndex = static_cast<U16>(cmdUid & 0xFFFF); | |
| 208 | 18 | const U16 currentSequenceIndex = static_cast<U16>(this->m_sequencesStarted & 0xFFFF); | |
| 209 | 18 | const U16 currentCmdIndex = static_cast<U16>(this->m_tlm.commandsDispatched & 0xFFFF); | |
| 210 | |||
| 211 | // If the response is from a previous execution window, treat it as a nominal | ||
| 212 | // late reply (e.g. a command that returned after a CANCEL) and just report it | ||
| 213 | // without failing the current sequence. | ||
| 214 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 16 times.
|
18 | if (sequenceIndex != currentSequenceIndex) { |
| 215 | 2 | this->log_WARNING_LO_CmdResponseFromOldSequence(CmdDispatcherCfg::getEventOpcode(opCode), response, | |
| 216 | sequenceIndex, currentSequenceIndex); | ||
| 217 | 2 | return; | |
| 218 | } | ||
| 219 | |||
| 220 | // From here on the response claims to be from the current sequence, so any | ||
| 221 | // inconsistency is a genuine error that should fail the sequence. | ||
| 222 |
4/9✓ Branch 2 taken 16 times.
✓ Branch 10 taken 16 times.
✗ Branch 11 not taken.
✓ Branch 13 taken 5 times.
✓ Branch 14 taken 11 times.
✗ Branch 15 not taken.
✗ Branch 16 not taken.
✗ Branch 19 not taken.
✗ Branch 20 not taken.
|
32 | if (this->interpreter_getState() != |
| 223 |
4/6✓ Branch 1 taken 11 times.
✓ Branch 2 taken 5 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 11 times.
✓ Branch 5 taken 16 times.
✗ Branch 6 not taken.
|
43 | WasmSequencer_InterpreterStateMachine_State::RUNNING_AWAITING_RESPONSE_WAITING || |
| 224 | 11 | this->m_pendingHostFunction.kind != WasmSequencer_HostFunction::COMMAND) { | |
| 225 |
2/2✓ Branch 6 taken 5 times.
✓ Branch 10 taken 5 times.
|
5 | this->interpreter_sendSignal_hostResponseUnexpected(WasmSequencer_HostFunction::COMMAND); |
| 226 | 5 | return; | |
| 227 | } | ||
| 228 | |||
| 229 | // Awaiting a command response, but was it for this exact dispatch instance, or | ||
| 230 | // an earlier one in this sequence with the same opcode? | ||
| 231 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 10 times.
|
11 | if (cmdIndex != currentCmdIndex) { |
| 232 | 1 | this->log_WARNING_HI_WrongCmdResponseIndex(CmdDispatcherCfg::getEventOpcode(opCode), response, cmdIndex, | |
| 233 | currentCmdIndex); | ||
| 234 |
2/2✓ Branch 6 taken 1 times.
✓ Branch 10 taken 1 times.
|
1 | this->interpreter_sendSignal_hostResponseUnexpected(WasmSequencer_HostFunction::COMMAND); |
| 235 | 1 | return; | |
| 236 | } | ||
| 237 | |||
| 238 | 10 | this->m_pendingHostFunction.clear(); | |
| 239 | |||
| 240 | // Track commands that came back with a non-OK response. | ||
| 241 |
2/2✓ Branch 4 taken 1 times.
✓ Branch 5 taken 9 times.
|
10 | if (response != Fw::CmdResponse::OK) { |
| 242 | 1 | this->m_tlm.commandsFailed++; | |
| 243 | } | ||
| 244 | |||
| 245 |
1/2✗ Branch 7 not taken.
✓ Branch 8 taken 10 times.
|
10 | this->interpreter_sendSignal_hostResumeI32(static_cast<I32>(response.e)); |
| 246 | } | ||
| 247 | |||
| 248 | 28 | void WasmSequencer ::writeTelemetry_handler(FwIndexType portNum, U32 context) { | |
| 249 | 28 | bool expected = false; | |
| 250 |
1/2✓ Branch 4 taken 28 times.
✗ Branch 5 not taken.
|
28 | if (this->m_writeTelemetryQueued.compare_exchange_strong(expected, true)) { |
| 251 |
1/1✓ Branch 5 taken 28 times.
|
28 | this->writeTelemetryInternal_internalInterfaceInvoke(); |
| 252 | } | ||
| 253 | 28 | } | |
| 254 | |||
| 255 | ✗ | void WasmSequencer ::writeTelemetryInternal_overflowHook() { | |
| 256 | ✗ | this->m_writeTelemetryQueued = false; | |
| 257 | ✗ | } | |
| 258 | |||
| 259 | 28 | void WasmSequencer ::writeTelemetryInternal_internalInterfaceHandler() { | |
| 260 | 28 | this->m_writeTelemetryQueued = false; | |
| 261 | 28 | FW_ASSERT(this->m_wasm != nullptr); | |
| 262 | |||
| 263 |
1/1✓ Branch 3 taken 28 times.
|
28 | auto now = this->getTime(); |
| 264 | |||
| 265 |
3/3✓ Branch 6 taken 28 times.
✓ Branch 11 taken 28 times.
✓ Branch 15 taken 28 times.
|
28 | this->tlmWrite_ControllerState(this->controller_getState(), now); |
| 266 |
3/3✓ Branch 6 taken 28 times.
✓ Branch 11 taken 28 times.
✓ Branch 15 taken 28 times.
|
28 | this->tlmWrite_InterpreterState(this->interpreter_getState(), now); |
| 267 |
2/2✓ Branch 6 taken 28 times.
✓ Branch 13 taken 28 times.
|
28 | this->tlmWrite_SequencesSucceeded(this->m_tlm.sequencesSucceeded, now); |
| 268 |
2/2✓ Branch 6 taken 28 times.
✓ Branch 13 taken 28 times.
|
28 | this->tlmWrite_SequencesFailed(this->m_tlm.sequencesFailed, now); |
| 269 |
2/2✓ Branch 6 taken 28 times.
✓ Branch 13 taken 28 times.
|
28 | this->tlmWrite_SequencesCancelled(this->m_tlm.sequencesCancelled, now); |
| 270 |
2/2✓ Branch 6 taken 28 times.
✓ Branch 13 taken 28 times.
|
28 | this->tlmWrite_CommandsDispatched(this->m_tlm.commandsDispatched, now); |
| 271 |
2/2✓ Branch 6 taken 28 times.
✓ Branch 13 taken 28 times.
|
28 | this->tlmWrite_CommandsFailed(this->m_tlm.commandsFailed, now); |
| 272 |
2/2✓ Branch 6 taken 28 times.
✓ Branch 12 taken 28 times.
|
28 | this->tlmWrite_LastTrapReason(this->m_exit.lastTrapReason, now); |
| 273 |
2/2✓ Branch 6 taken 28 times.
✓ Branch 12 taken 28 times.
|
28 | this->tlmWrite_SeqName(this->m_tlm.sequenceName, now); |
| 274 | 56 | } | |
| 275 | |||
| 276 | 12 | void WasmSequencer ::seqRunIn_handler(FwIndexType portNum, const Fw::StringBase& filename, const Svc::SeqArgs& args) { | |
| 277 | 12 | FW_ASSERT(this->m_wasm != nullptr); | |
| 278 | |||
| 279 |
1/1✓ Branch 2 taken 12 times.
|
12 | Fw::String runModuleName = ""; |
| 280 | |||
| 281 |
2/2✓ Branch 6 taken 12 times.
✓ Branch 10 taken 12 times.
|
36 | this->controller_sendSignal_run(Svc::WasmSequencer_LoadRequest( |
| 282 | filename, runModuleName, | ||
| 283 |
2/2✓ Branch 3 taken 12 times.
✓ Branch 7 taken 12 times.
|
24 | Svc::WasmSequencer_RequestContext(WasmSequencer_SignalSource::PORT_RUN, WasmSequencer_CommandRequest(0, 0), |
| 284 | BlockState::NO_BLOCK, | ||
| 285 | /* moduleIdx */ 0 // placeholder, gets filled in after load | ||
| 286 | ))); | ||
| 287 | 24 | } | |
| 288 | |||
| 289 | 2 | void WasmSequencer ::seqCancelIn_handler(FwIndexType portNum) { | |
| 290 | 2 | this->controller_sendSignal_cancel(); | |
| 291 | 2 | this->interpreter_sendSignal_cancel(); | |
| 292 | 2 | } | |
| 293 | |||
| 294 | // ---------------------------------------------------------------------- | ||
| 295 | // Handler implementations for serial input ports | ||
| 296 | // ---------------------------------------------------------------------- | ||
| 297 | |||
| 298 | 32 | void WasmSequencer ::serialIn_handler(FwIndexType portNum, Fw::LinearBufferBase& buffer) { | |
| 299 | 32 | FW_ASSERT(portNum < NUM_SERIALIN_INPUT_PORTS, portNum, NUM_SERIALIN_INPUT_PORTS); | |
| 300 |
1/1✓ Branch 5 taken 32 times.
|
32 | Os::ScopeLock scopeLock(this->m_serialInMutex); |
| 301 | 32 | auto& queue = this->m_serialInQueue[portNum]; | |
| 302 | 32 | auto fullFullBehavior = this->m_config.serialIn[portNum].fullBehavior; | |
| 303 | |||
| 304 | // Each message is framed on the queue as [U32 length][payload] | ||
| 305 | 32 | const FwSizeType headerSize = sizeof(U32); | |
| 306 |
1/1✓ Branch 7 taken 32 times.
|
32 | const FwSizeType payloadSize = buffer.getSize(); |
| 307 |
1/1✓ Branch 2 taken 32 times.
|
32 | const FwSizeType capacity = queue.get_capacity(); |
| 308 | |||
| 309 | // Make sure the queue is sized to hold this framed message at all. This does | ||
| 310 | // not check free space, only that the queue's capacity is large enough. | ||
| 311 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 29 times.
|
32 | if (headerSize + payloadSize > capacity) { |
| 312 | // The largest payload this queue could ever hold is capacity - headerSize. A queue smaller | ||
| 313 | // than the header (including a port left un-configured with capacity 0) can hold nothing. | ||
| 314 | 3 | const FwSizeType maxPayload = (capacity > headerSize) ? (capacity - headerSize) : 0; | |
| 315 |
1/1✓ Branch 5 taken 3 times.
|
3 | this->log_WARNING_HI_SerialInFrameTooLarge(static_cast<U32>(portNum), static_cast<U32>(payloadSize), |
| 316 | static_cast<U32>(maxPayload)); | ||
| 317 | 3 | return; | |
| 318 | } | ||
| 319 | |||
| 320 | // Total framed size; <= capacity by the assertions above, so it cannot overflow. | ||
| 321 | 29 | const FwSizeType frameSize = headerSize + payloadSize; | |
| 322 | |||
| 323 | // Check if we _can_ push the data to the queue | ||
| 324 |
3/3✓ Branch 2 taken 29 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 25 times.
|
29 | if (frameSize > queue.get_free_size()) { |
| 325 | // The queue is full and cannot push this data | ||
| 326 |
2/4✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
|
4 | switch (fullFullBehavior) { |
| 327 | 2 | case SerialInQueueFullBehavior::DROP_OLDEST: | |
| 328 | // Drop oldest messages until this one fits. | ||
| 329 |
3/3✓ Branch 2 taken 4 times.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 2 times.
|
4 | while (frameSize > queue.get_free_size()) { |
| 330 | 2 | U32 nextMsgSize; | |
| 331 |
1/1✓ Branch 2 taken 2 times.
|
2 | auto status = queue.peek(nextMsgSize); |
| 332 | 2 | FW_ASSERT(status == Fw::FW_SERIALIZE_OK, portNum, status); | |
| 333 | |||
| 334 |
1/1✓ Branch 2 taken 2 times.
|
2 | const FwSizeType allocated = queue.get_allocated_size(); |
| 335 | 2 | FW_ASSERT(allocated >= headerSize, portNum, static_cast<FwAssertArgType>(allocated)); | |
| 336 | 2 | FW_ASSERT(static_cast<FwSizeType>(nextMsgSize) <= allocated - headerSize, portNum, | |
| 337 | static_cast<FwAssertArgType>(nextMsgSize), static_cast<FwAssertArgType>(allocated)); | ||
| 338 | |||
| 339 |
1/1✓ Branch 2 taken 2 times.
|
2 | status = queue.rotate(headerSize + nextMsgSize); |
| 340 | |||
| 341 | 2 | FW_ASSERT(status == Fw::FW_SERIALIZE_OK, portNum, status); | |
| 342 | } | ||
| 343 | |||
| 344 | // Now it fits, fall through to push the data to the queue | ||
| 345 | 2 | break; | |
| 346 | 2 | case SerialInQueueFullBehavior::DROP_NEWEST: | |
| 347 | // Drop this message | ||
| 348 | 2 | return; | |
| 349 | |||
| 350 | ✗ | case SerialInQueueFullBehavior::ASSERT: | |
| 351 | ✗ | FW_ASSERT(false, portNum, static_cast<FwAssertArgType>(frameSize), | |
| 352 | static_cast<FwAssertArgType>(queue.get_free_size()), static_cast<FwAssertArgType>(capacity)); | ||
| 353 | ✗ | break; | |
| 354 | } | ||
| 355 | } | ||
| 356 | |||
| 357 | // The message should be able to be put into the queue. | ||
| 358 | // Enqueue it as a raw [U32 size][payload] frame. We use the raw (const U8*, size) | ||
| 359 |
1/1✓ Branch 2 taken 27 times.
|
54 | Fw::LinearBufferTemplate<sizeof(U32)> sizeSer; |
| 360 |
1/1✓ Branch 2 taken 27 times.
|
27 | auto status = sizeSer.serializeFrom(static_cast<U32>(payloadSize)); |
| 361 | 27 | FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status); | |
| 362 | |||
| 363 |
1/1✓ Branch 4 taken 27 times.
|
27 | status = queue.serialize(sizeSer.getBuffAddr(), headerSize); |
| 364 | 27 | FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status); | |
| 365 | |||
| 366 |
1/1✓ Branch 6 taken 27 times.
|
27 | status = queue.serialize(buffer.getBuffAddr(), payloadSize); |
| 367 | 27 | FW_ASSERT(status == Fw::FW_SERIALIZE_OK, status); | |
| 368 | |||
| 369 | // Wake up any blocking recv calls | ||
| 370 |
1/1✓ Branch 5 taken 27 times.
|
27 | this->interpreter_sendSignal_serialInMessage(portNum); |
| 371 | 32 | } | |
| 372 | |||
| 373 | // ---------------------------------------------------------------------- | ||
| 374 | // Handler implementations for commands | ||
| 375 | // ---------------------------------------------------------------------- | ||
| 376 | |||
| 377 | 137 | void WasmSequencer ::RUN_cmdHandler(FwOpcodeType opCode, | |
| 378 | U32 cmdSeq, | ||
| 379 | const Fw::CmdStringArg& fileName, | ||
| 380 | const Svc::BlockState& block) { | ||
| 381 | 137 | FW_ASSERT(this->m_wasm != nullptr); | |
| 382 | |||
| 383 |
1/1✓ Branch 2 taken 137 times.
|
137 | Fw::String runModuleName = ""; |
| 384 |
2/2✓ Branch 9 taken 137 times.
✓ Branch 13 taken 137 times.
|
411 | this->controller_sendSignal_run(Svc::WasmSequencer_LoadRequest( |
| 385 | fileName, runModuleName, | ||
| 386 |
1/1✓ Branch 3 taken 137 times.
|
685 | Svc::WasmSequencer_RequestContext(WasmSequencer_SignalSource::COMMAND_RUN, |
| 387 |
1/1✓ Branch 2 taken 137 times.
|
274 | WasmSequencer_CommandRequest(opCode, cmdSeq), block, |
| 388 | /* moduleIdx */ 0 // placeholder, gets filled in after load | ||
| 389 | 137 | ))); | |
| 390 | 274 | } | |
| 391 | |||
| 392 | 16 | void WasmSequencer ::WAIT_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { | |
| 393 | 16 | FW_ASSERT(this->m_wasm != nullptr); | |
| 394 | |||
| 395 |
3/3✓ Branch 3 taken 16 times.
✓ Branch 12 taken 2 times.
✓ Branch 13 taken 14 times.
|
16 | switch (this->controller_getState()) { |
| 396 | 2 | case WasmSequencer_ControllerStateMachine_State::IDLE: | |
| 397 | case WasmSequencer_ControllerStateMachine_State::READY: | ||
| 398 | // Nothing is executing, respond immediately | ||
| 399 |
2/2✓ Branch 6 taken 2 times.
✓ Branch 9 taken 2 times.
|
2 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); |
| 400 | 2 | break; | |
| 401 | 14 | default: { | |
| 402 |
1/1✓ Branch 7 taken 14 times.
|
14 | const auto status = this->m_waiting.enqueue(WaitingCmd(opCode, cmdSeq)); |
| 403 |
2/2✓ Branch 2 taken 1 times.
✓ Branch 3 taken 13 times.
|
14 | if (status != Fw::Success::SUCCESS) { |
| 404 |
1/1✓ Branch 5 taken 1 times.
|
1 | this->log_WARNING_HI_TooManyBlockingCommands(); |
| 405 |
2/2✓ Branch 6 taken 1 times.
✓ Branch 9 taken 1 times.
|
1 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR); |
| 406 | } | ||
| 407 | 14 | } | |
| 408 | } | ||
| 409 | 16 | } | |
| 410 | |||
| 411 | 60 | void WasmSequencer ::LOAD_cmdHandler(FwOpcodeType opCode, | |
| 412 | U32 cmdSeq, | ||
| 413 | const Fw::CmdStringArg& fileName, | ||
| 414 | const Fw::CmdStringArg& name) { | ||
| 415 | 60 | FW_ASSERT(this->m_wasm != nullptr); | |
| 416 | |||
| 417 |
2/2✓ Branch 12 taken 60 times.
✓ Branch 16 taken 60 times.
|
180 | this->controller_sendSignal_load(Svc::WasmSequencer_LoadRequest( |
| 418 | fileName, name, | ||
| 419 |
1/1✓ Branch 2 taken 60 times.
|
240 | Svc::WasmSequencer_RequestContext(WasmSequencer_SignalSource::COMMAND_LOAD, |
| 420 |
1/1✓ Branch 2 taken 60 times.
|
120 | WasmSequencer_CommandRequest(opCode, cmdSeq), Svc::BlockState::BLOCK, |
| 421 | /* moduleIdx */ 0 // placeholder, gets filled in after load | ||
| 422 | ))); | ||
| 423 | 60 | } | |
| 424 | |||
| 425 | 13 | void WasmSequencer ::INVOKE_cmdHandler(FwOpcodeType opCode, | |
| 426 | U32 cmdSeq, | ||
| 427 | const Fw::CmdStringArg& module, | ||
| 428 | const Svc::BlockState& block) { | ||
| 429 | 13 | FW_ASSERT(this->m_wasm != nullptr); | |
| 430 | |||
| 431 |
2/2✓ Branch 9 taken 13 times.
✓ Branch 13 taken 13 times.
|
39 | this->controller_sendSignal_invoke(Svc::WasmSequencer_InvokeRequest( |
| 432 |
1/1✓ Branch 3 taken 13 times.
|
65 | module, Svc::WasmSequencer_RequestContext(WasmSequencer_SignalSource::COMMAND_INVOKE, |
| 433 |
1/1✓ Branch 2 taken 13 times.
|
26 | WasmSequencer_CommandRequest(opCode, cmdSeq), block, |
| 434 | /* moduleIdx */ 0 // placeholder, gets filled in after invoke | ||
| 435 | 13 | ))); | |
| 436 | 13 | } | |
| 437 | |||
| 438 | 25 | void WasmSequencer ::CANCEL_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { | |
| 439 | 25 | this->controller_sendSignal_cancel(); | |
| 440 |
2/2✓ Branch 6 taken 25 times.
✓ Branch 10 taken 25 times.
|
25 | this->interpreter_sendSignal_cmdCancel(WasmSequencer_CommandRequest(opCode, cmdSeq)); |
| 441 | 25 | } | |
| 442 | |||
| 443 | 9 | void WasmSequencer ::PAUSE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { | |
| 444 |
3/3✓ Branch 3 taken 9 times.
✓ Branch 12 taken 1 times.
✓ Branch 13 taken 8 times.
|
9 | if (this->interpreter_getState() == WasmSequencer_InterpreterStateMachine_State::IDLE) { |
| 445 | 1 | this->log_WARNING_LO_SequenceNotRunning(); | |
| 446 |
2/2✓ Branch 6 taken 1 times.
✓ Branch 9 taken 1 times.
|
1 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR); |
| 447 | 1 | return; | |
| 448 | } | ||
| 449 | |||
| 450 | 8 | this->m_pendingPause = true; | |
| 451 |
2/2✓ Branch 6 taken 8 times.
✓ Branch 9 taken 8 times.
|
8 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); |
| 452 | } | ||
| 453 | |||
| 454 | 5 | void WasmSequencer ::CONTINUE_cmdHandler(FwOpcodeType opCode, U32 cmdSeq) { | |
| 455 | 5 | FW_ASSERT(this->m_wasm != nullptr); | |
| 456 | |||
| 457 |
4/5✓ Branch 3 taken 5 times.
✓ Branch 12 taken 1 times.
✓ Branch 13 taken 3 times.
✓ Branch 14 taken 1 times.
✗ Branch 15 not taken.
|
5 | switch (this->interpreter_getState()) { |
| 458 | 1 | case WasmSequencer_InterpreterStateMachine_State::RUNNING_AWAITING_RESPONSE_SLEEPING: | |
| 459 | case WasmSequencer_InterpreterStateMachine_State::RUNNING_AWAITING_RESPONSE_WAITING: | ||
| 460 | case WasmSequencer_InterpreterStateMachine_State::RUNNING_SPINNING: | ||
| 461 | // Already running | ||
| 462 |
2/2✓ Branch 6 taken 1 times.
✓ Branch 9 taken 1 times.
|
1 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); |
| 463 | 1 | break; | |
| 464 | 3 | case WasmSequencer_InterpreterStateMachine_State::RUNNING_PAUSED: | |
| 465 | 3 | this->interpreter_sendSignal_cmd_CONTINUE(); | |
| 466 |
2/2✓ Branch 6 taken 3 times.
✓ Branch 9 taken 3 times.
|
3 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); |
| 467 | 3 | break; | |
| 468 | 1 | case WasmSequencer_InterpreterStateMachine_State::IDLE: | |
| 469 | 1 | this->log_WARNING_LO_SequenceNotRunning(); | |
| 470 |
2/2✓ Branch 6 taken 1 times.
✓ Branch 9 taken 1 times.
|
1 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR); |
| 471 | 1 | break; | |
| 472 | ✗ | default: | |
| 473 | ✗ | FW_ASSERT(false, this->interpreter_getState()); | |
| 474 | } | ||
| 475 | 5 | } | |
| 476 | |||
| 477 | 9 | void WasmSequencer ::GLOBAL_SET_I32_cmdHandler(FwOpcodeType opCode, | |
| 478 | U32 cmdSeq, | ||
| 479 | const Fw::CmdStringArg& moduleName, | ||
| 480 | const Fw::CmdStringArg& name, | ||
| 481 | I32 value) { | ||
| 482 | 9 | FW_ASSERT(this->m_wasm != nullptr); | |
| 483 | |||
| 484 | 9 | spacewasm_value_t s_value; | |
| 485 | 9 | s_value.tag = SPACEWASM_I32; | |
| 486 | 9 | s_value.u.i32_ = value; | |
| 487 | |||
| 488 |
1/1✓ Branch 10 taken 9 times.
|
9 | auto status = this->setGlobal(moduleName, name, s_value); |
| 489 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 4 times.
|
9 | if (status == SPACEWASM_OK) { |
| 490 |
2/2✓ Branch 6 taken 5 times.
✓ Branch 9 taken 5 times.
|
5 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); |
| 491 | } else { | ||
| 492 |
2/2✓ Branch 6 taken 4 times.
✓ Branch 16 taken 4 times.
|
4 | this->log_WARNING_LO_GlobalSetFailed(moduleName, name, status); |
| 493 |
2/2✓ Branch 6 taken 4 times.
✓ Branch 9 taken 4 times.
|
4 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR); |
| 494 | } | ||
| 495 | 9 | } | |
| 496 | |||
| 497 | 3 | void WasmSequencer ::GLOBAL_SET_I64_cmdHandler(FwOpcodeType opCode, | |
| 498 | U32 cmdSeq, | ||
| 499 | const Fw::CmdStringArg& moduleName, | ||
| 500 | const Fw::CmdStringArg& name, | ||
| 501 | I64 value) { | ||
| 502 | 3 | FW_ASSERT(this->m_wasm != nullptr); | |
| 503 | |||
| 504 | 3 | spacewasm_value_t s_value; | |
| 505 | 3 | s_value.tag = SPACEWASM_I64; | |
| 506 | 3 | s_value.u.i64_ = value; | |
| 507 | |||
| 508 |
1/1✓ Branch 10 taken 3 times.
|
3 | auto status = this->setGlobal(moduleName, name, s_value); |
| 509 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
|
3 | if (status == SPACEWASM_OK) { |
| 510 |
2/2✓ Branch 6 taken 2 times.
✓ Branch 9 taken 2 times.
|
2 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); |
| 511 | } else { | ||
| 512 |
2/2✓ Branch 6 taken 1 times.
✓ Branch 16 taken 1 times.
|
1 | this->log_WARNING_LO_GlobalSetFailed(moduleName, name, status); |
| 513 |
2/2✓ Branch 6 taken 1 times.
✓ Branch 9 taken 1 times.
|
1 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR); |
| 514 | } | ||
| 515 | 3 | } | |
| 516 | |||
| 517 | 3 | void WasmSequencer ::GLOBAL_SET_F32_cmdHandler(FwOpcodeType opCode, | |
| 518 | U32 cmdSeq, | ||
| 519 | const Fw::CmdStringArg& moduleName, | ||
| 520 | const Fw::CmdStringArg& name, | ||
| 521 | F32 value) { | ||
| 522 | 3 | FW_ASSERT(this->m_wasm != nullptr); | |
| 523 | |||
| 524 | 3 | spacewasm_value_t s_value; | |
| 525 | 3 | s_value.tag = SPACEWASM_F32; | |
| 526 | 3 | s_value.u.f32_ = value; | |
| 527 | |||
| 528 |
1/1✓ Branch 10 taken 3 times.
|
3 | auto status = this->setGlobal(moduleName, name, s_value); |
| 529 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
|
3 | if (status == SPACEWASM_OK) { |
| 530 |
2/2✓ Branch 6 taken 2 times.
✓ Branch 9 taken 2 times.
|
2 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); |
| 531 | } else { | ||
| 532 |
2/2✓ Branch 6 taken 1 times.
✓ Branch 16 taken 1 times.
|
1 | this->log_WARNING_LO_GlobalSetFailed(moduleName, name, status); |
| 533 |
2/2✓ Branch 6 taken 1 times.
✓ Branch 9 taken 1 times.
|
1 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR); |
| 534 | } | ||
| 535 | 3 | } | |
| 536 | |||
| 537 | 3 | void WasmSequencer ::GLOBAL_SET_F64_cmdHandler(FwOpcodeType opCode, | |
| 538 | U32 cmdSeq, | ||
| 539 | const Fw::CmdStringArg& moduleName, | ||
| 540 | const Fw::CmdStringArg& name, | ||
| 541 | F64 value) { | ||
| 542 | 3 | FW_ASSERT(this->m_wasm != nullptr); | |
| 543 | |||
| 544 | 3 | spacewasm_value_t g_value; | |
| 545 | 3 | g_value.tag = SPACEWASM_F64; | |
| 546 | 3 | g_value.u.f64_ = value; | |
| 547 | |||
| 548 |
1/1✓ Branch 10 taken 3 times.
|
3 | auto status = this->setGlobal(moduleName, name, g_value); |
| 549 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 1 times.
|
3 | if (status == SPACEWASM_OK) { |
| 550 |
2/2✓ Branch 6 taken 2 times.
✓ Branch 9 taken 2 times.
|
2 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); |
| 551 | } else { | ||
| 552 |
2/2✓ Branch 6 taken 1 times.
✓ Branch 16 taken 1 times.
|
1 | this->log_WARNING_LO_GlobalSetFailed(moduleName, name, status); |
| 553 |
2/2✓ Branch 6 taken 1 times.
✓ Branch 9 taken 1 times.
|
1 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR); |
| 554 | } | ||
| 555 | 3 | } | |
| 556 | |||
| 557 | 25 | void WasmSequencer ::GLOBAL_GET_cmdHandler(FwOpcodeType opCode, | |
| 558 | U32 cmdSeq, | ||
| 559 | const Fw::CmdStringArg& moduleName, | ||
| 560 | const Fw::CmdStringArg& name) { | ||
| 561 | 25 | FW_ASSERT(this->m_wasm != nullptr); | |
| 562 | |||
| 563 | 25 | spacewasm_value_t g_value; | |
| 564 |
1/1✓ Branch 10 taken 25 times.
|
25 | auto status = this->getGlobal(moduleName, name, g_value); |
| 565 |
2/2✓ Branch 0 taken 21 times.
✓ Branch 1 taken 4 times.
|
25 | if (status == SPACEWASM_OK) { |
| 566 |
4/5✓ Branch 0 taken 12 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 3 times.
✗ Branch 4 not taken.
|
21 | switch (g_value.tag) { |
| 567 | 12 | case SPACEWASM_I32: | |
| 568 |
1/1✓ Branch 11 taken 12 times.
|
12 | this->log_ACTIVITY_LO_GlobalValueI32(moduleName, name, g_value.u.i32_); |
| 569 | 12 | break; | |
| 570 | 3 | case SPACEWASM_I64: | |
| 571 |
1/1✓ Branch 11 taken 3 times.
|
3 | this->log_ACTIVITY_LO_GlobalValueI64(moduleName, name, g_value.u.i64_); |
| 572 | 3 | break; | |
| 573 | 3 | case SPACEWASM_F32: | |
| 574 |
1/1✓ Branch 11 taken 3 times.
|
3 | this->log_ACTIVITY_LO_GlobalValueF32(moduleName, name, g_value.u.f32_); |
| 575 | 3 | break; | |
| 576 | 3 | case SPACEWASM_F64: | |
| 577 |
1/1✓ Branch 11 taken 3 times.
|
3 | this->log_ACTIVITY_LO_GlobalValueF64(moduleName, name, g_value.u.f64_); |
| 578 | 3 | break; | |
| 579 | ✗ | default: | |
| 580 | ✗ | FW_ASSERT(false, g_value.tag); | |
| 581 | } | ||
| 582 | |||
| 583 |
2/2✓ Branch 6 taken 21 times.
✓ Branch 9 taken 21 times.
|
21 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::OK); |
| 584 | } else { | ||
| 585 |
2/2✓ Branch 6 taken 4 times.
✓ Branch 16 taken 4 times.
|
4 | this->log_WARNING_LO_GlobalGetFailed(moduleName, name, status); |
| 586 |
2/2✓ Branch 6 taken 4 times.
✓ Branch 9 taken 4 times.
|
4 | this->cmdResponse_out(opCode, cmdSeq, Fw::CmdResponse::EXECUTION_ERROR); |
| 587 | } | ||
| 588 | 25 | } | |
| 589 | |||
| 590 | } // namespace Svc | ||
| 591 |