mirror of
https://github.com/oliverschmidt/contiki.git
synced 2024-12-23 01:29:33 +00:00
560 lines
16 KiB
C
560 lines
16 KiB
C
/*
|
|
* Original file:
|
|
* Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com/
|
|
* All rights reserved.
|
|
*
|
|
* Port to Contiki:
|
|
* Copyright (c) 2014 Andreas Dröscher <contiki@anticat.ch>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* 3. Neither the name of the copyright holder nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
|
|
* OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
/**
|
|
* \addtogroup cc2538-ecc
|
|
* @{
|
|
*
|
|
* \file
|
|
* Implementation of the cc2538 ECC driver
|
|
*/
|
|
#include "ecc-driver.h"
|
|
#include "reg.h"
|
|
#include "dev/nvic.h"
|
|
|
|
#define ASSERT(IF) if(!(IF)) { return PKA_STATUS_INVALID_PARAM; }
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
uint8_t
|
|
ecc_mul_start(uint32_t *scalar, ec_point_t *ec_point,
|
|
ecc_curve_info_t *curve, uint32_t *result_vector,
|
|
struct process *process)
|
|
{
|
|
|
|
uint8_t extraBuf;
|
|
uint32_t offset;
|
|
int i;
|
|
|
|
/* Check for the arguments. */
|
|
ASSERT(NULL != scalar);
|
|
ASSERT(NULL != ec_point);
|
|
ASSERT(NULL != ec_point->x);
|
|
ASSERT(NULL != ec_point->y);
|
|
ASSERT(NULL != curve);
|
|
ASSERT(curve->size <= PKA_MAX_CURVE_SIZE);
|
|
ASSERT(NULL != result_vector);
|
|
|
|
offset = 0;
|
|
|
|
/* Make sure no PKA operation is in progress. */
|
|
if((REG(PKA_FUNCTION) & PKA_FUNCTION_RUN) != 0) {
|
|
return PKA_STATUS_OPERATION_INPRG;
|
|
}
|
|
|
|
/* Calculate the extra buffer requirement. */
|
|
extraBuf = 2 + curve->size % 2;
|
|
|
|
/* Update the A ptr with the offset address of the PKA RAM location
|
|
* where the scalar will be stored. */
|
|
REG(PKA_APTR) = offset >> 2;
|
|
|
|
/* Load the scalar in PKA RAM. */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = *scalar++;
|
|
}
|
|
|
|
/* Determine the offset for the next data. */
|
|
offset += 4 * (i + (curve->size % 2));
|
|
|
|
/* Update the B ptr with the offset address of the PKA RAM location
|
|
* where the curve parameters will be stored. */
|
|
REG(PKA_BPTR) = offset >> 2;
|
|
|
|
/* Write curve parameter 'p' as 1st part of vector B immediately
|
|
* following vector A at PKA RAM */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->prime[i];
|
|
}
|
|
|
|
/* Determine the offset for the next data. */
|
|
offset += 4 * (i + extraBuf);
|
|
|
|
/* Copy curve parameter 'a' in PKA RAM. */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->a[i];
|
|
}
|
|
|
|
/* Determine the offset for the next data. */
|
|
offset += 4 * (i + extraBuf);
|
|
|
|
/* Copy curve parameter 'b' in PKA RAM. */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->b[i];
|
|
}
|
|
|
|
/* Determine the offset for the next data. */
|
|
offset += 4 * (i + extraBuf);
|
|
|
|
/* Update the C ptr with the offset address of the PKA RAM location
|
|
* where the x, y will be stored. */
|
|
REG(PKA_CPTR) = offset >> 2;
|
|
|
|
/* Write elliptic curve point.x co-ordinate value. */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = ec_point->x[i];
|
|
}
|
|
|
|
/* Determine the offset for the next data. */
|
|
offset += 4 * (i + extraBuf);
|
|
|
|
/* Write elliptic curve point.y co-ordinate value. */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = ec_point->y[i];
|
|
}
|
|
|
|
/* Determine the offset for the next data. */
|
|
offset += 4 * (i + extraBuf);
|
|
|
|
/* Update the result location. */
|
|
*result_vector = PKA_RAM_BASE + offset;
|
|
|
|
/* Load D ptr with the result location in PKA RAM. */
|
|
REG(PKA_DPTR) = offset >> 2;
|
|
|
|
/* Load length registers. */
|
|
REG(PKA_ALENGTH) = curve->size;
|
|
REG(PKA_BLENGTH) = curve->size;
|
|
|
|
/* set the PKA function to ECC-MULT and start the operation. */
|
|
REG(PKA_FUNCTION) = 0x0000D000;
|
|
|
|
/* Enable Interrupt */
|
|
if(process != NULL) {
|
|
pka_register_process_notification(process);
|
|
nvic_interrupt_unpend(NVIC_INT_PKA);
|
|
nvic_interrupt_enable(NVIC_INT_PKA);
|
|
}
|
|
|
|
return PKA_STATUS_SUCCESS;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
uint8_t
|
|
ecc_mul_get_result(ec_point_t *ec_point,
|
|
uint32_t result_vector)
|
|
{
|
|
int i;
|
|
uint32_t addr;
|
|
uint32_t regMSWVal;
|
|
uint32_t len;
|
|
|
|
/* Check for the arguments. */
|
|
ASSERT(NULL != ec_point);
|
|
ASSERT(NULL != ec_point->x);
|
|
ASSERT(NULL != ec_point->y);
|
|
ASSERT(result_vector > PKA_RAM_BASE);
|
|
ASSERT(result_vector < (PKA_RAM_BASE + PKA_RAM_SIZE));
|
|
|
|
/* Verify that the operation is completed. */
|
|
if((REG(PKA_FUNCTION) & PKA_FUNCTION_RUN) != 0) {
|
|
return PKA_STATUS_OPERATION_INPRG;
|
|
}
|
|
|
|
/* Disable Interrupt */
|
|
nvic_interrupt_disable(NVIC_INT_PKA);
|
|
pka_register_process_notification(NULL);
|
|
|
|
if(REG(PKA_SHIFT) == 0x00000000) {
|
|
/* Get the MSW register value. */
|
|
regMSWVal = REG(PKA_MSW);
|
|
|
|
/* Check to make sure that the result vector is not all zeroes. */
|
|
if(regMSWVal & PKA_MSW_RESULT_IS_ZERO) {
|
|
return PKA_STATUS_RESULT_0;
|
|
}
|
|
|
|
/* Get the length of the result */
|
|
len = ((regMSWVal & PKA_MSW_MSW_ADDRESS_M) + 1)
|
|
- ((result_vector - PKA_RAM_BASE) >> 2);
|
|
|
|
addr = result_vector;
|
|
|
|
/* copy the x co-ordinate value of the result from vector D into
|
|
* the \e ec_point. */
|
|
for(i = 0; i < len; i++) {
|
|
ec_point->x[i] = REG(addr + 4 * i);
|
|
}
|
|
|
|
addr += 4 * (i + 2 + len % 2);
|
|
|
|
/* copy the y co-ordinate value of the result from vector D into
|
|
* the \e ec_point. */
|
|
for(i = 0; i < len; i++) {
|
|
ec_point->y[i] = REG(addr + 4 * i);
|
|
}
|
|
|
|
return PKA_STATUS_SUCCESS;
|
|
} else {
|
|
return PKA_STATUS_FAILURE;
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
uint8_t
|
|
ecc_mul_gen_pt_start(uint32_t *scalar, ecc_curve_info_t *curve,
|
|
uint32_t *result_vector, struct process *process)
|
|
{
|
|
uint8_t extraBuf;
|
|
uint32_t offset;
|
|
int i;
|
|
|
|
/* check for the arguments. */
|
|
ASSERT(NULL != scalar);
|
|
ASSERT(NULL != curve);
|
|
ASSERT(curve->size <= PKA_MAX_CURVE_SIZE);
|
|
ASSERT(NULL != result_vector);
|
|
|
|
offset = 0;
|
|
|
|
/* Make sure no operation is in progress. */
|
|
if((REG(PKA_FUNCTION) & PKA_FUNCTION_RUN) != 0) {
|
|
return PKA_STATUS_OPERATION_INPRG;
|
|
}
|
|
|
|
/* Calculate the extra buffer requirement. */
|
|
extraBuf = 2 + curve->size % 2;
|
|
|
|
/* Update the A ptr with the offset address of the PKA RAM location
|
|
* where the scalar will be stored. */
|
|
REG(PKA_APTR) = offset >> 2;
|
|
|
|
/* Load the scalar in PKA RAM. */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = *scalar++;
|
|
}
|
|
|
|
/* Determine the offset in PKA RAM for the next data. */
|
|
offset += 4 * (i + (curve->size % 2));
|
|
|
|
/* Update the B ptr with the offset address of the PKA RAM location
|
|
* where the curve parameters will be stored. */
|
|
REG(PKA_BPTR) = offset >> 2;
|
|
|
|
/* Write curve parameter 'p' as 1st part of vector B. */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->prime[i];
|
|
}
|
|
|
|
/* Determine the offset in PKA RAM for the next data. */
|
|
offset += 4 * (i + extraBuf);
|
|
|
|
/* Write curve parameter 'a' in PKA RAM. */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->a[i];
|
|
}
|
|
|
|
/* Determine the offset in PKA RAM for the next data. */
|
|
offset += 4 * (i + extraBuf);
|
|
|
|
/* write curve parameter 'b' in PKA RAM. */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->b[i];
|
|
}
|
|
|
|
/* Determine the offset in PKA RAM for the next data. */
|
|
offset += 4 * (i + extraBuf);
|
|
|
|
/* Update the C ptr with the offset address of the PKA RAM location
|
|
* where the x, y will be stored. */
|
|
REG(PKA_CPTR) = offset >> 2;
|
|
|
|
/* Write x co-ordinate value of the Generator point in PKA RAM. */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->x[i];
|
|
}
|
|
|
|
/* Determine the offset in PKA RAM for the next data. */
|
|
offset += 4 * (i + extraBuf);
|
|
|
|
/* Write y co-ordinate value of the Generator point in PKA RAM. */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->y[i];
|
|
}
|
|
|
|
/* Determine the offset in PKA RAM for the next data. */
|
|
offset += 4 * (i + extraBuf);
|
|
|
|
/* Update the result location. */
|
|
*result_vector = PKA_RAM_BASE + offset;
|
|
|
|
/* Load D ptr with the result location in PKA RAM. */
|
|
REG(PKA_DPTR) = offset >> 2;
|
|
|
|
/* Load length registers. */
|
|
REG(PKA_ALENGTH) = curve->size;
|
|
REG(PKA_BLENGTH) = curve->size;
|
|
|
|
/* Set the PKA function to ECC-MULT and start the operation. */
|
|
REG(PKA_FUNCTION) = 0x0000D000;
|
|
|
|
/* Enable Interrupt */
|
|
if(process != NULL) {
|
|
pka_register_process_notification(process);
|
|
nvic_interrupt_unpend(NVIC_INT_PKA);
|
|
nvic_interrupt_enable(NVIC_INT_PKA);
|
|
}
|
|
|
|
return PKA_STATUS_SUCCESS;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
uint8_t
|
|
ecc_mul_gen_pt_get_result(ec_point_t *ec_point,
|
|
uint32_t result_vector)
|
|
{
|
|
|
|
int i;
|
|
uint32_t regMSWVal;
|
|
uint32_t addr;
|
|
uint32_t len;
|
|
|
|
/* Check for the arguments. */
|
|
ASSERT(NULL != ec_point);
|
|
ASSERT(NULL != ec_point->x);
|
|
ASSERT(NULL != ec_point->y);
|
|
ASSERT(result_vector > PKA_RAM_BASE);
|
|
ASSERT(result_vector < (PKA_RAM_BASE + PKA_RAM_SIZE));
|
|
|
|
/* Verify that the operation is completed. */
|
|
if((REG(PKA_FUNCTION) & PKA_FUNCTION_RUN) != 0) {
|
|
return PKA_STATUS_OPERATION_INPRG;
|
|
}
|
|
|
|
/* Disable Interrupt */
|
|
nvic_interrupt_disable(NVIC_INT_PKA);
|
|
pka_register_process_notification(NULL);
|
|
|
|
if(REG(PKA_SHIFT) == 0x00000000) {
|
|
/* Get the MSW register value. */
|
|
regMSWVal = REG(PKA_MSW);
|
|
|
|
/* Check to make sure that the result vector is not all zeroes. */
|
|
if(regMSWVal & PKA_MSW_RESULT_IS_ZERO) {
|
|
return PKA_STATUS_RESULT_0;
|
|
}
|
|
|
|
/* Get the length of the result. */
|
|
len = ((regMSWVal & PKA_MSW_MSW_ADDRESS_M) + 1)
|
|
- ((result_vector - PKA_RAM_BASE) >> 2);
|
|
|
|
addr = result_vector;
|
|
|
|
/* Copy the x co-ordinate value of the result from vector D into the
|
|
* EC point. */
|
|
for(i = 0; i < len; i++) {
|
|
ec_point->x[i] = REG(addr + 4 * i);
|
|
}
|
|
|
|
addr += 4 * (i + 2 + len % 2);
|
|
|
|
/* Copy the y co-ordinate value of the result from vector D into the
|
|
* EC point. */
|
|
for(i = 0; i < len; i++) {
|
|
ec_point->y[i] = REG(addr + 4 * i);
|
|
}
|
|
|
|
return PKA_STATUS_SUCCESS;
|
|
} else {
|
|
return PKA_STATUS_FAILURE;
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
uint8_t
|
|
ecc_add_start(ec_point_t *ec_point1, ec_point_t *ec_point2,
|
|
ecc_curve_info_t *curve, uint32_t *result_vector,
|
|
struct process *process)
|
|
{
|
|
|
|
uint8_t extraBuf;
|
|
uint32_t offset;
|
|
int i;
|
|
|
|
/* Check for the arguments. */
|
|
ASSERT(NULL != ec_point1);
|
|
ASSERT(NULL != ec_point1->x);
|
|
ASSERT(NULL != ec_point1->y);
|
|
ASSERT(NULL != ec_point2);
|
|
ASSERT(NULL != ec_point2->x);
|
|
ASSERT(NULL != ec_point2->y);
|
|
ASSERT(NULL != curve);
|
|
ASSERT(NULL != result_vector);
|
|
|
|
offset = 0;
|
|
|
|
/* Make sure no operation is in progress. */
|
|
if((REG(PKA_FUNCTION) & PKA_FUNCTION_RUN) != 0) {
|
|
return PKA_STATUS_OPERATION_INPRG;
|
|
}
|
|
|
|
/* Calculate the extra buffer requirement. */
|
|
extraBuf = 2 + curve->size % 2;
|
|
|
|
/* Update the A ptr with the offset address of the PKA RAM location
|
|
* where the first ecPt will be stored. */
|
|
REG(PKA_APTR) = offset >> 2;
|
|
|
|
/* Load the x co-ordinate value of the first EC point in PKA RAM. */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = ec_point1->x[i];
|
|
}
|
|
|
|
/* Determine the offset in PKA RAM for the next data. */
|
|
offset += 4 * (i + extraBuf);
|
|
|
|
/* Load the y co-ordinate value of the first EC point in PKA RAM. */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = ec_point1->y[i];
|
|
}
|
|
|
|
/* Determine the offset in PKA RAM for the next data. */
|
|
offset += 4 * (i + extraBuf);
|
|
|
|
/* Update the B ptr with the offset address of the PKA RAM location
|
|
* where the curve parameters will be stored. */
|
|
REG(PKA_BPTR) = offset >> 2;
|
|
|
|
/* Write curve parameter 'p' as 1st part of vector B */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->prime[i];
|
|
}
|
|
|
|
/* Determine the offset in PKA RAM for the next data. */
|
|
offset += 4 * (i + extraBuf);
|
|
|
|
/* Write curve parameter 'a'. */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = (uint32_t)curve->a[i];
|
|
}
|
|
|
|
/* Determine the offset in PKA RAM for the next data. */
|
|
offset += 4 * (i + extraBuf);
|
|
|
|
/* Update the C ptr with the offset address of the PKA RAM location
|
|
* where the ecPt2 will be stored. */
|
|
REG(PKA_CPTR) = offset >> 2;
|
|
|
|
/* Load the x co-ordinate value of the second EC point in PKA RAM. */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = ec_point2->x[i];
|
|
}
|
|
|
|
/* Determine the offset in PKA RAM for the next data. */
|
|
offset += 4 * (i + extraBuf);
|
|
|
|
/* Load the y co-ordinate value of the second EC point in PKA RAM. */
|
|
for(i = 0; i < curve->size; i++) {
|
|
REG(PKA_RAM_BASE + offset + 4 * i) = ec_point2->y[i];
|
|
}
|
|
|
|
/* Determine the offset in PKA RAM for the next data. */
|
|
offset += 4 * (i + extraBuf);
|
|
|
|
/* Copy the result vector location. */
|
|
*result_vector = PKA_RAM_BASE + offset;
|
|
|
|
/* Load D ptr with the result location in PKA RAM. */
|
|
REG(PKA_DPTR) = offset >> 2;
|
|
|
|
/* Load length registers. */
|
|
REG(PKA_BLENGTH) = curve->size;
|
|
|
|
/* Set the PKA Function to ECC-ADD and start the operation. */
|
|
REG(PKA_FUNCTION) = 0x0000B000;
|
|
|
|
/* Enable Interrupt */
|
|
if(process != NULL) {
|
|
pka_register_process_notification(process);
|
|
nvic_interrupt_unpend(NVIC_INT_PKA);
|
|
nvic_interrupt_enable(NVIC_INT_PKA);
|
|
}
|
|
|
|
return PKA_STATUS_SUCCESS;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
uint8_t
|
|
ecc_add_get_result(ec_point_t *ec_point, uint32_t result_vector)
|
|
{
|
|
uint32_t regMSWVal;
|
|
uint32_t addr;
|
|
int i;
|
|
uint32_t len;
|
|
|
|
/* Check for the arguments. */
|
|
ASSERT(NULL != ec_point);
|
|
ASSERT(NULL != ec_point->x);
|
|
ASSERT(NULL != ec_point->y);
|
|
ASSERT(result_vector > PKA_RAM_BASE);
|
|
ASSERT(result_vector < (PKA_RAM_BASE + PKA_RAM_SIZE));
|
|
|
|
if((REG(PKA_FUNCTION) & PKA_FUNCTION_RUN) != 0) {
|
|
return PKA_STATUS_OPERATION_INPRG;
|
|
}
|
|
|
|
/* Disable Interrupt */
|
|
nvic_interrupt_disable(NVIC_INT_PKA);
|
|
pka_register_process_notification(NULL);
|
|
|
|
if(REG(PKA_SHIFT) == 0x00000000) {
|
|
/* Get the MSW register value. */
|
|
regMSWVal = REG(PKA_MSW);
|
|
|
|
/* Check to make sure that the result vector is not all zeroes. */
|
|
if(regMSWVal & PKA_MSW_RESULT_IS_ZERO) {
|
|
return PKA_STATUS_RESULT_0;
|
|
}
|
|
|
|
/* Get the length of the result. */
|
|
len = ((regMSWVal & PKA_MSW_MSW_ADDRESS_M) + 1)
|
|
- ((result_vector - PKA_RAM_BASE) >> 2);
|
|
|
|
addr = result_vector;
|
|
|
|
/* Copy the x co-ordinate value of result from vector D into the
|
|
* the output EC Point. */
|
|
for(i = 0; i < len; i++) {
|
|
ec_point->x[i] = REG(addr + 4 * i);
|
|
}
|
|
|
|
addr += 4 * (i + 2 + len % 2);
|
|
|
|
/* Copy the y co-ordinate value of result from vector D into the
|
|
* the output EC Point. */
|
|
for(i = 0; i < len; i++) {
|
|
ec_point->y[i] = REG(addr + 4 * i);
|
|
}
|
|
|
|
return PKA_STATUS_SUCCESS;
|
|
} else {
|
|
return PKA_STATUS_FAILURE;
|
|
}
|
|
}
|
|
/** @} */
|