blob: ac074e3a39eb3211d952f5b530cbe572af2475d8 [file] [log] [blame]
Vernon Mauerya3702c12019-05-22 13:20:59 -07001/*
2// Copyright (c) 2018 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16
Yong Lic3580e92019-08-15 14:36:47 +080017#include <bitset>
Vernon Mauerya3702c12019-05-22 13:20:59 -070018#include <bridgingcommands.hpp>
19#include <cstring>
Vernon Mauery15419dd2019-05-24 09:40:30 -070020#include <ipmid/api.hpp>
Yong Lic3580e92019-08-15 14:36:47 +080021#include <ipmid/utils.hpp>
Vernon Mauerya3702c12019-05-22 13:20:59 -070022#include <phosphor-logging/log.hpp>
23#include <sdbusplus/bus.hpp>
24#include <sdbusplus/bus/match.hpp>
25#include <sdbusplus/message.hpp>
26#include <vector>
27
Yong Lic3580e92019-08-15 14:36:47 +080028static constexpr const char *wdtService = "xyz.openbmc_project.Watchdog";
29static constexpr const char *wdtInterface =
30 "xyz.openbmc_project.State.Watchdog";
31static constexpr const char *wdtObjPath = "/xyz/openbmc_project/watchdog/host0";
32static constexpr const char *wdtInterruptFlagProp =
33 "PreTimeoutInterruptOccurFlag";
34
Vernon Mauerya3702c12019-05-22 13:20:59 -070035static constexpr const char *ipmbBus = "xyz.openbmc_project.Ipmi.Channel.Ipmb";
36static constexpr const char *ipmbObj = "/xyz/openbmc_project/Ipmi/Channel/Ipmb";
37static constexpr const char *ipmbIntf = "org.openbmc.Ipmb";
38
39static Bridging bridging;
40
jayaprakash Mutyala405f54a2019-10-18 18:23:27 +000041void Bridging::clearResponseQueue()
42{
43 responseQueue.clear();
44}
45
Vernon Mauerya3702c12019-05-22 13:20:59 -070046/**
47 * @brief utils for checksum
48 */
49static bool ipmbChecksumValidate(uint8_t *data, uint8_t length)
50{
51 if (data == nullptr)
52 {
53 return false;
54 }
55
56 uint8_t checksum = 0;
57
58 for (uint8_t idx = 0; idx < length; idx++)
59 {
60 checksum += data[idx];
61 }
62
63 if (0 == checksum)
64 {
65 return true;
66 }
67
68 return false;
69}
70
71static uint8_t ipmbChecksumCompute(uint8_t *data, uint8_t length)
72{
73 if (data == nullptr)
74 {
75 return 0;
76 }
77
78 uint8_t checksum = 0;
79
80 for (uint8_t idx = 0; idx < length; idx++)
81 {
82 checksum += data[idx];
83 }
84
85 checksum = (~checksum) + 1;
86 return checksum;
87}
88
89static inline bool ipmbConnectionHeaderChecksumValidate(ipmbHeader *ipmbHeader)
90{
91 return ipmbChecksumValidate(reinterpret_cast<uint8_t *>(ipmbHeader),
92 ipmbConnectionHeaderLength);
93}
94
95static inline bool ipmbDataChecksumValidate(ipmbHeader *ipmbHeader,
96 uint8_t length)
97{
98 return ipmbChecksumValidate(
99 (reinterpret_cast<uint8_t *>(ipmbHeader) + ipmbConnectionHeaderLength),
100 (length - ipmbConnectionHeaderLength));
101}
102
103static bool isFrameValid(ipmbHeader *frame, uint8_t length)
104{
105 if ((length < ipmbMinFrameLength) || (length > ipmbMaxFrameLength))
106 {
107 return false;
108 }
109
110 if (false == ipmbConnectionHeaderChecksumValidate(frame))
111 {
112 return false;
113 }
114
115 if (false == ipmbDataChecksumValidate(frame, length))
116 {
117 return false;
118 }
119
120 return true;
121}
122
123IpmbRequest::IpmbRequest(const ipmbHeader *ipmbBuffer, size_t bufferLength)
124{
125 address = ipmbBuffer->Header.Req.address;
126 netFn = ipmbNetFnGet(ipmbBuffer->Header.Req.rsNetFnLUN);
127 rsLun = ipmbLunFromNetFnLunGet(ipmbBuffer->Header.Req.rsNetFnLUN);
128 rqSA = ipmbBuffer->Header.Req.rqSA;
129 seq = ipmbSeqGet(ipmbBuffer->Header.Req.rqSeqLUN);
130 rqLun = ipmbLunFromSeqLunGet(ipmbBuffer->Header.Req.rqSeqLUN);
131 cmd = ipmbBuffer->Header.Req.cmd;
132
133 size_t dataLength =
134 bufferLength - (ipmbConnectionHeaderLength +
135 ipmbRequestDataHeaderLength + ipmbChecksumSize);
136
137 if (dataLength > 0)
138 {
139 data.insert(data.end(), ipmbBuffer->Header.Req.data,
140 &ipmbBuffer->Header.Req.data[dataLength]);
141 }
142}
143
144IpmbResponse::IpmbResponse(uint8_t address, uint8_t netFn, uint8_t rqLun,
145 uint8_t rsSA, uint8_t seq, uint8_t rsLun,
146 uint8_t cmd, uint8_t completionCode,
147 std::vector<uint8_t> &inputData) :
148 address(address),
149 netFn(netFn), rqLun(rqLun), rsSA(rsSA), seq(seq), rsLun(rsLun), cmd(cmd),
150 completionCode(completionCode)
151{
152 data.reserve(ipmbMaxDataSize);
153
154 if (inputData.size() > 0)
155 {
156 data = std::move(inputData);
157 }
158}
159
160void IpmbResponse::ipmbToi2cConstruct(uint8_t *buffer, size_t *bufferLength)
161{
162 ipmbHeader *ipmbBuffer = (ipmbHeader *)buffer;
163
164 ipmbBuffer->Header.Resp.address = address;
165 ipmbBuffer->Header.Resp.rqNetFnLUN = ipmbNetFnLunSet(netFn, rqLun);
166 ipmbBuffer->Header.Resp.rsSA = rsSA;
167 ipmbBuffer->Header.Resp.rsSeqLUN = ipmbSeqLunSet(seq, rsLun);
168 ipmbBuffer->Header.Resp.cmd = cmd;
169 ipmbBuffer->Header.Resp.completionCode = completionCode;
170
171 ipmbBuffer->Header.Resp.checksum1 = ipmbChecksumCompute(
172 buffer, ipmbConnectionHeaderLength - ipmbChecksumSize);
173
174 if (data.size() > 0)
175 {
176 std::copy(
177 data.begin(), data.end(),
178 &buffer[ipmbConnectionHeaderLength + ipmbResponseDataHeaderLength]);
179 }
180
181 *bufferLength = data.size() + ipmbResponseDataHeaderLength +
182 ipmbConnectionHeaderLength + ipmbChecksumSize;
183
184 buffer[*bufferLength - ipmbChecksumSize] =
185 ipmbChecksumCompute(&buffer[ipmbChecksum2StartOffset],
186 (ipmbResponseDataHeaderLength + data.size()));
187}
188
189void IpmbRequest::prepareRequest(sdbusplus::message::message &mesg)
190{
191 mesg.append(ipmbMeChannelNum, netFn, rqLun, cmd, data);
192}
193
Richard Marian Thomaiyare646a252019-11-20 22:54:03 +0530194static constexpr unsigned int makeCmdKey(unsigned int netFn, unsigned int cmd)
195{
196 return (netFn << 8) | cmd;
197}
198
199static constexpr bool isMeCmdAllowed(uint8_t netFn, uint8_t cmd)
200{
201 constexpr uint8_t netFnMeOEM = 0x2E;
202 constexpr uint8_t cmdMeOemSendRawPeci = 0x40;
203 constexpr uint8_t cmdMeOemAggSendRawPeci = 0x41;
204 constexpr uint8_t cmdMeOemCpuPkgConfWrite = 0x43;
205 constexpr uint8_t cmdMeOemCpuPciConfWrite = 0x45;
206 constexpr uint8_t cmdMeOemReadMemSmbus = 0x47;
207 constexpr uint8_t cmdMeOemWriteMemSmbus = 0x48;
208 constexpr uint8_t cmdMeOemSlotIpmb = 0x51;
209 constexpr uint8_t cmdMeOemSlotI2cMasterWriteRead = 0x52;
210 constexpr uint8_t cmdMeOemSendRawPmbus = 0xD9;
211 constexpr uint8_t cmdMeOemUnlockMeRegion = 0xE7;
212 constexpr uint8_t cmdMeOemAggSendRawPmbus = 0xEC;
213
214 switch (makeCmdKey(netFn, cmd))
215 {
216 // Restrict ME Master write command
217 case makeCmdKey(ipmi::netFnApp, ipmi::app::cmdMasterWriteRead):
218 // Restrict ME OEM commands
219 case makeCmdKey(netFnMeOEM, cmdMeOemSendRawPeci):
220 case makeCmdKey(netFnMeOEM, cmdMeOemAggSendRawPeci):
221 case makeCmdKey(netFnMeOEM, cmdMeOemCpuPkgConfWrite):
222 case makeCmdKey(netFnMeOEM, cmdMeOemCpuPciConfWrite):
223 case makeCmdKey(netFnMeOEM, cmdMeOemReadMemSmbus):
224 case makeCmdKey(netFnMeOEM, cmdMeOemWriteMemSmbus):
225 case makeCmdKey(netFnMeOEM, cmdMeOemSlotIpmb):
226 case makeCmdKey(netFnMeOEM, cmdMeOemSlotI2cMasterWriteRead):
227 case makeCmdKey(netFnMeOEM, cmdMeOemSendRawPmbus):
228 case makeCmdKey(netFnMeOEM, cmdMeOemUnlockMeRegion):
229 case makeCmdKey(netFnMeOEM, cmdMeOemAggSendRawPmbus):
230 return false;
231 default:
232 return true;
233 }
234}
235
Vernon Mauerya3702c12019-05-22 13:20:59 -0700236ipmi_return_codes Bridging::handleIpmbChannel(sSendMessageReq *sendMsgReq,
237 ipmi_response_t response,
238 ipmi_data_len_t dataLen)
239{
240 if ((*dataLen < (sizeof(sSendMessageReq) + ipmbMinFrameLength)) ||
241 (*dataLen > (sizeof(sSendMessageReq) + ipmbMaxFrameLength)))
242 {
243 *dataLen = 0;
244 return IPMI_CC_REQ_DATA_LEN_INVALID;
245 }
246
247 auto sendMsgReqData = reinterpret_cast<ipmbHeader *>(sendMsgReq->data);
248
249 // TODO: check privilege lvl. Bridging to ME requires Administrator lvl
250
251 // allow bridging to ME only
252 if (sendMsgReqData->Header.Req.address != ipmbMeSlaveAddress)
253 {
254 phosphor::logging::log<phosphor::logging::level::INFO>(
255 "handleIpmbChannel, IPMB address invalid");
256 *dataLen = 0;
257 return IPMI_CC_PARM_OUT_OF_RANGE;
258 }
259
Richard Marian Thomaiyare646a252019-11-20 22:54:03 +0530260 constexpr uint8_t shiftLUN = 2;
261 if (!isMeCmdAllowed((sendMsgReqData->Header.Req.rsNetFnLUN >> shiftLUN),
262 sendMsgReqData->Header.Req.cmd))
263 {
264 return IPMI_CC_INVALID_FIELD_REQUEST;
265 }
266
Vernon Mauerya3702c12019-05-22 13:20:59 -0700267 // check allowed modes
268 if (sendMsgReq->modeGet() != modeNoTracking &&
269 sendMsgReq->modeGet() != modeTrackRequest)
270 {
271 phosphor::logging::log<phosphor::logging::level::INFO>(
272 "handleIpmbChannel, mode not supported");
273 *dataLen = 0;
274 return IPMI_CC_PARM_OUT_OF_RANGE;
275 }
276
277 // check if request contains valid IPMB frame
278 if (!isFrameValid(sendMsgReqData, (*dataLen - sizeof(sSendMessageReq))))
279 {
280 phosphor::logging::log<phosphor::logging::level::INFO>(
281 "handleIpmbChannel, IPMB frame invalid");
282 *dataLen = 0;
283 return IPMI_CC_PARM_OUT_OF_RANGE;
284 }
285
286 auto ipmbRequest =
287 IpmbRequest(sendMsgReqData, (*dataLen - sizeof(sSendMessageReq)));
288
289 std::tuple<int, uint8_t, uint8_t, uint8_t, uint8_t, std::vector<uint8_t>>
290 ipmbResponse;
291
292 // send request to IPMB
293 try
294 {
Vernon Mauery15419dd2019-05-24 09:40:30 -0700295 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
Vernon Mauerya3702c12019-05-22 13:20:59 -0700296 auto mesg =
Vernon Mauery15419dd2019-05-24 09:40:30 -0700297 dbus->new_method_call(ipmbBus, ipmbObj, ipmbIntf, "sendRequest");
Vernon Mauerya3702c12019-05-22 13:20:59 -0700298 ipmbRequest.prepareRequest(mesg);
Vernon Mauery15419dd2019-05-24 09:40:30 -0700299 auto ret = dbus->call(mesg);
Vernon Mauerya3702c12019-05-22 13:20:59 -0700300 ret.read(ipmbResponse);
301 }
302 catch (sdbusplus::exception::SdBusError &e)
303 {
304 phosphor::logging::log<phosphor::logging::level::ERR>(
305 "handleIpmbChannel, dbus call exception");
306 *dataLen = 0;
307 return IPMI_CC_UNSPECIFIED_ERROR;
308 }
309
310 std::vector<uint8_t> dataReceived(0);
311 int status = -1;
312 uint8_t netFn = 0, lun = 0, cmd = 0, cc = 0;
313
314 std::tie(status, netFn, lun, cmd, cc, dataReceived) = ipmbResponse;
315
316 auto respReceived =
317 IpmbResponse(ipmbRequest.rqSA, netFn, lun, ipmbRequest.address,
318 ipmbRequest.seq, lun, cmd, cc, dataReceived);
319
320 // check IPMB layer status
321 if (status)
322 {
323 phosphor::logging::log<phosphor::logging::level::WARNING>(
324 "handleIpmbChannel, ipmb returned non zero status");
325 *dataLen = 0;
326 return IPMI_CC_RESPONSE_ERROR;
327 }
328
329 auto sendMsgRes = reinterpret_cast<uint8_t *>(response);
330
331 switch (sendMsgReq->modeGet())
332 {
333 case modeNoTracking:
334 if (responseQueue.size() == responseQueueMaxSize)
335 {
336 *dataLen = 0;
337 return IPMI_CC_BUSY;
338 }
339 responseQueue.insert(responseQueue.end(), std::move(respReceived));
340 *dataLen = 0;
341 return IPMI_CC_OK;
342
343 break;
344 case modeTrackRequest:
345 respReceived.ipmbToi2cConstruct(sendMsgRes, dataLen);
346 return IPMI_CC_OK;
347
348 break;
349 default:
350 phosphor::logging::log<phosphor::logging::level::INFO>(
351 "handleIpmbChannel, mode not supported");
352 *dataLen = 0;
353 return IPMI_CC_PARM_OUT_OF_RANGE;
354 }
355
356 *dataLen = 0;
357 return IPMI_CC_UNSPECIFIED_ERROR;
358}
359
360ipmi_return_codes Bridging::sendMessageHandler(ipmi_request_t request,
361 ipmi_response_t response,
362 ipmi_data_len_t dataLen)
363{
364 ipmi_return_codes retCode = IPMI_CC_OK;
365
366 if (*dataLen < sizeof(sSendMessageReq))
367 {
368 *dataLen = 0;
369 return IPMI_CC_REQ_DATA_LEN_INVALID;
370 }
371
372 auto sendMsgReq = reinterpret_cast<sSendMessageReq *>(request);
373
374 // check message fields:
375 // encryption not supported
376 if (sendMsgReq->encryptionGet() != 0)
377 {
378 phosphor::logging::log<phosphor::logging::level::INFO>(
379 "sendMessageHandler, encryption not supported");
380 *dataLen = 0;
381 return IPMI_CC_PARM_OUT_OF_RANGE;
382 }
383
384 // authentication not supported
385 if (sendMsgReq->authenticationGet() != 0)
386 {
387 phosphor::logging::log<phosphor::logging::level::INFO>(
388 "sendMessageHandler, authentication not supported");
389 *dataLen = 0;
390 return IPMI_CC_PARM_OUT_OF_RANGE;
391 }
392
393 switch (sendMsgReq->channelNumGet())
394 {
395 // we only handle ipmb for now
396 case targetChannelIpmb:
397 case targetChannelOtherLan:
398 retCode = handleIpmbChannel(sendMsgReq, response, dataLen);
399 break;
400 // fall through to default
401 case targetChannelIcmb10:
402 case targetChannelIcmb09:
403 case targetChannelLan:
404 case targetChannelSerialModem:
405 case targetChannelPciSmbus:
406 case targetChannelSmbus10:
407 case targetChannelSmbus20:
408 case targetChannelSystemInterface:
409 default:
410 phosphor::logging::log<phosphor::logging::level::INFO>(
411 "sendMessageHandler, TargetChannel invalid");
412 *dataLen = 0;
413 return IPMI_CC_PARM_OUT_OF_RANGE;
414 }
415
416 return retCode;
417}
418
419ipmi_return_codes Bridging::getMessageHandler(ipmi_request_t request,
420 ipmi_response_t response,
421 ipmi_data_len_t dataLen)
422{
423 if (*dataLen != 0)
424 {
425 *dataLen = 0;
426 return IPMI_CC_REQ_DATA_LEN_INVALID;
427 }
428
429 auto getMsgRes = reinterpret_cast<sGetMessageRes *>(response);
430 auto getMsgResData = static_cast<uint8_t *>(getMsgRes->data);
431
432 std::memset(getMsgRes, 0, sizeof(sGetMessageRes));
433
434 auto respQueueItem = responseQueue.begin();
435
436 if (respQueueItem == responseQueue.end())
437 {
438 phosphor::logging::log<phosphor::logging::level::INFO>(
439 "getMessageHandler, no data available");
440 *dataLen = 0;
441 return ipmiGetMessageCmdDataNotAvailable;
442 }
443
444 // set message fields
445 getMsgRes->privilegeLvlSet(SYSTEM_INTERFACE);
446 getMsgRes->channelNumSet(targetChannelSystemInterface);
447
448 // construct response
449 respQueueItem->ipmbToi2cConstruct(getMsgResData, dataLen);
450 responseQueue.erase(respQueueItem);
451
452 *dataLen = *dataLen + sizeof(sGetMessageRes);
453 return IPMI_CC_OK;
454}
455
Vernon Mauerya3702c12019-05-22 13:20:59 -0700456ipmi_ret_t ipmiAppSendMessage(ipmi_netfn_t netFn, ipmi_cmd_t cmd,
457 ipmi_request_t request, ipmi_response_t response,
458 ipmi_data_len_t dataLen, ipmi_context_t context)
459{
460 ipmi_ret_t retCode = IPMI_CC_OK;
461 retCode = bridging.sendMessageHandler(request, response, dataLen);
462
463 return retCode;
464}
465
466ipmi_ret_t ipmiAppGetMessage(ipmi_netfn_t netFn, ipmi_cmd_t cmd,
467 ipmi_request_t request, ipmi_response_t response,
468 ipmi_data_len_t dataLen, ipmi_context_t context)
469{
470 ipmi_ret_t retCode = IPMI_CC_OK;
471 retCode = bridging.getMessageHandler(request, response, dataLen);
472
473 return retCode;
474}
475
Yong Lic3580e92019-08-15 14:36:47 +0800476std::size_t Bridging::getResponseQueueSize()
Vernon Mauerya3702c12019-05-22 13:20:59 -0700477{
Yong Lic3580e92019-08-15 14:36:47 +0800478 return responseQueue.size();
479}
Vernon Mauerya3702c12019-05-22 13:20:59 -0700480
Yong Lic3580e92019-08-15 14:36:47 +0800481/**
482@brief This command is used to retrive present message available states.
483
484@return IPMI completion code plus Flags as response data on success.
485**/
486ipmi::RspType<std::bitset<8>> ipmiAppGetMessageFlags()
487{
488 std::bitset<8> getMsgFlagsRes;
489
490 getMsgFlagsRes.set(getMsgFlagEventMessageBit);
491
492 // set message fields
493 if (bridging.getResponseQueueSize() > 0)
494 {
495 getMsgFlagsRes.set(getMsgFlagReceiveMessageBit);
496 }
497 else
498 {
499 getMsgFlagsRes.reset(getMsgFlagReceiveMessageBit);
500 }
501
502 try
503 {
504 std::shared_ptr<sdbusplus::asio::connection> dbus = getSdBus();
505 ipmi::Value variant = ipmi::getDbusProperty(
506 *dbus, wdtService, wdtObjPath, wdtInterface, wdtInterruptFlagProp);
507 if (std::get<bool>(variant))
508 {
509 getMsgFlagsRes.set(getMsgFlagWatchdogPreTimeOutBit);
510 }
511 }
512 catch (sdbusplus::exception::SdBusError &e)
513 {
514 phosphor::logging::log<phosphor::logging::level::ERR>(
515 "ipmiAppGetMessageFlags, dbus call exception");
516 return ipmi::responseUnspecifiedError();
517 }
518
519 return ipmi::responseSuccess(getMsgFlagsRes);
Vernon Mauerya3702c12019-05-22 13:20:59 -0700520}
521
jayaprakash Mutyala405f54a2019-10-18 18:23:27 +0000522/** @brief This command is used to flush unread data from the receive
523 * message queue
524 * @param receiveMessage - clear receive message queue
525 * @param eventMsgBufFull - clear event message buffer full
526 * @param reserved2 - reserved bit
527 * @param watchdogTimeout - clear watchdog pre-timeout interrupt flag
528 * @param reserved1 - reserved bit
529 * @param oem0 - clear OEM 0 data
530 * @param oem1 - clear OEM 1 data
531 * @param oem2 - clear OEM 2 data
532
533 * @return IPMI completion code on success
534 */
535ipmi::RspType<> ipmiAppClearMessageFlags(bool receiveMessage,
536 bool eventMsgBufFull, bool reserved2,
537 bool watchdogTimeout, bool reserved1,
538 bool oem0, bool oem1, bool oem2)
Vernon Mauerya3702c12019-05-22 13:20:59 -0700539{
jayaprakash Mutyala405f54a2019-10-18 18:23:27 +0000540 if (reserved1 || reserved2)
541 {
542 return ipmi::responseInvalidFieldRequest();
543 }
Vernon Mauerya3702c12019-05-22 13:20:59 -0700544
jayaprakash Mutyala405f54a2019-10-18 18:23:27 +0000545 if (receiveMessage)
546 {
547 bridging.clearResponseQueue();
548 }
549 return ipmi::responseSuccess();
Vernon Mauerya3702c12019-05-22 13:20:59 -0700550}
551
552static void register_bridging_functions() __attribute__((constructor));
553static void register_bridging_functions()
554{
jayaprakash Mutyala405f54a2019-10-18 18:23:27 +0000555 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnApp,
556 ipmi::app::cmdClearMessageFlags,
557 ipmi::Privilege::User, ipmiAppClearMessageFlags);
Vernon Mauerya3702c12019-05-22 13:20:59 -0700558
Yong Lic3580e92019-08-15 14:36:47 +0800559 ipmi::registerHandler(ipmi::prioOemBase, ipmi::netFnApp,
560 ipmi::app::cmdGetMessageFlags, ipmi::Privilege::User,
561 ipmiAppGetMessageFlags);
Vernon Mauerya3702c12019-05-22 13:20:59 -0700562
563 ipmi_register_callback(NETFUN_APP,
564 Bridging::IpmiAppBridgingCmds::ipmiCmdGetMessage,
565 NULL, ipmiAppGetMessage, PRIVILEGE_USER);
566
567 ipmi_register_callback(NETFUN_APP,
568 Bridging::IpmiAppBridgingCmds::ipmiCmdSendMessage,
569 NULL, ipmiAppSendMessage, PRIVILEGE_USER);
570
571 return;
572}