Merge remote-tracking branch 'origin/development' into mueller/expand-retval-if
fsfw/fsfw/pipeline/pr-development This commit looks good Details

This commit is contained in:
Robin Müller 2022-08-15 15:15:51 +02:00
commit 94a718ff19
16 changed files with 298 additions and 71 deletions

View File

@ -1,10 +1,10 @@
#ifndef FSFW_INC_FSFW_SERIALIZE_H_
#define FSFW_INC_FSFW_SERIALIZE_H_
#include "src/core/serialize/EndianConverter.h"
#include "src/core/serialize/SerialArrayListAdapter.h"
#include "src/core/serialize/SerialBufferAdapter.h"
#include "src/core/serialize/SerialLinkedListAdapter.h"
#include "src/core/serialize/SerializeElement.h"
#include "fsfw/serialize/EndianConverter.h"
#include "fsfw/serialize/SerialArrayListAdapter.h"
#include "fsfw/serialize/SerialBufferAdapter.h"
#include "fsfw/serialize/SerialLinkedListAdapter.h"
#include "fsfw/serialize/SerializeElement.h"
#endif /* FSFW_INC_FSFW_SERIALIZE_H_ */

View File

@ -4,7 +4,7 @@
#include <cstdint>
#include <cstring>
#include "../osal/Endiness.h"
#include "fsfw/osal/Endiness.h"
/**
* Helper class to convert variables or bitstreams between machine
@ -36,7 +36,7 @@
*/
class EndianConverter {
private:
EndianConverter(){};
EndianConverter() = default;
public:
/**
@ -49,8 +49,8 @@ class EndianConverter {
#error BYTE_ORDER_SYSTEM not defined
#elif BYTE_ORDER_SYSTEM == LITTLE_ENDIAN
T tmp;
uint8_t *pointerOut = (uint8_t *)&tmp;
uint8_t *pointerIn = (uint8_t *)&in;
auto *pointerOut = reinterpret_cast<uint8_t *>(&tmp);
auto *pointerIn = reinterpret_cast<uint8_t *>(&in);
for (size_t count = 0; count < sizeof(T); count++) {
pointerOut[sizeof(T) - count - 1] = pointerIn[count];
}
@ -73,10 +73,8 @@ class EndianConverter {
for (size_t count = 0; count < size; count++) {
out[size - count - 1] = in[count];
}
return;
#elif BYTE_ORDER_SYSTEM == BIG_ENDIAN
memcpy(out, in, size);
return;
#endif
}
@ -90,8 +88,8 @@ class EndianConverter {
#error BYTE_ORDER_SYSTEM not defined
#elif BYTE_ORDER_SYSTEM == BIG_ENDIAN
T tmp;
uint8_t *pointerOut = (uint8_t *)&tmp;
uint8_t *pointerIn = (uint8_t *)&in;
auto *pointerOut = reinterpret_cast<uint8_t *>(&tmp);
auto *pointerIn = reinterpret_cast<uint8_t *>(&in);
for (size_t count = 0; count < sizeof(T); count++) {
pointerOut[sizeof(T) - count - 1] = pointerIn[count];
}
@ -113,10 +111,8 @@ class EndianConverter {
for (size_t count = 0; count < size; count++) {
out[size - count - 1] = in[count];
}
return;
#elif BYTE_ORDER_SYSTEM == LITTLE_ENDIAN
memcpy(out, in, size);
return;
#endif
}
};

View File

@ -3,7 +3,7 @@
#include <cstddef>
#include "../returnvalues/HasReturnvaluesIF.h"
#include "fsfw/returnvalues/HasReturnvaluesIF.h"
/**
* @defgroup serialize Serialization
@ -34,7 +34,7 @@ class SerializeIF {
static const ReturnValue_t TOO_MANY_ELEMENTS =
MAKE_RETURN_CODE(3); // !< There are too many elements to be deserialized
virtual ~SerializeIF() {}
virtual ~SerializeIF() = default;
/**
* @brief
* Function to serialize the object into a buffer with maxSize. Size represents the written
@ -59,14 +59,21 @@ class SerializeIF {
* - @c RETURN_FAILED Generic error
* - @c RETURN_OK Successful serialization
*/
virtual ReturnValue_t serialize(uint8_t **buffer, size_t *size, size_t maxSize,
Endianness streamEndianness) const = 0;
[[nodiscard]] virtual ReturnValue_t serialize(uint8_t **buffer, size_t *size, size_t maxSize,
Endianness streamEndianness) const = 0;
/**
* Forwards to regular @serialize call with big (network) endianness
*/
[[nodiscard]] virtual ReturnValue_t serializeBe(uint8_t **buffer, size_t *size,
size_t maxSize) const {
return serialize(buffer, size, maxSize, SerializeIF::Endianness::NETWORK);
}
/**
* Gets the size of a object if it would be serialized in a buffer
* @return Size of serialized object
*/
virtual size_t getSerializedSize() const = 0;
[[nodiscard]] virtual size_t getSerializedSize() const = 0;
/**
* @brief
@ -90,6 +97,57 @@ class SerializeIF {
*/
virtual ReturnValue_t deSerialize(const uint8_t **buffer, size_t *size,
Endianness streamEndianness) = 0;
/**
* Forwards to regular @deSerialize call with big (network) endianness
*/
virtual ReturnValue_t deSerializeBe(const uint8_t **buffer, size_t *size) {
return deSerialize(buffer, size, SerializeIF::Endianness::NETWORK);
}
/**
* Helper method which can be used if serialization should be performed without any additional
* pointer arithmetic on a passed buffer pointer
* @param buffer
* @param maxSize
* @param streamEndianness
* @return
*/
[[nodiscard]] virtual ReturnValue_t serialize(uint8_t *buffer, size_t &serLen, size_t maxSize,
Endianness streamEndianness) const {
size_t tmpSize = 0;
ReturnValue_t result = serialize(&buffer, &tmpSize, maxSize, streamEndianness);
serLen = tmpSize;
return result;
}
/**
* Forwards to regular @serialize call with big (network) endianness
*/
[[nodiscard]] virtual ReturnValue_t serializeBe(uint8_t *buffer, size_t &serLen,
size_t maxSize) const {
return serialize(buffer, serLen, maxSize, SerializeIF::Endianness::NETWORK);
}
/**
* Helper methods which can be used if deserialization should be performed without any additional
* pointer arithmetic on a passed buffer pointer
* @param buffer
* @param maxSize
* @param streamEndianness
* @return
*/
virtual ReturnValue_t deSerialize(const uint8_t *buffer, size_t &deserSize, size_t maxSize,
Endianness streamEndianness) {
size_t deserLen = maxSize;
ReturnValue_t result = deSerialize(&buffer, &deserLen, streamEndianness);
deserSize = maxSize - deserLen;
return result;
}
/**
* Forwards to regular @serialize call with big (network) endianness
*/
virtual ReturnValue_t deSerializeBe(const uint8_t *buffer, size_t &deserSize, size_t maxSize) {
return deSerialize(buffer, deserSize, maxSize, SerializeIF::Endianness::NETWORK);
}
};
#endif /* FSFW_SERIALIZE_SERIALIZEIF_H_ */

