2017-02-06 14:09:08 +01:00
|
|
|
#
|
|
|
|
/*
|
2017-03-23 18:32:25 +01:00
|
|
|
* Copyright (C) 2015 .. 2017
|
2017-02-06 14:09:08 +01:00
|
|
|
* Jan van Katwijk (J.vanKatwijk@gmail.com)
|
|
|
|
* Lazy Chair Computing
|
|
|
|
*
|
2017-03-23 18:32:25 +01:00
|
|
|
* This file is part of the Qt-DAB
|
|
|
|
* Qt-DAB is free software; you can redistribute it and/or modify
|
2017-02-06 14:09:08 +01:00
|
|
|
* 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.
|
|
|
|
*
|
2017-03-23 18:32:25 +01:00
|
|
|
* Qt-DAB is distributed in the hope that it will be useful,
|
2017-02-06 14:09:08 +01:00
|
|
|
* 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
|
2017-03-28 18:40:11 +02:00
|
|
|
* along with Qt-DAB; if not, write to the Free Software
|
2017-02-06 14:09:08 +01:00
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
*/
|
|
|
|
#include "pad-handler.h"
|
|
|
|
#include <cstring>
|
|
|
|
#include "radio.h"
|
|
|
|
#include "charsets.h"
|
|
|
|
#include "mot-data.h"
|
|
|
|
/**
|
|
|
|
* \class padHandler
|
2017-03-27 16:32:49 +02:00
|
|
|
* Handles the pad segments passed on from mp2- and mp4Processor
|
2017-02-06 14:09:08 +01:00
|
|
|
*/
|
2017-05-31 17:46:42 +02:00
|
|
|
padHandler::padHandler (RadioInterface *mr, QString picturesPath) {
|
2017-02-06 14:09:08 +01:00
|
|
|
myRadioInterface = mr;
|
|
|
|
connect (this, SIGNAL (showLabel (QString)),
|
|
|
|
mr, SLOT (showLabel (QString)));
|
2017-03-27 16:32:49 +02:00
|
|
|
connect (this, SIGNAL (show_motHandling (bool)),
|
|
|
|
mr, SLOT (show_motHandling (bool)));
|
2017-05-31 17:46:42 +02:00
|
|
|
my_motHandler = new motHandler (mr, picturesPath);
|
2017-03-27 16:32:49 +02:00
|
|
|
//
|
|
|
|
// mscGroupElement indicates whether we are handling an
|
|
|
|
// msc datagroup or not.
|
|
|
|
mscGroupElement = false;
|
|
|
|
// xpadLength tells - if mscGroupElement is "on" - the size of the
|
|
|
|
// xpadfields, needed for handling xpads without CI's
|
|
|
|
xpadLength = -1;
|
2017-02-06 14:09:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
padHandler::~padHandler (void) {
|
|
|
|
delete my_motHandler;
|
|
|
|
}
|
|
|
|
|
2017-03-27 16:32:49 +02:00
|
|
|
// Data is stored reverse, we pass the vector and the index of the
|
|
|
|
// last element of the XPad data.
|
|
|
|
// L0 is the "top" byte of the L field, L1 the next to top one.
|
|
|
|
void padHandler::processPAD (uint8_t *buffer, int16_t last,
|
|
|
|
uint8_t L1, uint8_t L0) {
|
|
|
|
uint8_t fpadType = (L1 >> 6) & 03;
|
|
|
|
|
2017-02-06 14:09:08 +01:00
|
|
|
if (fpadType != 00)
|
|
|
|
return;
|
|
|
|
//
|
|
|
|
// OK, we'll try
|
2017-03-27 16:32:49 +02:00
|
|
|
|
2017-08-24 19:30:52 +02:00
|
|
|
uint8_t x_padInd = (L1 >> 4) & 03;
|
|
|
|
uint8_t CI_flag = L0 & 02;
|
|
|
|
|
2017-02-06 14:09:08 +01:00
|
|
|
switch (x_padInd) {
|
2017-03-27 16:32:49 +02:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
|
2017-02-06 14:09:08 +01:00
|
|
|
case 01 :
|
2017-08-25 08:51:16 +02:00
|
|
|
handle_shortPAD (buffer, last, CI_flag);
|
2017-02-06 14:09:08 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 02:
|
2017-03-27 16:32:49 +02:00
|
|
|
handle_variablePAD (buffer, last, CI_flag);
|
2017-02-06 14:09:08 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-08-24 19:30:52 +02:00
|
|
|
int16_t still_to_go = 0;
|
2017-03-27 16:32:49 +02:00
|
|
|
// Since the data is stored in reversed order, we pass
|
|
|
|
// on the vector address and the offset of the last element
|
2017-03-30 18:54:45 +02:00
|
|
|
// in that vector
|
2017-08-24 19:30:52 +02:00
|
|
|
void padHandler::handle_shortPAD (uint8_t *b, int16_t last, uint8_t CIf) {
|
|
|
|
uint8_t data [5];
|
2017-02-06 14:09:08 +01:00
|
|
|
int16_t i;
|
|
|
|
|
2017-08-24 19:30:52 +02:00
|
|
|
if (CIf != 0) { // has a CI flag
|
|
|
|
uint8_t CI = b [last];
|
|
|
|
uint8_t AcTy = CI & 037; // application type
|
|
|
|
switch (AcTy) {
|
|
|
|
default:
|
|
|
|
fprintf (stderr, "AcTy: %d\n", AcTy);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0: // end marker
|
|
|
|
if ((still_to_go <= 0) && (dynamicLabelText. length () > 0)) {
|
|
|
|
showLabel (dynamicLabelText);
|
|
|
|
dynamicLabelText. clear ();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 2: // start of fragment, extract the length
|
2017-08-25 08:51:16 +02:00
|
|
|
if ((b [last - 1] & 0xF0) == 0x40) {
|
|
|
|
if (dynamicLabelText. length () > 0) {
|
|
|
|
showLabel (dynamicLabelText);
|
|
|
|
dynamicLabelText. clear ();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ((b [last - 1] & 0xF0) == 0x20) {
|
|
|
|
// start of a new message
|
|
|
|
}
|
2017-08-24 19:30:52 +02:00
|
|
|
still_to_go = b [last - 1] & 0x0F;
|
|
|
|
dynamicLabelText. append (QChar(b [last - 3]));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else { // No CI flag
|
|
|
|
uint8_t len = 0;
|
|
|
|
data [4] = 0;
|
|
|
|
if (still_to_go > 0) {
|
|
|
|
// X-PAD field is all data
|
|
|
|
for (i = 0; (i < 4) && (still_to_go > 0); i ++) {
|
|
|
|
data [i] = b [last - i] & 0x7F;
|
|
|
|
still_to_go --;
|
|
|
|
}
|
|
|
|
for (; i < 4; i ++)
|
|
|
|
data [i] = 0;
|
|
|
|
|
|
|
|
dynamicLabelText. append ((char *)data);
|
|
|
|
}
|
|
|
|
}
|
2017-02-06 14:09:08 +01:00
|
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// Here we end up when F_PAD type = 00 and X-PAD Ind = 02
|
|
|
|
static
|
|
|
|
int16_t lengthTable [] = {4, 6, 8, 12, 16, 24, 32, 48};
|
|
|
|
|
2017-03-27 16:32:49 +02:00
|
|
|
//
|
2017-03-30 18:54:45 +02:00
|
|
|
// dataGroupLength is set when having processed an appType 1
|
2017-03-27 16:32:49 +02:00
|
|
|
static int dataGroupLength = 0;
|
|
|
|
//
|
2017-03-30 18:54:45 +02:00
|
|
|
// msc_dataGroupLength is used while assembling an msc_data group,
|
|
|
|
// in the end it should be equal or somewhat larger than dataGroupLength
|
2017-03-27 16:32:49 +02:00
|
|
|
static int msc_dataGroupLength;
|
|
|
|
|
|
|
|
//
|
|
|
|
// The msc_dataGroupBuffer is - as the name suggests - used for
|
|
|
|
// assembling the msc_data group.
|
|
|
|
static
|
|
|
|
QByteArray msc_dataGroupBuffer;
|
|
|
|
|
|
|
|
// Since the data is reversed, we pass on the vector address
|
|
|
|
// and the offset of the last element in the vector,
|
2017-03-30 18:54:45 +02:00
|
|
|
// i.e. we start (downwards) beginning at b [last];
|
2017-02-06 14:09:08 +01:00
|
|
|
void padHandler::handle_variablePAD (uint8_t *b,
|
2017-03-27 16:32:49 +02:00
|
|
|
int16_t last, uint8_t CI_flag) {
|
|
|
|
int16_t CI_Index = 0;
|
|
|
|
uint8_t CI_table [4];
|
2017-02-06 14:09:08 +01:00
|
|
|
int16_t i, j;
|
2017-03-27 16:32:49 +02:00
|
|
|
int16_t base = last;
|
|
|
|
QByteArray data; // for the local addition
|
2017-02-06 14:09:08 +01:00
|
|
|
|
2017-03-27 16:32:49 +02:00
|
|
|
// If an xpadfield shows with a CI_flag == 0, and if we are
|
|
|
|
// dealing with an msc field, the size to be taken is
|
|
|
|
// the size of the latest xpadfield that had a CI_flag != 0
|
|
|
|
if (CI_flag == 0) {
|
|
|
|
if (mscGroupElement && (xpadLength > 0)) {
|
|
|
|
data. resize (xpadLength);
|
|
|
|
for (j = 0; j < xpadLength; j ++)
|
|
|
|
data [j] = b [last - j];
|
|
|
|
add_MSC_element (data);
|
|
|
|
}
|
2017-02-06 14:09:08 +01:00
|
|
|
return;
|
2017-03-27 16:32:49 +02:00
|
|
|
}
|
2017-02-06 14:09:08 +01:00
|
|
|
//
|
|
|
|
// The CI flag in the F_PAD data is set, so we have local CI's
|
|
|
|
// 7.4.2.2: Contents indicators are one byte long
|
|
|
|
|
2017-03-27 16:32:49 +02:00
|
|
|
while (((b [base] & 037) != 0) && (CI_Index < 4))
|
|
|
|
CI_table [CI_Index ++] = b [base --];
|
2017-02-06 14:09:08 +01:00
|
|
|
|
2017-03-27 16:32:49 +02:00
|
|
|
if (CI_Index < 4) // we have a "0" indicator, adjust base
|
2017-02-06 14:09:08 +01:00
|
|
|
base -= 1;
|
2017-03-27 16:32:49 +02:00
|
|
|
|
|
|
|
// The space for the CI's does belong to the Cpadfield, so
|
|
|
|
// but do not forget to take into account the '0'field if CI_Index < 4
|
|
|
|
if (mscGroupElement) {
|
|
|
|
xpadLength = 0;
|
|
|
|
for (i = 0; i < CI_Index; i ++)
|
|
|
|
xpadLength += lengthTable [CI_table [i] >> 5];
|
|
|
|
xpadLength += CI_Index == 4 ? 4 : CI_Index + 1;
|
|
|
|
}
|
2017-02-06 14:09:08 +01:00
|
|
|
//
|
2017-03-27 16:32:49 +02:00
|
|
|
// Handle the contents
|
|
|
|
for (i = 0; i < CI_Index; i ++) {
|
2017-02-06 14:09:08 +01:00
|
|
|
uint8_t appType = CI_table [i] & 037;
|
|
|
|
int16_t length = lengthTable [CI_table [i] >> 5];
|
|
|
|
if (appType == 1) {
|
2017-03-27 16:32:49 +02:00
|
|
|
dataGroupLength = ((b [base] & 077) << 8) | b [base - 1];
|
2017-02-06 14:09:08 +01:00
|
|
|
base -= 4;
|
|
|
|
last_appType = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-03-27 16:32:49 +02:00
|
|
|
// collect data, reverse the reversed bytes
|
|
|
|
data. resize (length);
|
2017-02-06 14:09:08 +01:00
|
|
|
for (j = 0; j < length; j ++)
|
|
|
|
data [j] = b [base - j];
|
|
|
|
|
|
|
|
switch (appType) {
|
|
|
|
default:
|
|
|
|
return; // sorry, we do not handle this
|
|
|
|
|
|
|
|
case 2:
|
|
|
|
case 3:
|
2017-03-27 16:32:49 +02:00
|
|
|
dynamicLabel ((uint8_t *)(data. data ()),
|
|
|
|
data. length (), CI_table [i]);
|
2017-02-06 14:09:08 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 12:
|
2017-03-27 16:32:49 +02:00
|
|
|
new_MSC_element (data, dataGroupLength);
|
2017-02-06 14:09:08 +01:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 13:
|
2017-03-27 16:32:49 +02:00
|
|
|
add_MSC_element (data);
|
2017-02-06 14:09:08 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
last_appType = appType;
|
|
|
|
base -= length;
|
2017-03-27 16:32:49 +02:00
|
|
|
if (base < 0 && i < CI_Index - 1) {
|
2017-02-06 14:09:08 +01:00
|
|
|
fprintf (stderr, "Hier gaat het fout, base = %d\n", base);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
|
|
// A dynamic label is created from a sequence of (dynamic) xpad
|
|
|
|
// fields, starting with CI = 2, continuing with CI = 3
|
|
|
|
void padHandler::dynamicLabel (uint8_t *data, int16_t length, uint8_t CI) {
|
|
|
|
static int16_t segmentno = 0;
|
|
|
|
static int16_t remainDataLength = 0;
|
|
|
|
static bool isLastSegment = false;
|
|
|
|
static bool moreXPad = false;
|
|
|
|
int16_t dataLength = 0;
|
|
|
|
|
|
|
|
if ((CI & 037) == 02) { // start of segment
|
|
|
|
uint16_t prefix = (data [0] << 8) | data [1];
|
|
|
|
uint8_t field_1 = (prefix >> 8) & 017;
|
|
|
|
uint8_t Cflag = (prefix >> 12) & 01;
|
|
|
|
uint8_t first = (prefix >> 14) & 01;
|
|
|
|
uint8_t last = (prefix >> 13) & 01;
|
|
|
|
dataLength = length - 2; // The length with header removed
|
|
|
|
|
|
|
|
if (first) {
|
|
|
|
segmentno = 1;
|
|
|
|
charSet = (prefix >> 4) & 017;
|
|
|
|
dynamicLabelText. clear ();
|
|
|
|
}
|
|
|
|
else
|
2017-06-02 14:08:03 +02:00
|
|
|
segmentno = ((prefix >> 4) & 07) + 1;
|
2017-02-06 14:09:08 +01:00
|
|
|
|
|
|
|
if (Cflag) { // special dynamic label command
|
|
|
|
// the only specified command is to clear the display
|
|
|
|
dynamicLabelText. clear ();
|
|
|
|
}
|
|
|
|
else { // Dynamic text length
|
|
|
|
int16_t totalDataLength = field_1 + 1;
|
|
|
|
if (length - 2 < totalDataLength) {
|
|
|
|
dataLength = length - 2; // the length is shortened by header
|
|
|
|
moreXPad = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
dataLength = totalDataLength; // no more xpad app's 3
|
|
|
|
moreXPad = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// convert dynamic label
|
|
|
|
QString segmentText = toQStringUsingCharset (
|
|
|
|
(const char *)&data [2],
|
|
|
|
(CharacterSet) charSet,
|
|
|
|
dataLength);
|
|
|
|
|
|
|
|
dynamicLabelText. append (segmentText);
|
|
|
|
|
|
|
|
// if at the end, show the label
|
|
|
|
if (last) {
|
|
|
|
if (!moreXPad) {
|
|
|
|
showLabel (dynamicLabelText);
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
isLastSegment = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
isLastSegment = false;
|
|
|
|
// calculate remaining data length
|
|
|
|
remainDataLength = totalDataLength - dataLength;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
if (((CI & 037) == 03) && moreXPad) {
|
|
|
|
if (remainDataLength > length) {
|
|
|
|
dataLength = length;
|
|
|
|
remainDataLength -= length;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
dataLength = remainDataLength;
|
|
|
|
moreXPad = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString segmentText = toQStringUsingCharset (
|
|
|
|
(const char *) data,
|
|
|
|
(CharacterSet) charSet,
|
|
|
|
dataLength);
|
|
|
|
dynamicLabelText. append(segmentText);
|
|
|
|
if (!moreXPad && isLastSegment) {
|
|
|
|
showLabel (dynamicLabelText);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
2017-03-27 16:32:49 +02:00
|
|
|
// Called at the start of the msc datagroupfield,
|
|
|
|
// the msc_length was given by the preceding appType "1"
|
|
|
|
void padHandler::new_MSC_element (QByteArray data, int msc_length) {
|
|
|
|
mscGroupElement = true;
|
|
|
|
msc_dataGroupBuffer. clear ();
|
|
|
|
msc_dataGroupBuffer = data;
|
|
|
|
msc_dataGroupLength = msc_length;
|
|
|
|
show_motHandling (true);
|
|
|
|
}
|
|
|
|
|
2017-02-06 14:09:08 +01:00
|
|
|
//
|
2017-03-27 16:32:49 +02:00
|
|
|
void padHandler::add_MSC_element (QByteArray data) {
|
2017-02-06 14:09:08 +01:00
|
|
|
int16_t i;
|
2017-03-27 16:32:49 +02:00
|
|
|
int16_t currentLength = msc_dataGroupBuffer. length ();
|
|
|
|
//
|
|
|
|
// just to ensure that, when a "12" appType is missing, the
|
|
|
|
// data of "13" appType elements is not endless collected.
|
|
|
|
if (currentLength == 0)
|
2017-02-06 14:09:08 +01:00
|
|
|
return;
|
|
|
|
|
2017-03-27 16:32:49 +02:00
|
|
|
msc_dataGroupBuffer. append (data);
|
|
|
|
if (msc_dataGroupBuffer. length () >= msc_dataGroupLength) {
|
|
|
|
build_MSC_segment (msc_dataGroupBuffer, msc_dataGroupLength);
|
|
|
|
msc_dataGroupBuffer. clear ();
|
|
|
|
// mscGroupElement = false;
|
|
|
|
xpadLength = -1;
|
|
|
|
show_motHandling (false);
|
|
|
|
}
|
2017-02-06 14:09:08 +01:00
|
|
|
}
|
|
|
|
|
2017-03-27 16:32:49 +02:00
|
|
|
void padHandler::build_MSC_segment (QByteArray mscdataGroup,
|
|
|
|
int msc_length) {
|
2017-02-06 14:09:08 +01:00
|
|
|
// we have a MOT segment, let us look what is in it
|
|
|
|
// according to DAB 300 401 (page 37) the header (MSC data group)
|
|
|
|
// is
|
2017-03-27 16:32:49 +02:00
|
|
|
uint8_t *data = (uint8_t *)(mscdataGroup. data ());
|
|
|
|
int16_t size = mscdataGroup. length ();
|
|
|
|
|
|
|
|
uint8_t groupType = data [0] & 0xF;
|
|
|
|
uint8_t continuityIndex = (data [1] & 0xF) >> 4;
|
|
|
|
uint8_t repetitionIndex = data [1] & 0xF;
|
2017-02-06 14:09:08 +01:00
|
|
|
int16_t segmentNumber = -1; // default
|
|
|
|
int16_t transportId = -1; // default
|
|
|
|
bool lastFlag = false; // default
|
|
|
|
uint16_t index;
|
|
|
|
|
2017-03-27 16:32:49 +02:00
|
|
|
if ((data [0] & 0x40) != 0) {
|
|
|
|
bool res = check_crc_bytes (data, msc_length - 2);
|
2017-02-06 14:09:08 +01:00
|
|
|
if (!res) {
|
2017-04-04 14:04:25 +02:00
|
|
|
// fprintf (stderr, "crc failed ");
|
2017-02-06 14:09:08 +01:00
|
|
|
return;
|
|
|
|
}
|
2017-03-31 16:44:07 +02:00
|
|
|
// else
|
2017-04-04 14:04:25 +02:00
|
|
|
// fprintf (stderr, "crc success ");
|
2017-02-06 14:09:08 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if ((groupType != 3) && (groupType != 4))
|
|
|
|
return; // do not know yet
|
2017-04-04 14:04:25 +02:00
|
|
|
|
2017-02-06 14:09:08 +01:00
|
|
|
// extensionflag
|
2017-03-27 16:32:49 +02:00
|
|
|
bool extensionFlag = (data [0] & 0x80) != 0;
|
2017-02-06 14:09:08 +01:00
|
|
|
// if the segmentflag is on, then a lastflag and segmentnumber are
|
|
|
|
// available, i.e. 2 bytes more
|
|
|
|
index = extensionFlag ? 4 : 2;
|
2017-03-27 16:32:49 +02:00
|
|
|
bool segmentFlag = (data [0] & 0x20) != 0;
|
2017-02-06 14:09:08 +01:00
|
|
|
if ((segmentFlag) != 0) {
|
2017-03-27 16:32:49 +02:00
|
|
|
lastFlag = data [index] & 0x80;
|
|
|
|
segmentNumber = ((data [index] & 0x7F) << 8) | data [index + 1];
|
2017-02-06 14:09:08 +01:00
|
|
|
index += 2;
|
|
|
|
}
|
2017-04-04 14:04:25 +02:00
|
|
|
|
2017-02-06 14:09:08 +01:00
|
|
|
// if the user access flag is on there is a user accessfield
|
2017-03-27 16:32:49 +02:00
|
|
|
if ((data [0] & 0x10) != 0) {
|
|
|
|
int16_t lengthIndicator = data [index] & 0x0F;
|
|
|
|
if ((data [index] & 0x10) != 0) { //transportid flag
|
|
|
|
transportId = data [index + 1] << 8 |
|
|
|
|
data [index + 2];
|
2017-02-06 14:09:08 +01:00
|
|
|
index += 3;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
fprintf (stderr, "sorry no transportId\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
index += (lengthIndicator - 2);
|
|
|
|
}
|
2017-03-27 16:32:49 +02:00
|
|
|
|
|
|
|
// the segment is handled by the mot handler, which also
|
|
|
|
// handles the MOT's from the regular data services
|
|
|
|
my_motHandler -> process_mscGroup (&data [index],
|
2017-02-06 14:09:08 +01:00
|
|
|
groupType,
|
|
|
|
lastFlag,
|
|
|
|
segmentNumber,
|
|
|
|
transportId);
|
|
|
|
}
|
|
|
|
|