2019-12-24 17:20:07 +08:00
|
|
|
//-----------------------------------------------------------------------------
|
2022-01-06 09:19:46 +08:00
|
|
|
// Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
|
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
|
|
|
// it under the terms of the GNU General Public License as published by
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU General Public License for more details.
|
|
|
|
//
|
|
|
|
// See LICENSE.txt for the text of the license.
|
2019-12-24 17:20:07 +08:00
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
// LF ADC read/write implementation
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
|
|
|
|
#include "lfadc.h"
|
|
|
|
#include "lfsampling.h"
|
|
|
|
#include "fpgaloader.h"
|
|
|
|
#include "ticks.h"
|
2020-02-22 20:16:04 +08:00
|
|
|
#include "dbprint.h"
|
2020-06-23 17:13:49 +08:00
|
|
|
#include "appmain.h"
|
2019-12-24 17:20:07 +08:00
|
|
|
|
|
|
|
// Sam7s has several timers, we will use the source TIMER_CLOCK1 (aka AT91C_TC_CLKS_TIMER_DIV1_CLOCK)
|
|
|
|
// TIMER_CLOCK1 = MCK/2, MCK is running at 48 MHz, Timer is running at 48/2 = 24 MHz
|
|
|
|
// Carrier periods (T0) have duration of 8 microseconds (us), which is 1/125000 per second
|
|
|
|
// T0 = TIMER_CLOCK1 / 125000 = 192
|
|
|
|
//#define T0 192
|
|
|
|
|
|
|
|
// Sam7s has three counters, we will use the first TIMER_COUNTER_0 (aka TC0)
|
|
|
|
// using TIMER_CLOCK3 (aka AT91C_TC_CLKS_TIMER_DIV3_CLOCK)
|
|
|
|
// as a counting signal. TIMER_CLOCK3 = MCK/32, MCK is running at 48 MHz, so the timer is running at 48/32 = 1500 kHz
|
|
|
|
// Carrier period (T0) have duration of 8 microseconds (us), which is 1/125000 per second (125 kHz frequency)
|
|
|
|
// T0 = timer/carrier = 1500kHz/125kHz = 1500000/125000 = 6
|
2020-01-17 21:25:57 +08:00
|
|
|
//#define HITAG_T0 3
|
2019-12-24 17:20:07 +08:00
|
|
|
|
2020-05-19 23:05:43 +08:00
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Exported global variables
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
bool g_logging = true;
|
|
|
|
|
2019-12-24 17:20:07 +08:00
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Global variables
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
2020-05-19 23:05:43 +08:00
|
|
|
static bool rising_edge = false;
|
|
|
|
static bool reader_mode = false;
|
2019-12-24 17:20:07 +08:00
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Auxiliary functions
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
bool lf_test_periods(size_t expected, size_t count) {
|
|
|
|
// Compute 10% deviation (integer operation, so rounded down)
|
|
|
|
size_t diviation = expected / 10;
|
|
|
|
return ((count > (expected - diviation)) && (count < (expected + diviation)));
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Low frequency (LF) adc passthrough functionality
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
2021-04-04 23:08:19 +08:00
|
|
|
static uint8_t previous_adc_val = 0; //0xFF;
|
2020-05-19 23:25:50 +08:00
|
|
|
static uint8_t adc_avg = 0;
|
2020-02-22 20:16:04 +08:00
|
|
|
|
2021-04-04 23:08:19 +08:00
|
|
|
uint8_t get_adc_avg(void) {
|
|
|
|
return adc_avg;
|
|
|
|
}
|
2020-02-22 20:16:04 +08:00
|
|
|
void lf_sample_mean(void) {
|
|
|
|
uint8_t periods = 0;
|
|
|
|
uint32_t adc_sum = 0;
|
|
|
|
while (periods < 32) {
|
|
|
|
if (AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) {
|
|
|
|
adc_sum += AT91C_BASE_SSC->SSC_RHR;
|
|
|
|
periods++;
|
|
|
|
}
|
|
|
|
}
|
2020-03-24 17:08:11 +08:00
|
|
|
// division by 32
|
2020-02-22 20:16:04 +08:00
|
|
|
adc_avg = adc_sum >> 5;
|
2021-04-04 23:08:19 +08:00
|
|
|
previous_adc_val = adc_avg;
|
2020-02-22 20:16:04 +08:00
|
|
|
|
2021-08-22 05:02:27 +08:00
|
|
|
if (g_dbglevel >= DBG_EXTENDED)
|
2020-02-22 20:16:04 +08:00
|
|
|
Dbprintf("LF ADC average %u", adc_avg);
|
|
|
|
}
|
2019-12-24 17:20:07 +08:00
|
|
|
|
2020-05-10 22:59:38 +08:00
|
|
|
static size_t lf_count_edge_periods_ex(size_t max, bool wait, bool detect_gap) {
|
2021-04-04 23:08:19 +08:00
|
|
|
|
|
|
|
#define LIMIT_DEV 20
|
|
|
|
|
2021-04-08 16:44:31 +08:00
|
|
|
// timeout limit to 100 000 w/o
|
2021-04-04 23:08:19 +08:00
|
|
|
uint32_t timeout = 100000;
|
2019-12-24 17:20:07 +08:00
|
|
|
size_t periods = 0;
|
2021-04-04 23:08:19 +08:00
|
|
|
uint8_t avg_peak = adc_avg + LIMIT_DEV;
|
|
|
|
uint8_t avg_through = adc_avg - LIMIT_DEV;
|
2019-12-24 17:20:07 +08:00
|
|
|
|
2020-06-23 17:13:49 +08:00
|
|
|
while (BUTTON_PRESS() == false) {
|
2021-04-04 23:08:19 +08:00
|
|
|
WDT_HIT();
|
|
|
|
|
|
|
|
timeout--;
|
|
|
|
if (timeout == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_TXRDY)) {
|
|
|
|
AT91C_BASE_SSC->SSC_THR = 0x00;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-12-24 17:20:07 +08:00
|
|
|
if (AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) {
|
2021-04-04 23:08:19 +08:00
|
|
|
|
2019-12-24 17:20:07 +08:00
|
|
|
periods++;
|
|
|
|
|
2021-04-04 23:08:19 +08:00
|
|
|
// reset timeout
|
|
|
|
timeout = 100000;
|
|
|
|
|
|
|
|
volatile uint8_t adc_val = AT91C_BASE_SSC->SSC_RHR;
|
|
|
|
|
2020-05-19 23:05:43 +08:00
|
|
|
if (g_logging) logSampleSimple(adc_val);
|
2019-12-24 17:20:07 +08:00
|
|
|
|
|
|
|
// Only test field changes if state of adc values matter
|
2020-01-30 00:26:08 +08:00
|
|
|
if (wait == false) {
|
2019-12-24 17:20:07 +08:00
|
|
|
// Test if we are locating a field modulation (100% ASK = complete field drop)
|
|
|
|
if (detect_gap) {
|
2021-10-10 07:35:38 +08:00
|
|
|
// Only return when the field completely disappeared
|
2019-12-24 17:20:07 +08:00
|
|
|
if (adc_val == 0) {
|
|
|
|
return periods;
|
|
|
|
}
|
2020-06-23 17:13:49 +08:00
|
|
|
|
2019-12-24 17:20:07 +08:00
|
|
|
} else {
|
|
|
|
// Trigger on a modulation swap by observing an edge change
|
|
|
|
if (rising_edge) {
|
2021-04-04 23:08:19 +08:00
|
|
|
|
2019-12-24 17:20:07 +08:00
|
|
|
if ((previous_adc_val > avg_peak) && (adc_val <= previous_adc_val)) {
|
|
|
|
rising_edge = false;
|
|
|
|
return periods;
|
|
|
|
}
|
2021-04-04 23:08:19 +08:00
|
|
|
|
2019-12-24 17:20:07 +08:00
|
|
|
} else {
|
2021-04-04 23:08:19 +08:00
|
|
|
|
2019-12-24 17:20:07 +08:00
|
|
|
if ((previous_adc_val < avg_through) && (adc_val >= previous_adc_val)) {
|
|
|
|
rising_edge = true;
|
|
|
|
return periods;
|
|
|
|
}
|
2021-04-04 23:08:19 +08:00
|
|
|
|
2019-12-24 17:20:07 +08:00
|
|
|
}
|
2021-04-08 16:44:31 +08:00
|
|
|
}
|
2019-12-24 17:20:07 +08:00
|
|
|
}
|
2021-04-04 23:08:19 +08:00
|
|
|
|
2019-12-24 17:20:07 +08:00
|
|
|
previous_adc_val = adc_val;
|
2020-02-22 20:16:04 +08:00
|
|
|
|
2021-04-04 23:08:19 +08:00
|
|
|
if (periods >= max) {
|
|
|
|
return 0;
|
|
|
|
}
|
2019-12-24 17:20:07 +08:00
|
|
|
}
|
|
|
|
}
|
2020-06-23 17:13:49 +08:00
|
|
|
|
2020-05-19 23:05:43 +08:00
|
|
|
if (g_logging) logSampleSimple(0xFF);
|
2019-12-24 17:20:07 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t lf_count_edge_periods(size_t max) {
|
|
|
|
return lf_count_edge_periods_ex(max, false, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t lf_detect_gap(size_t max) {
|
|
|
|
return lf_count_edge_periods_ex(max, false, true);
|
|
|
|
}
|
|
|
|
|
2020-05-10 22:59:38 +08:00
|
|
|
void lf_reset_counter(void) {
|
2020-03-01 23:39:25 +08:00
|
|
|
|
2019-12-24 17:20:07 +08:00
|
|
|
// TODO: find out the correct reset settings for tag and reader mode
|
2020-03-01 23:39:25 +08:00
|
|
|
// if (reader_mode) {
|
2020-03-24 17:08:11 +08:00
|
|
|
// Reset values for reader mode
|
|
|
|
rising_edge = false;
|
|
|
|
previous_adc_val = 0xFF;
|
2020-03-01 23:39:25 +08:00
|
|
|
|
|
|
|
// } else {
|
2020-03-24 17:08:11 +08:00
|
|
|
// Reset values for tag/transponder mode
|
2020-03-01 23:39:25 +08:00
|
|
|
// rising_edge = false;
|
|
|
|
// previous_adc_val = 0xFF;
|
|
|
|
// }
|
2019-12-24 17:20:07 +08:00
|
|
|
}
|
|
|
|
|
2020-05-10 22:59:38 +08:00
|
|
|
bool lf_get_tag_modulation(void) {
|
2019-12-24 17:20:07 +08:00
|
|
|
return (rising_edge == false);
|
|
|
|
}
|
2020-02-22 20:16:04 +08:00
|
|
|
|
2020-05-10 22:59:38 +08:00
|
|
|
bool lf_get_reader_modulation(void) {
|
2020-02-22 20:16:04 +08:00
|
|
|
return rising_edge;
|
2020-01-30 00:26:08 +08:00
|
|
|
}
|
2019-12-24 17:20:07 +08:00
|
|
|
|
|
|
|
void lf_wait_periods(size_t periods) {
|
2020-08-13 18:25:04 +08:00
|
|
|
// wait detect gap
|
2019-12-24 17:20:07 +08:00
|
|
|
lf_count_edge_periods_ex(periods, true, false);
|
|
|
|
}
|
|
|
|
|
2021-11-18 21:26:41 +08:00
|
|
|
void lf_init(bool reader, bool simulate, bool ledcontrol) {
|
2020-01-29 11:37:10 +08:00
|
|
|
|
|
|
|
StopTicks();
|
|
|
|
|
2019-12-24 17:20:07 +08:00
|
|
|
reader_mode = reader;
|
|
|
|
|
|
|
|
FpgaDownloadAndGo(FPGA_BITSTREAM_LF);
|
|
|
|
|
2020-02-23 17:45:23 +08:00
|
|
|
sample_config *sc = getSamplingConfig();
|
|
|
|
sc->decimation = 1;
|
|
|
|
sc->averaging = 0;
|
|
|
|
|
|
|
|
FpgaSendCommand(FPGA_CMD_SET_DIVISOR, sc->divisor);
|
2019-12-24 17:20:07 +08:00
|
|
|
if (reader) {
|
|
|
|
FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_ADC | FPGA_LF_ADC_READER_FIELD);
|
|
|
|
} else {
|
2020-01-29 11:37:10 +08:00
|
|
|
if (simulate)
|
|
|
|
FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_ADC);
|
|
|
|
else
|
2020-03-24 17:08:11 +08:00
|
|
|
// Sniff
|
2021-04-04 23:08:19 +08:00
|
|
|
//FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_ADC);
|
2020-03-24 17:08:11 +08:00
|
|
|
FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_EDGE_DETECT | FPGA_LF_EDGE_DETECT_TOGGLE_MODE);
|
2020-01-29 11:37:10 +08:00
|
|
|
|
2019-12-24 17:20:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Connect the A/D to the peak-detected low-frequency path.
|
|
|
|
SetAdcMuxFor(GPIO_MUXSEL_LOPKD);
|
|
|
|
|
|
|
|
// Now set up the SSC to get the ADC samples that are now streaming at us.
|
2020-07-02 18:33:53 +08:00
|
|
|
FpgaSetupSsc(FPGA_MAJOR_MODE_LF_READER);
|
2019-12-24 17:20:07 +08:00
|
|
|
|
|
|
|
// When in reader mode, give the field a bit of time to settle.
|
2020-01-18 00:06:46 +08:00
|
|
|
// 313T0 = 313 * 8us = 2504us = 2.5ms Hitag2 tags needs to be fully powered.
|
2021-04-04 23:08:19 +08:00
|
|
|
// if (reader) {
|
2021-04-08 16:44:31 +08:00
|
|
|
// 10 ms
|
|
|
|
SpinDelay(10);
|
2021-04-04 23:08:19 +08:00
|
|
|
// }
|
2019-12-24 17:20:07 +08:00
|
|
|
|
|
|
|
// Steal this pin from the SSP (SPI communication channel with fpga) and use it to control the modulation
|
|
|
|
AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DOUT;
|
|
|
|
AT91C_BASE_PIOA->PIO_OER = GPIO_SSC_DOUT;
|
|
|
|
LOW(GPIO_SSC_DOUT);
|
|
|
|
|
2020-01-10 02:28:44 +08:00
|
|
|
// Enable peripheral Clock for TIMER_CLOCK 0
|
2019-12-24 17:20:07 +08:00
|
|
|
AT91C_BASE_PMC->PMC_PCER = (1 << AT91C_ID_TC0);
|
|
|
|
AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKDIS;
|
|
|
|
AT91C_BASE_TC0->TC_CMR = AT91C_TC_CLKS_TIMER_DIV4_CLOCK;
|
|
|
|
|
2020-01-10 02:28:44 +08:00
|
|
|
// Enable peripheral Clock for TIMER_CLOCK 1
|
2019-12-24 17:20:07 +08:00
|
|
|
AT91C_BASE_PMC->PMC_PCER = (1 << AT91C_ID_TC1);
|
|
|
|
AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS;
|
|
|
|
AT91C_BASE_TC1->TC_CMR = AT91C_TC_CLKS_TIMER_DIV4_CLOCK;
|
|
|
|
|
|
|
|
// Clear all leds
|
2021-11-18 21:26:41 +08:00
|
|
|
if (ledcontrol) LEDsoff();
|
2019-12-24 17:20:07 +08:00
|
|
|
|
|
|
|
// Reset and enable timers
|
|
|
|
AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG;
|
|
|
|
AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG;
|
|
|
|
|
2021-06-24 23:20:48 +08:00
|
|
|
// Assert a sync signal. This sets all timers to 0 on next active clock edge
|
|
|
|
AT91C_BASE_TCB->TCB_BCR = 1;
|
|
|
|
|
2019-12-24 17:20:07 +08:00
|
|
|
// Prepare data trace
|
2020-02-22 20:16:04 +08:00
|
|
|
uint32_t bufsize = 10000;
|
2020-01-15 05:08:43 +08:00
|
|
|
|
2020-01-29 11:37:10 +08:00
|
|
|
// use malloc
|
2020-05-19 23:05:43 +08:00
|
|
|
if (g_logging) initSampleBufferEx(&bufsize, true);
|
2019-12-24 17:20:07 +08:00
|
|
|
|
2020-02-22 20:16:04 +08:00
|
|
|
lf_sample_mean();
|
2019-12-24 17:20:07 +08:00
|
|
|
}
|
|
|
|
|
2021-11-18 21:26:41 +08:00
|
|
|
void lf_finalize(bool ledcontrol) {
|
2019-12-24 17:20:07 +08:00
|
|
|
// Disable timers
|
|
|
|
AT91C_BASE_TC0->TC_CCR = AT91C_TC_CLKDIS;
|
|
|
|
AT91C_BASE_TC1->TC_CCR = AT91C_TC_CLKDIS;
|
|
|
|
|
|
|
|
// return stolen pin to SSP
|
|
|
|
AT91C_BASE_PIOA->PIO_PDR = GPIO_SSC_DOUT;
|
|
|
|
AT91C_BASE_PIOA->PIO_ASR = GPIO_SSC_DIN | GPIO_SSC_DOUT;
|
|
|
|
|
|
|
|
FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
|
|
|
|
|
2021-11-18 21:26:41 +08:00
|
|
|
if (ledcontrol) LEDsoff();
|
2020-01-29 15:18:45 +08:00
|
|
|
|
2020-01-29 11:37:10 +08:00
|
|
|
StartTicks();
|
2019-12-24 17:20:07 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
size_t lf_detect_field_drop(size_t max) {
|
2020-08-13 18:25:04 +08:00
|
|
|
/*
|
|
|
|
size_t periods = 0;
|
|
|
|
// int16_t checked = 0;
|
|
|
|
|
|
|
|
while (BUTTON_PRESS() == false) {
|
|
|
|
|
|
|
|
// // only every 1000th times, in order to save time when collecting samples.
|
|
|
|
// if (checked == 4000) {
|
|
|
|
// if (data_available()) {
|
|
|
|
// checked = -1;
|
|
|
|
// break;
|
|
|
|
// } else {
|
|
|
|
// checked = 0;
|
|
|
|
// }
|
2020-06-23 17:13:49 +08:00
|
|
|
// }
|
2020-08-13 18:25:04 +08:00
|
|
|
// ++checked;
|
2019-12-24 17:20:07 +08:00
|
|
|
|
2020-08-13 18:25:04 +08:00
|
|
|
WDT_HIT();
|
2019-12-24 17:20:07 +08:00
|
|
|
|
2020-08-13 18:25:04 +08:00
|
|
|
if (AT91C_BASE_SSC->SSC_SR & (AT91C_SSC_RXRDY)) {
|
|
|
|
periods++;
|
|
|
|
volatile uint8_t adc_val = AT91C_BASE_SSC->SSC_RHR;
|
2019-12-24 17:20:07 +08:00
|
|
|
|
2020-08-13 18:25:04 +08:00
|
|
|
if (g_logging) logSampleSimple(adc_val);
|
2019-12-24 17:20:07 +08:00
|
|
|
|
2020-08-13 18:25:04 +08:00
|
|
|
if (adc_val == 0) {
|
|
|
|
rising_edge = false;
|
|
|
|
return periods;
|
|
|
|
}
|
2019-12-24 17:20:07 +08:00
|
|
|
|
2020-08-13 18:25:04 +08:00
|
|
|
if (periods == max) return 0;
|
|
|
|
}
|
2019-12-24 17:20:07 +08:00
|
|
|
}
|
2020-08-13 18:25:04 +08:00
|
|
|
*/
|
2019-12-24 17:20:07 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-01-29 15:18:45 +08:00
|
|
|
void lf_modulation(bool modulation) {
|
2019-12-24 17:20:07 +08:00
|
|
|
if (modulation) {
|
|
|
|
HIGH(GPIO_SSC_DOUT);
|
|
|
|
} else {
|
|
|
|
LOW(GPIO_SSC_DOUT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-29 11:37:10 +08:00
|
|
|
// simulation
|
2020-01-29 15:18:45 +08:00
|
|
|
static void lf_manchester_send_bit(uint8_t bit) {
|
2019-12-24 17:20:07 +08:00
|
|
|
lf_modulation(bit != 0);
|
|
|
|
lf_wait_periods(16);
|
|
|
|
lf_modulation(bit == 0);
|
2020-02-22 20:16:04 +08:00
|
|
|
lf_wait_periods(32);
|
2019-12-24 17:20:07 +08:00
|
|
|
}
|
|
|
|
|
2020-01-29 11:37:10 +08:00
|
|
|
// simulation
|
2021-11-18 21:26:41 +08:00
|
|
|
bool lf_manchester_send_bytes(const uint8_t *frame, size_t frame_len, bool ledcontrol) {
|
2019-12-24 17:20:07 +08:00
|
|
|
|
2021-11-18 21:26:41 +08:00
|
|
|
if (ledcontrol) LED_B_ON();
|
2019-12-24 17:20:07 +08:00
|
|
|
|
2020-01-29 11:37:10 +08:00
|
|
|
lf_manchester_send_bit(1);
|
|
|
|
lf_manchester_send_bit(1);
|
|
|
|
lf_manchester_send_bit(1);
|
|
|
|
lf_manchester_send_bit(1);
|
|
|
|
lf_manchester_send_bit(1);
|
|
|
|
|
2019-12-24 17:20:07 +08:00
|
|
|
// Send the content of the frame
|
|
|
|
for (size_t i = 0; i < frame_len; i++) {
|
|
|
|
lf_manchester_send_bit((frame[i / 8] >> (7 - (i % 8))) & 1);
|
|
|
|
}
|
|
|
|
|
2021-11-18 21:26:41 +08:00
|
|
|
if (ledcontrol) LED_B_OFF();
|
2019-12-24 17:20:07 +08:00
|
|
|
return true;
|
|
|
|
}
|