View File

@ -286,26 +286,22 @@ ReturnValue_t MgmLIS3MDLHandler::interpretDeviceReply(DeviceCommandId_t id, cons
PoolReadGuard readHelper(&dataset);
if (readHelper.getReadResult() == HasReturnvaluesIF::RETURN_OK) {
if (std::abs(mgmX) > absLimitX or std::abs(mgmY) > absLimitY or
std::abs(mgmZ) > absLimitZ) {
dataset.fieldStrengths.setValid(false);
}
if (std::abs(mgmX) < absLimitX) {
dataset.fieldStrengthX = mgmX;
dataset.fieldStrengthX.setValid(true);
} else {
dataset.fieldStrengthX.setValid(false);
dataset.fieldStrengths[0] = mgmX;
}
if (std::abs(mgmY) < absLimitY) {
dataset.fieldStrengthY = mgmY;
dataset.fieldStrengthY.setValid(true);
} else {
dataset.fieldStrengthY.setValid(false);
dataset.fieldStrengths[1] = mgmY;
}
if (std::abs(mgmZ) < absLimitZ) {
dataset.fieldStrengthZ = mgmZ;
dataset.fieldStrengthZ.setValid(true);
} else {
dataset.fieldStrengthZ.setValid(false);
dataset.fieldStrengths[2] = mgmZ;
}
dataset.fieldStrengths.setValid(true);
}
break;
}
@ -468,10 +464,9 @@ void MgmLIS3MDLHandler::modeChanged(void) { internalState = InternalState::STATE
ReturnValue_t MgmLIS3MDLHandler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap,
LocalDataPoolManager &poolManager) {
localDataPoolMap.emplace(MGMLIS3MDL::FIELD_STRENGTH_X, new PoolEntry<float>({0.0}));
localDataPoolMap.emplace(MGMLIS3MDL::FIELD_STRENGTH_Y, new PoolEntry<float>({0.0}));
localDataPoolMap.emplace(MGMLIS3MDL::FIELD_STRENGTH_Z, new PoolEntry<float>({0.0}));
localDataPoolMap.emplace(MGMLIS3MDL::TEMPERATURE_CELCIUS, new PoolEntry<float>({0.0}));
localDataPoolMap.emplace(MGMLIS3MDL::FIELD_STRENGTHS, &mgmXYZ);
localDataPoolMap.emplace(MGMLIS3MDL::TEMPERATURE_CELCIUS, &temperature);
poolManager.subscribeForPeriodicPacket(dataset.getSid(), false, 10.0, false);
return HasReturnvaluesIF::RETURN_OK;
}

