Commit e7725ad2 authored by Jihun Ha's avatar Jihun Ha Committed by Madan Lanka

Add unittest code for Enrollee

Using Hippomock, unittest codes for enrollee have been implemented.

Change-Id: I1c64a97aae20e0b27c605e7a78bd6a0ada14c6b9
Signed-off-by: default avatarJihun Ha <jihun.ha@samsung.com>
Reviewed-on: https://gerrit.iotivity.org/gerrit/9923Tested-by: default avatarjenkins-iotivity <jenkins-iotivity@opendaylight.org>
Reviewed-by: default avatarMadan Lanka <lanka.madan@samsung.com>
parent f6651b90
......@@ -43,16 +43,14 @@ enrollee_env.PrependUnique(CPPPATH = [
enrollee_env.get('SRC_DIR') + '/resource/include',
enrollee_env.get('SRC_DIR') + '/resource/csdk/logger/include',
enrollee_env.get('SRC_DIR') + '/resource/oc_logger/include',
enrollee_env.get('SRC_DIR') + '/resource/csdk/stack/include',
enrollee_env.get('SRC_DIR') + '/resource/csdk/logger/include',
enrollee_env.get('SRC_DIR') + '/resource/csdk/stack/include',
enrollee_env.get('SRC_DIR') + '/resource/csdk/logger/include',
enrollee_env.get('SRC_DIR') + '/resource/csdk/security/include',
enrollee_env.get('SRC_DIR') + '/extlibs/cjson',
enrollee_env.get('SRC_DIR') + '/extlibs/sqlite3',
enrollee_env.get('SRC_DIR') + '/service/easy-setup/inc',
enrollee_env.get('SRC_DIR') + '/service/easy-setup/enrollee/linux/wifi',
enrollee_env.get('SRC_DIR') + '/service/easy-setup/enrollee/inc',
enrollee_env.get('SRC_DIR') + '/service/easy-setup/enrollee/src',
enrollee_env.get('SRC_DIR') + '/service/easy-setup/enrollee/arduino'])
enrollee_env.get('SRC_DIR') + '/service/easy-setup/enrollee/src'])
if enrollee_env.get('SECURED') == '1':
enrollee_env.AppendUnique(CPPPATH = [
......@@ -63,13 +61,14 @@ if enrollee_env.get('SECURED') == '1':
if target_os not in ['windows']:
enrollee_env.AppendUnique(CXXFLAGS = ['-Wall', '-std=c++0x'])
if not env.get('RELEASE'):
enrollee_env.PrependUnique(LIBS = ['gcov'])
enrollee_env.AppendUnique(CCFLAGS = ['--coverage'])
######################################################################
# Linux Enrollee
######################################################################
if target_os in ['linux']:
enrollee_env.PrependUnique(CPPPATH = [
enrollee_env.get('SRC_DIR') + '/service/easy-setup/enrollee/linux/wifi'])
enrollee_env.AppendUnique(LIBS = ['pthread', 'dl'])
enrollee_env.AppendUnique(LIBPATH = [enrollee_env.get('BUILD_DIR')])
enrollee_env.AppendUnique(RPATH = [enrollee_env.get('BUILD_DIR')])
......@@ -95,41 +94,23 @@ if target_os == 'arduino':
enrollee_env.AppendUnique(LIBPATH = [enrollee_env.get('BUILD_DIR')])
enrollee_env.AppendUnique(RPATH = [enrollee_env.get('BUILD_DIR')])
enrollee_env.PrependUnique(LIBS = ['oc', 'octbstack', 'oc_logger', 'pthread', 'connectivity_abstraction'])
enrollee_env.AppendUnique(CPPPATH = [
enrollee_env.get('SRC_DIR') + '/service/easy-setup/enrollee/arduino/wifi'])
######################################################################
# Source files and Targets
######################################################################
es_enrollee_src = None
if target_os == 'linux':
es_enrollee_common_src = [enrollee_env.get('SRC_DIR') + '/service/easy-setup/enrollee/src/easysetup.c',
enrollee_env.get('SRC_DIR') + '/service/easy-setup/enrollee/src/resourcehandler.c'
]
enrollee_env.AppendUnique(es_enrollee_src = es_enrollee_common_src)
enrollee_sdk_shared = enrollee_env.SharedLibrary('ESEnrolleeSDK', enrollee_env.get('es_enrollee_src'))
if target_os in ['linux', 'tizen']:
es_enrollee_common_src = ['./src/easysetup.c','./src/resourcehandler.c']
enrollee_sdk_shared = enrollee_env.SharedLibrary('ESEnrolleeSDK', es_enrollee_common_src)
enrollee_env.InstallTarget(enrollee_sdk_shared, 'libESEnrollee')
if target_os == 'tizen':
es_enrollee_common_src = [enrollee_env.get('SRC_DIR') + '/service/easy-setup/enrollee/src/easysetup.c',
enrollee_env.get('SRC_DIR') + '/service/easy-setup/enrollee/src/resourcehandler.c'
]
enrollee_env.AppendUnique(es_enrollee_src = es_enrollee_common_src)
enrollee_sdk_shared = enrollee_env.SharedLibrary('ESEnrolleeSDK', enrollee_env.get('es_enrollee_src'))
enrollee_env.InstallTarget(enrollee_sdk_shared, 'libESEnrollee')
enrollee_env.UserInstallTargetLib(enrollee_sdk_shared, 'libESEnrolleeSDK')
enrollee_env.UserInstallTargetLib(enrollee_sdk_shared, 'libESEnrollee')
if target_os == 'arduino':
es_sdk_static = enrollee_env.StaticLibrary('ESEnrolleeSDK', [
enrollee_env.get('SRC_DIR') + '/service/easy-setup/enrollee/src/easysetup.c',
enrollee_env.get('SRC_DIR') + '/service/easy-setup/enrollee/src/resourcehandler.c'
])
enrollee_env.InstallTarget(es_sdk_static, 'libESEnrolleeSDK')
es_enrollee_common_src = ['./src/easysetup.c','./src/resourcehandler.c']
enrollee_sdk_static = enrollee_env.StaticLibrary('ESEnrolleeSDK', es_enrollee_common_src)
enrollee_env.InstallTarget(enrollee_sdk_static, 'libESEnrolleeSDK')
enrollee_env.UserInstallTargetLib(enrollee_sdk_static, 'libESEnrollee')
#Go to build sample apps
#if target_os == 'arduino':
......@@ -138,5 +119,5 @@ if target_os == 'arduino':
if target_os in ['linux']:
SConscript('../sampleapp/enrollee/linux/SConscript')
#Build UnitTestcases for Enrollee
# SConscript('../enrollee/unittests/SConscript')
SConscript('../enrollee/unittests/SConscript')
......@@ -25,8 +25,6 @@
#include "escommon.h"
#include "ESEnrolleeCommon.h"
#include "ocpayload.h"
/**
* @file
*
......
......@@ -142,7 +142,7 @@ ESResult ESInitEnrollee(bool isSecured, ESResourceMask resourceMask, ESProvision
}
if((resourceMask & ES_CLOUD_RESOURCE) == ES_CLOUD_RESOURCE)
{
if(callbacks.DevConfProvCb != NULL)
if(callbacks.CloudDataProvCb != NULL)
{
gESProvisioningCb.CloudDataProvCb = callbacks.CloudDataProvCb;
RegisterCloudRsrcEventCallBack(ESCloudRsrcCallback);
......
......@@ -48,7 +48,6 @@ static DevConfResource gDevConfResource;
//-----------------------------------------------------------------------------
OCEntityHandlerResult OCEntityHandlerCb(OCEntityHandlerFlag flag, OCEntityHandlerRequest *ehRequest,
void *callback);
const char *getResult(OCStackResult result);
OCEntityHandlerResult ProcessGetRequest(OCEntityHandlerRequest *ehRequest, OCRepPayload** payload);
OCEntityHandlerResult ProcessPutRequest(OCEntityHandlerRequest *ehRequest, OCRepPayload** payload);
OCEntityHandlerResult ProcessPostRequest(OCEntityHandlerRequest *ehRequest, OCRepPayload** payload);
......@@ -56,6 +55,7 @@ void updateProvResource(OCEntityHandlerRequest* ehRequest, OCRepPayload* input);
void updateWiFiResource(OCRepPayload* input);
void updateCloudResource(OCRepPayload* input);
void updateDevConfResource(OCRepPayload* input);
const char *getResult(OCStackResult result);
ESWiFiCB gWifiRsrcEvtCb = NULL;
ESCloudCB gCloudRsrcEvtCb = NULL;
......@@ -145,13 +145,13 @@ OCStackResult initProvResource(bool isSecured)
res = OCBindResourceInterfaceToResource(gProvResource.handle, OC_RSRVD_INTERFACE_LL);
if(res)
{
OIC_LOG_V(INFO, ES_RH_TAG, "Created Prov resource with result: %s", getResult(res));
OIC_LOG_V(INFO, ES_RH_TAG, "Binding Resource interface with result: %s", getResult(res));
return res;
}
res = OCBindResourceInterfaceToResource(gProvResource.handle, OC_RSRVD_INTERFACE_BATCH);
if(res)
{
OIC_LOG_V(INFO, ES_RH_TAG, "Created Prov resource with result: %s", getResult(res));
OIC_LOG_V(INFO, ES_RH_TAG, "Binding Resource interface with result: %s", getResult(res));
return res;
}
......@@ -305,7 +305,9 @@ void updateWiFiResource(OCRepPayload* input)
}
if(gReadUserdataCb)
{
gReadUserdataCb(input, OC_RSRVD_ES_RES_TYPE_WIFI, wiFiData->userdata);
}
if(ssid || cred || authType!= -1 || encType != -1)
{
......@@ -359,7 +361,9 @@ void updateCloudResource(OCRepPayload* input)
}
if(gReadUserdataCb)
{
gReadUserdataCb(input, OC_RSRVD_ES_RES_TYPE_CLOUDSERVER, cloudData->userdata);
}
if(authCode || authProvider || ciServer)
{
......@@ -405,7 +409,9 @@ void updateDevConfResource(OCRepPayload* input)
}
if(gReadUserdataCb)
{
gReadUserdataCb(input, OC_RSRVD_ES_RES_TYPE_DEVCONF, devConfData->userdata);
}
if(country || language)
{
......@@ -450,7 +456,9 @@ OCRepPayload* constructResponseOfWiFi()
OCRepPayloadSetPropInt(payload, OC_RSRVD_ES_ENCTYPE, (int) gWiFiResource.encType);
if(gWriteUserdataCb)
{
gWriteUserdataCb(payload, OC_RSRVD_ES_RES_TYPE_WIFI);
}
return payload;
}
......@@ -492,7 +500,9 @@ OCRepPayload* constructResponseOfDevConf()
OCRepPayloadSetPropString(payload, OC_RSRVD_ES_COUNTRY, gDevConfResource.country);
if(gWriteUserdataCb)
{
gWriteUserdataCb(payload, OC_RSRVD_ES_RES_TYPE_DEVCONF);
}
return payload;
}
......@@ -513,7 +523,9 @@ OCRepPayload* constructResponseOfProv(OCEntityHandlerRequest *ehRequest)
OCRepPayloadSetPropString(payload, OC_RSRVD_ES_LINKS, gProvResource.ocfWebLinks);
if(gWriteUserdataCb)
{
gWriteUserdataCb(payload, OC_RSRVD_ES_RES_TYPE_PROV);
}
if(ehRequest->query)
{
......@@ -674,13 +686,21 @@ OCEntityHandlerResult ProcessGetRequest(OCEntityHandlerRequest *ehRequest, OCRep
OCRepPayload *getResp = NULL;
if(ehRequest->resource == gProvResource.handle)
{
getResp = constructResponseOfProv(ehRequest);
}
else if(ehRequest->resource == gWiFiResource.handle)
{
getResp = constructResponseOfWiFi();
}
else if(ehRequest->resource == gCloudResource.handle)
{
getResp = constructResponseOfCloud();
}
else if(ehRequest->resource == gDevConfResource.handle)
{
getResp = constructResponseOfDevConf();
}
if (!getResp)
{
......@@ -711,28 +731,40 @@ OCEntityHandlerResult ProcessPostRequest(OCEntityHandlerRequest *ehRequest, OCRe
return ehResult;
}
// TBD : Discuss about triggering flag (to be existed or not)
// ES_PS_PROVISIONING_COMPLETED state indicates that already provisioning is completed.
// A new request for provisioning means overriding existing network provisioning information.
if(ehRequest->resource == gProvResource.handle)
{
updateProvResource(ehRequest, input);
}
else if(ehRequest->resource == gWiFiResource.handle)
{
updateWiFiResource(input);
}
else if(ehRequest->resource == gCloudResource.handle)
{
updateCloudResource(input);
}
else if(ehRequest->resource == gDevConfResource.handle)
{
updateDevConfResource(input);
}
OCRepPayload *getResp = NULL;
if(ehRequest->resource == gProvResource.handle)
{
getResp = constructResponseOfProv(ehRequest);
}
else if(ehRequest->resource == gWiFiResource.handle)
{
getResp = constructResponseOfWiFi();
}
else if(ehRequest->resource == gCloudResource.handle)
{
getResp = constructResponseOfCloud();
}
else if(ehRequest->resource == gDevConfResource.handle)
{
getResp = constructResponseOfDevConf();
}
if (!getResp)
{
......@@ -877,6 +909,7 @@ OCStackResult SetEnrolleeErrCode(ESErrorCode esErrCode)
OIC_LOG(INFO, ES_RH_TAG, "SetEnrolleeErrCode OUT");
return OC_STACK_OK;
}
const char *getResult(OCStackResult result)
{
switch (result)
......@@ -916,5 +949,4 @@ const char *getResult(OCStackResult result)
default:
return "UNKNOWN";
}
}
}
\ No newline at end of file
//******************************************************************
//
// Copyright 2016 Samsung Electronics All Rights Reserved.
//
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#include <gtest/gtest.h>
#include <HippoMocks/hippomocks.h>
#include <atomic>
#include <functional>
#include <condition_variable>
#include <mutex>
#include <chrono>
#include "ESMediatorSimulator.h"
#include "easysetup.h"
using namespace OC;
namespace
{
std::atomic_bool g_isStartedStack(false);
std::chrono::milliseconds g_waitForResponse(1000);
std::condition_variable responseCon;
std::mutex mutexForCondition;
ESMediatorSimulator g_mediatorSimul;
}
class TestWithMock: public testing::Test
{
public:
MockRepository mocks;
protected:
virtual ~TestWithMock() noexcept(noexcept(std::declval<Test>().~Test())) {}
virtual void TearDown() {
try
{
mocks.VerifyAll();
}
catch (...)
{
mocks.reset();
throw;
}
}
};
class EasysetupEnrolleeTest : public TestWithMock
{
public:
EasysetupEnrolleeTest() = default;
~EasysetupEnrolleeTest() = default;
static void discoveredResource(std::shared_ptr<OC::OCResource>)
{
std::cout << __func__ << std::endl;
}
static void onGetStatus(std::shared_ptr< GetEnrolleeStatus >)
{
std::cout << __func__ << std::endl;
}
static void onGetConfiguration(std::shared_ptr< GetConfigurationStatus >)
{
std::cout << __func__ << std::endl;
}
static void deviceProvisioningStatusCallback(std::shared_ptr< DevicePropProvisioningStatus >)
{
std::cout << __func__ << std::endl;
}
static void cloudProvisioningStatusCallback(std::shared_ptr< CloudPropProvisioningStatus >)
{
std::cout << __func__ << std::endl;
}
static void WiFiProvCbInApp(ESWiFiProvData *)
{
std::cout << __func__ << std::endl;
}
static void DevConfProvCbInApp(ESDevConfProvData *)
{
std::cout << __func__ << std::endl;
}
static void CloudDataCbInApp(ESCloudProvData *)
{
std::cout << __func__ << std::endl;
}
ESResult startEnrollee()
{
ESResourceMask resourcemMask = (ESResourceMask)(ES_WIFI_RESOURCE |
ES_CLOUD_RESOURCE |
ES_DEVCONF_RESOURCE);
ESProvisioningCallbacks callbacks;
callbacks.WiFiProvCb = &EasysetupEnrolleeTest::WiFiProvCbInApp;
callbacks.DevConfProvCb = &EasysetupEnrolleeTest::DevConfProvCbInApp;
callbacks.CloudDataProvCb = &EasysetupEnrolleeTest::CloudDataCbInApp;
return ESInitEnrollee(false, resourcemMask, callbacks);
}
ESResult setDeviceProperty()
{
ESDeviceProperty deviceProperty = {
{{WIFI_11G, WiFi_EOF}, WIFI_5G}, {"Test Device"}
};
return ESSetDeviceProperty(&deviceProperty);
}
protected:
void SetUp()
{
TestWithMock::SetUp();
if (g_isStartedStack == false)
{
if (OCInit(NULL, 0, OC_SERVER) != OC_STACK_OK)
{
printf("OCStack init error!!\n");
return;
}
g_isStartedStack = true;
}
}
void TearDown()
{
TestWithMock::TearDown();
}
};
TEST_F(EasysetupEnrolleeTest, ESInitEnrolleeSuccess)
{
ESResult ret = startEnrollee();
EXPECT_EQ(ret, ES_OK);
ESTerminateEnrollee();
}
TEST_F(EasysetupEnrolleeTest, ESInitEnrolleeFailedByWiFiCbIsNull)
{
ESResourceMask resourcemMask = (ESResourceMask)(ES_WIFI_RESOURCE |
ES_CLOUD_RESOURCE |
ES_DEVCONF_RESOURCE);
ESProvisioningCallbacks callbacks;
callbacks.WiFiProvCb = NULL;
callbacks.DevConfProvCb = &EasysetupEnrolleeTest::DevConfProvCbInApp;
callbacks.CloudDataProvCb = &EasysetupEnrolleeTest::CloudDataCbInApp;
ESResult ret = ESInitEnrollee(false, resourcemMask, callbacks);
EXPECT_EQ(ret, ES_ERROR);
ESTerminateEnrollee();
}
TEST_F(EasysetupEnrolleeTest, ESInitEnrolleeFailedByDevConfCbIsNull)
{
ESResourceMask resourcemMask = (ESResourceMask)(ES_WIFI_RESOURCE |
ES_CLOUD_RESOURCE |
ES_DEVCONF_RESOURCE);
ESProvisioningCallbacks callbacks;
callbacks.WiFiProvCb = &EasysetupEnrolleeTest::WiFiProvCbInApp;
callbacks.DevConfProvCb = NULL;
callbacks.CloudDataProvCb = &EasysetupEnrolleeTest::CloudDataCbInApp;
ESResult ret = ESInitEnrollee(false, resourcemMask, callbacks);
EXPECT_EQ(ret, ES_ERROR);
ESTerminateEnrollee();
}
TEST_F(EasysetupEnrolleeTest, ESInitEnrolleeFailedByCloudCbIsNull)
{
ESResourceMask resourcemMask = (ESResourceMask)(ES_WIFI_RESOURCE |
ES_CLOUD_RESOURCE |
ES_DEVCONF_RESOURCE);
ESProvisioningCallbacks callbacks;
callbacks.WiFiProvCb = &EasysetupEnrolleeTest::WiFiProvCbInApp;
callbacks.DevConfProvCb = &EasysetupEnrolleeTest::DevConfProvCbInApp;
callbacks.CloudDataProvCb = NULL;
ESResult ret = ESInitEnrollee(false, resourcemMask, callbacks);
EXPECT_EQ(ret, ES_ERROR);
ESTerminateEnrollee();
}
TEST_F(EasysetupEnrolleeTest, FindProvisioningResourceAtEnrolleeWithSuccess)
{
mocks.ExpectCallFunc(discoveredResource).Do(
[](std::shared_ptr<OC::OCResource> resource)
{
try
{
std::cout<<"DISCOVERED Resource:"<<std::endl;
// Get the resource host address
std::string uri = resource->uri();
std::cout << "\tURI of the resource: " << uri << std::endl;
} catch (OCException &e)
{
std::cout << e.reason() << std::endl;
}
});
ESResult ret = startEnrollee();
g_mediatorSimul.discoverRemoteEnrollee(discoveredResource);
std::unique_lock< std::mutex > lock{ mutexForCondition };
responseCon.wait_for(lock, g_waitForResponse);
EXPECT_EQ(ret, ES_OK);
ESTerminateEnrollee();
}
TEST_F(EasysetupEnrolleeTest, SetDevicePropertyWithSuccess)
{
ESResult ret = startEnrollee();
ret = setDeviceProperty();
EXPECT_EQ(ret, ES_OK);
ESTerminateEnrollee();
}
TEST_F(EasysetupEnrolleeTest, SetProvStatusWithSuccess)
{
ESResult ret = startEnrollee();
ret = ESSetState(ES_STATE_CONNECTED_TO_ENROLLER);
EXPECT_EQ(ret, ES_OK);
ESTerminateEnrollee();
}
TEST_F(EasysetupEnrolleeTest, SetErrorCodeWithSuccess)
{
ESResult ret = startEnrollee();
ret = ESSetErrorCode(ES_ERRCODE_PW_WRONG);
EXPECT_EQ(ret, ES_OK);
ESTerminateEnrollee();
}
TEST_F(EasysetupEnrolleeTest, DevicePropertyIsWellConstructedInResponsePayload)
{
bool isWellConstructed = false;
mocks.ExpectCallFunc(onGetConfiguration).Do(
[& isWellConstructed](std::shared_ptr< GetConfigurationStatus > status)
{
if(status->getESResult() == ES_OK)
{
EnrolleeConf conf = status->getEnrolleeConf();
if(conf.getWiFiModes().at(0) == WIFI_11G &&
conf.getWiFiFreq() == WIFI_5G &&
!strcmp(conf.getDeviceName().c_str(), "Test Device"))
{
isWellConstructed = true;
}
}
});
ESResult ret = startEnrollee();
ret = setDeviceProperty();
g_mediatorSimul.getConfiguration(onGetConfiguration);
std::unique_lock< std::mutex > lock{ mutexForCondition };
responseCon.wait_for(lock, g_waitForResponse);
EXPECT_EQ(ret, ES_OK);
ESTerminateEnrollee();
}
TEST_F(EasysetupEnrolleeTest, ProvisioningPropertiesIsWellConstructedInResponsePayload)
{
bool isWellConstructed = false;
mocks.ExpectCallFunc(onGetStatus).Do(
[& isWellConstructed](std::shared_ptr< GetEnrolleeStatus > status)
{
if(status->getESResult() == ES_OK)
{
EnrolleeStatus enrolleeStatus = status->getEnrolleeStatus();
if(enrolleeStatus.getProvStatus() == ES_STATE_CONNECTED_TO_ENROLLER &&
enrolleeStatus.getLastErrCode() == ES_ERRCODE_NO_INTERNETCONNECTION)
isWellConstructed = true;
}
});
ESResult ret = startEnrollee();
ret = setDeviceProperty();
ret = ESSetState(ES_STATE_CONNECTED_TO_ENROLLER);
ret = ESSetErrorCode(ES_ERRCODE_NO_INTERNETCONNECTION);
g_mediatorSimul.getStatus(onGetStatus);
std::unique_lock< std::mutex > lock{ mutexForCondition };
responseCon.wait_for(lock, g_waitForResponse);
EXPECT_EQ(ret, ES_OK);
ESTerminateEnrollee();
}
TEST_F(EasysetupEnrolleeTest, WiFiAndDevConfProperiesProvisionedWithSuccess)
{
int cntForReceivedCallbackWithSuccess = 0;
mocks.OnCallFunc(deviceProvisioningStatusCallback).Do(
[& cntForReceivedCallbackWithSuccess](std::shared_ptr< DevicePropProvisioningStatus > status)
{
if(status->getESResult() == ES_OK)
cntForReceivedCallbackWithSuccess++;
});
mocks.OnCallFunc(WiFiProvCbInApp).Do(
[& cntForReceivedCallbackWithSuccess](ESWiFiProvData *data)
{
if(!strcmp(data->ssid, "Iotivity_SSID") &&
!strcmp(data->pwd, "Iotivity_PWD") &&
data->authtype == WPA2_PSK &&