View File

@ -103,6 +103,8 @@ class MgmLIS3MDLHandler : public DeviceHandlerBase {
CommunicationStep communicationStep = CommunicationStep::DATA;
bool commandExecuted = false;
PoolEntry<float> mgmXYZ = PoolEntry<float>(3);
PoolEntry<float> temperature = PoolEntry<float>();
/*------------------------------------------------------------------------*/
/* Device specific commands and variables */
/*------------------------------------------------------------------------*/

View File

@ -309,9 +309,8 @@ void MgmRM3100Handler::modeChanged(void) { internalState = InternalState::NONE;
ReturnValue_t MgmRM3100Handler::initializeLocalDataPool(localpool::DataPool &localDataPoolMap,
LocalDataPoolManager &poolManager) {
localDataPoolMap.emplace(RM3100::FIELD_STRENGTH_X, new PoolEntry<float>({0.0}));
localDataPoolMap.emplace(RM3100::FIELD_STRENGTH_Y, new PoolEntry<float>({0.0}));
localDataPoolMap.emplace(RM3100::FIELD_STRENGTH_Z, new PoolEntry<float>({0.0}));
localDataPoolMap.emplace(RM3100::FIELD_STRENGTHS, &mgmXYZ);
poolManager.subscribeForPeriodicPacket(primaryDataset.getSid(), false, 10.0, false);
return HasReturnvaluesIF::RETURN_OK;
}
@ -354,9 +353,9 @@ ReturnValue_t MgmRM3100Handler::handleDataReadout(const uint8_t *packet) {
// TODO: Sanity check on values?
PoolReadGuard readGuard(&primaryDataset);
if (readGuard.getReadResult() == HasReturnvaluesIF::RETURN_OK) {
primaryDataset.fieldStrengthX = fieldStrengthX;
primaryDataset.fieldStrengthY = fieldStrengthY;
primaryDataset.fieldStrengthZ = fieldStrengthZ;
primaryDataset.fieldStrengths[0] = fieldStrengthX;
primaryDataset.fieldStrengths[1] = fieldStrengthY;
primaryDataset.fieldStrengths[2] = fieldStrengthZ;
primaryDataset.setValidity(true, true);
}
return RETURN_OK;

View File

@ -85,6 +85,7 @@ class MgmRM3100Handler : public DeviceHandlerBase {
bool goToNormalModeAtStartup = false;
uint32_t transitionDelay;
PoolEntry<float> mgmXYZ = PoolEntry<float>(3);
ReturnValue_t handleCycleCountConfigCommand(DeviceCommandId_t deviceCommand,
const uint8_t *commandData, size_t commandDataLen);

View File

@ -139,12 +139,7 @@ static const uint8_t CTRL_REG5_DEFAULT = 0;
static const uint32_t MGM_DATA_SET_ID = READ_CONFIG_AND_DATA;
enum MgmPoolIds : lp_id_t {
FIELD_STRENGTH_X,
FIELD_STRENGTH_Y,
FIELD_STRENGTH_Z,
TEMPERATURE_CELCIUS
};
enum MgmPoolIds : lp_id_t { FIELD_STRENGTHS, TEMPERATURE_CELCIUS };
class MgmPrimaryDataset : public StaticLocalDataSet<4> {
public:
@ -152,9 +147,10 @@ class MgmPrimaryDataset : public StaticLocalDataSet<4> {
MgmPrimaryDataset(object_id_t mgmId) : StaticLocalDataSet(sid_t(mgmId, MGM_DATA_SET_ID)) {}
lp_var_t<float> fieldStrengthX = lp_var_t<float>(sid.objectId, FIELD_STRENGTH_X, this);
lp_var_t<float> fieldStrengthY = lp_var_t<float>(sid.objectId, FIELD_STRENGTH_Y, this);
lp_var_t<float> fieldStrengthZ = lp_var_t<float>(sid.objectId, FIELD_STRENGTH_Z, this);
/**
* Field strenghts in uT
*/
lp_vec_t<float, 3> fieldStrengths = lp_vec_t<float, 3>(sid.objectId, FIELD_STRENGTHS, this);
lp_var_t<float> temperature = lp_var_t<float>(sid.objectId, TEMPERATURE_CELCIUS, this);
};

View File

@ -101,11 +101,7 @@ class CycleCountCommand : public SerialLinkedListAdapter<SerializeIF> {
static constexpr uint32_t MGM_DATASET_ID = READ_DATA;
enum MgmPoolIds : lp_id_t {
FIELD_STRENGTH_X,
FIELD_STRENGTH_Y,
FIELD_STRENGTH_Z,
};
enum MgmPoolIds : lp_id_t { FIELD_STRENGTHS };
class Rm3100PrimaryDataset : public StaticLocalDataSet<3> {
public:
@ -113,10 +109,10 @@ class Rm3100PrimaryDataset : public StaticLocalDataSet<3> {
Rm3100PrimaryDataset(object_id_t mgmId) : StaticLocalDataSet(sid_t(mgmId, MGM_DATASET_ID)) {}
// Field strengths in micro Tesla.
lp_var_t<float> fieldStrengthX = lp_var_t<float>(sid.objectId, FIELD_STRENGTH_X, this);
lp_var_t<float> fieldStrengthY = lp_var_t<float>(sid.objectId, FIELD_STRENGTH_Y, this);
lp_var_t<float> fieldStrengthZ = lp_var_t<float>(sid.objectId, FIELD_STRENGTH_Z, this);
/**
* Field strenghts in uT
*/
lp_vec_t<float, 3> fieldStrengths = lp_vec_t<float, 3>(sid.objectId, FIELD_STRENGTHS, this);
};
} // namespace RM3100

View File

@ -0,0 +1,40 @@
#ifndef FSFW_TESTS_SIMPLESERIALIZABLE_H
#define FSFW_TESTS_SIMPLESERIALIZABLE_H
#include "fsfw/serialize.h"
#include "fsfw/osal/Endiness.h"
class SimpleSerializable : public SerializeIF {
public:
ReturnValue_t serialize(uint8_t **buffer, size_t *size, size_t maxSize,
Endianness streamEndianness) const override {
if (*size + getSerializedSize() > maxSize) {
return SerializeIF::BUFFER_TOO_SHORT;
}
**buffer = someU8;
*buffer += 1;
*size += 1;
return SerializeAdapter::serialize(&someU16, buffer, size, maxSize, streamEndianness);
}
[[nodiscard]] size_t getSerializedSize() const override { return 3; }
ReturnValue_t deSerialize(const uint8_t **buffer, size_t *size,
Endianness streamEndianness) override {
if (*size < getSerializedSize()) {
return SerializeIF::STREAM_TOO_SHORT;
}
someU8 = **buffer;
*size -= 1;
*buffer += 1;
return SerializeAdapter::deSerialize(&someU16, buffer, size, streamEndianness);
}
[[nodiscard]] uint8_t getU8() const { return someU8; }
[[nodiscard]] uint16_t getU16() const { return someU16; }
private:
uint8_t someU8 = 1;
uint16_t someU16 = 0x0203;
};
#endif // FSFW_TESTS_SIMPLESERIALIZABLE_H

View File

@ -1,5 +1,6 @@
target_sources(${FSFW_TEST_TGT} PRIVATE
TestSerialBufferAdapter.cpp
TestSerialization.cpp
TestSerialLinkedPacket.cpp
testSerialBufferAdapter.cpp
testSerializeAdapter.cpp
testSerialLinkedPacket.cpp
testSerializeIF.cpp
)

View File

@ -26,13 +26,13 @@ class TestPacket : public SerialLinkedListAdapter<SerializeIF> {
setLinks();
}
uint32_t getHeader() const { return header.entry; }
[[nodiscard]] uint32_t getHeader() const { return header.entry; }
const uint8_t* getBuffer() { return buffer.entry.getConstBuffer(); }
[[nodiscard]] const uint8_t* getBuffer() const { return buffer.entry.getConstBuffer(); }
size_t getBufferLength() { return buffer.getSerializedSize(); }
uint16_t getTail() const { return tail.entry; }
[[nodiscard]] uint16_t getTail() const { return tail.entry; }
private:
void setLinks() {
@ -47,4 +47,4 @@ class TestPacket : public SerialLinkedListAdapter<SerializeIF> {
SerializeElement<uint32_t> tail = 0;
};
#endif /* UNITTEST_TESTFW_NEWTESTS_TESTTEMPLATE_H_ */
#endif /* UNITTEST_HOSTED_TESTSERIALLINKEDPACKET_H_ */

View File

@ -92,7 +92,7 @@ TEST_CASE("Serial Buffer Adapter", "[single-file]") {
testArray[3] = 1;
testArray[4] = 1;
testArray[5] = 0;
std::array<uint8_t, 4> test_recv_array;
std::array<uint8_t, 4> test_recv_array{};
arrayPtr = testArray.data();
// copy testArray[1] to testArray[4] into receive buffer, skip
// size field (testArray[0]) for deSerialization.
@ -116,7 +116,7 @@ TEST_CASE("Serial Buffer Adapter", "[single-file]") {
testArray[3] = 1;
testArray[4] = 1;
testArray[5] = 0;
std::array<uint8_t, 4> test_recv_array;
std::array<uint8_t, 4> test_recv_array{};
arrayPtr = testArray.data() + 2;
// copy testArray[1] to testArray[4] into receive buffer, skip
// size field (testArray[0])

View File

@ -1,11 +1,10 @@
#include "TestSerialLinkedPacket.h"
#include <fsfw/globalfunctions/arrayprinter.h>
#include <array>
#include <catch2/catch_test_macros.hpp>
#include "CatchDefinitions.h"
#include "SerialLinkedListAdapterPacket.h"
TEST_CASE("Serial Linked Packet", "[SerLinkPacket]") {
// perform set-up here

View File

@ -0,0 +1,144 @@
#include <array>
#include <catch2/catch_test_macros.hpp>
#include "mocks/SimpleSerializable.h"
using namespace std;
TEST_CASE("Serialize IF Serialize", "[serialize-if-ser]") {
auto simpleSer = SimpleSerializable();
array<uint8_t, 16> buf{};
uint8_t* ptr = buf.data();
size_t len = 0;
SECTION("Little Endian Normal") {
REQUIRE(simpleSer.serialize(&ptr, &len, buf.size(), SerializeIF::Endianness::LITTLE) ==
HasReturnvaluesIF::RETURN_OK);
CHECK(buf[0] == 1);
CHECK(buf[1] == 3);
CHECK(buf[2] == 2);
// Verify pointer arithmetic and size increment
CHECK(ptr == buf.data() + 3);
CHECK(len == 3);
}
SECTION("Little Endian Simple") {
size_t serLen = 0xff;
REQUIRE(simpleSer.SerializeIF::serialize(buf.data(), serLen, buf.size(),
SerializeIF::Endianness::LITTLE) ==
HasReturnvaluesIF::RETURN_OK);
CHECK(buf[0] == 1);
CHECK(buf[1] == 3);
CHECK(buf[2] == 2);
CHECK(serLen == 3);
}
SECTION("Big Endian Normal") {
SECTION("Explicit") {
REQUIRE(simpleSer.serialize(&ptr, &len, buf.size(), SerializeIF::Endianness::BIG) ==
HasReturnvaluesIF::RETURN_OK);
}
SECTION("Network 0") {
REQUIRE(simpleSer.serialize(&ptr, &len, buf.size(), SerializeIF::Endianness::NETWORK) ==
HasReturnvaluesIF::RETURN_OK);
}
SECTION("Network 1") {
REQUIRE(simpleSer.serializeBe(&ptr, &len, buf.size()) == HasReturnvaluesIF::RETURN_OK);
}
CHECK(buf[0] == 1);
CHECK(buf[1] == 2);
CHECK(buf[2] == 3);
// Verify pointer arithmetic and size increment
CHECK(ptr == buf.data() + 3);
CHECK(len == 3);
}
SECTION("Big Endian Simple") {
size_t serLen = 0xff;
SECTION("Explicit") {
REQUIRE(simpleSer.SerializeIF::serialize(buf.data(), serLen, buf.size(),
SerializeIF::Endianness::BIG) ==
HasReturnvaluesIF::RETURN_OK);
}
SECTION("Network 0") {
REQUIRE(simpleSer.SerializeIF::serialize(buf.data(), serLen, buf.size(),
SerializeIF::Endianness::NETWORK) ==
HasReturnvaluesIF::RETURN_OK);
}
SECTION("Network 1") {
REQUIRE(simpleSer.SerializeIF::serializeBe(buf.data(), serLen, buf.size()) ==
HasReturnvaluesIF::RETURN_OK);
}
CHECK(buf[0] == 1);
CHECK(buf[1] == 2);
CHECK(buf[2] == 3);
CHECK(serLen == 3);
}
}
TEST_CASE("SerializeIF Deserialize", "[serialize-if-de]") {
auto simpleSer = SimpleSerializable();
array<uint8_t, 3> buf = {5, 0, 1};
const uint8_t* ptr = buf.data();
size_t len = buf.size();
SECTION("Little Endian Normal") {
REQUIRE(simpleSer.deSerialize(&ptr, &len, SerializeIF::Endianness::LITTLE) ==
HasReturnvaluesIF::RETURN_OK);
CHECK(simpleSer.getU8() == 5);
CHECK(simpleSer.getU16() == 0x0100);
CHECK(ptr == buf.data() + 3);
CHECK(len == 0);
}
SECTION("Little Endian Simple") {
size_t deserLen = 0xff;
REQUIRE(
simpleSer.SerializeIF::deSerialize(ptr, deserLen, len, SerializeIF::Endianness::LITTLE) ==
HasReturnvaluesIF::RETURN_OK);
CHECK(simpleSer.getU8() == 5);
CHECK(simpleSer.getU16() == 0x0100);
CHECK(deserLen == 3);
}
SECTION("Big Endian Normal") {
SECTION("Explicit") {
REQUIRE(simpleSer.deSerialize(&ptr, &len, SerializeIF::Endianness::BIG) ==
HasReturnvaluesIF::RETURN_OK);
}
SECTION("Network 0") {
REQUIRE(simpleSer.deSerialize(&ptr, &len, SerializeIF::Endianness::NETWORK) ==
HasReturnvaluesIF::RETURN_OK);
}
SECTION("Network 1") {
REQUIRE(simpleSer.SerializeIF::deSerializeBe(&ptr, &len) == HasReturnvaluesIF::RETURN_OK);
}
CHECK(simpleSer.getU8() == 5);
CHECK(simpleSer.getU16() == 1);
CHECK(ptr == buf.data() + 3);
CHECK(len == 0);
}
SECTION("Big Endian Simple") {
size_t deserLen = 0xff;
SECTION("Explicit") {
REQUIRE(simpleSer.SerializeIF::deSerialize(buf.data(), deserLen, buf.size(),
SerializeIF::Endianness::BIG) ==
HasReturnvaluesIF::RETURN_OK);
}
SECTION("Network 0") {
REQUIRE(simpleSer.SerializeIF::deSerialize(buf.data(), deserLen, buf.size(),
SerializeIF::Endianness::NETWORK) ==
HasReturnvaluesIF::RETURN_OK);
}
SECTION("Network 1") {
REQUIRE(simpleSer.SerializeIF::deSerializeBe(buf.data(), deserLen, buf.size()) ==
HasReturnvaluesIF::RETURN_OK);
}
CHECK(simpleSer.getU8() == 5);
CHECK(simpleSer.getU16() == 1);
CHECK(deserLen == 3);
}
}