Add missing files to securitydialogs/Autolock/indicatorplugin directory (Bug 3653)
/*
* Copyright (c) 2000, 2004, 2010 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: This file contains the implementation of CCrCrypto class.
*
*/
// INCLUDE FILES
#include "crcrypto.h"
#include <e32def.h> // REINTERPRET_CAST
#include "crdata.h"
#include <bigint.h> // Big integer.
#include <hash.h>
#include <symmetric.h>
// -----------------------------------------------------------------------------
// CCrCrypto
// Constructor.
// -----------------------------------------------------------------------------
CCrCrypto::CCrCrypto()
: iAlgorithmInfos(0)
{
}
// -----------------------------------------------------------------------------
// ~CCrCrypto
// Destructor.
// -----------------------------------------------------------------------------
CCrCrypto::~CCrCrypto()
{
if( iAlgorithmInfos )
{
Reset();
iAlgorithmInfos->Reset();
delete iAlgorithmInfos;
iAlgorithmInfos = NULL;
}
}
// -----------------------------------------------------------------------------
// CCrCrypto::ConstructL
// This function initializes this object's members.
// -----------------------------------------------------------------------------
void CCrCrypto::ConstructL()
{
iAlgorithmInfos = new (ELeave) CArrayPtrFlat<CCrAlgInfo>(1);
}
// -----------------------------------------------------------------------------
// CCrCrypto::NewLC
// -----------------------------------------------------------------------------
CCrCrypto* CCrCrypto::NewLC()
{
CCrCrypto* self = new (ELeave) CCrCrypto();
CleanupStack::PushL(self);
self->ConstructL();
return self;
}
// -----------------------------------------------------------------------------
// CCrCrypto::NewL
// -----------------------------------------------------------------------------
CCrCrypto* CCrCrypto::NewL()
{
CCrCrypto* self = NewLC();
CleanupStack::Pop();
return self;
}
// -----------------------------------------------------------------------------
// CCrCrypto::Reset
// Reset all algorithms initialized into this object and free
// memory associated to them. Note that GetDigest etc. functions are
// meaningles after this until new ones are initialized and finalized.
// -----------------------------------------------------------------------------
void CCrCrypto::Reset()
{
TInt i = 0;
TInt size = 0;
CCrAlgInfo* algInfo = 0;
size = iAlgorithmInfos->Count();
for (i = 0; i < size; i++)
{
algInfo = (*iAlgorithmInfos)[i];
if (algInfo)
{
switch (algInfo->iType)
{
case ECrDES2:
case ECrDES3:
{
if (algInfo->iAlgorithmObject)
{
delete algInfo->iAlgorithmObject;
algInfo->iAlgorithmObject = NULL;
}
if (algInfo->iPadding)
{
delete algInfo->iPadding;
algInfo->iPadding = NULL;
}
break;
}
default:
{
if (algInfo->iAlgorithmObject)
{
delete algInfo->iAlgorithmObject;
algInfo->iAlgorithmObject = NULL;
}
if (algInfo->iPadding)
{
delete algInfo->iPadding;
algInfo->iPadding = NULL;
}
break;
}
}
}
}
iAlgorithmInfos->ResetAndDestroy();
}
// -----------------------------------------------------------------------------
// CCrCrypto::InitCrypt3DESL
// Initialize encryption or decryption with 3DES algorithm.
// -----------------------------------------------------------------------------
TCrStatus CCrCrypto::InitCrypt3DESL(
const TDesC8& aKey1,
const TDesC8& aKey2,
const TDesC8& aKey3,
const TDesC8& aIV,
TBool aEncrypt, // ETrue
TCrSymmMode aMode, // ECrCBC
TPaddingRule /*aPadRule*/) // ECrPKCS1
{
// Number of items pushed to CleanupStack
TUint pushedToCStack = 0;
// Combined keys.
HBufC8* keys = HBufC8::NewLC(
aKey1.Length() + aKey2.Length() + aKey3.Length());
++pushedToCStack;
TPtr8 ptrKeys = keys->Des();
// Combine given keys to be able to give them to algorithm info object.
ptrKeys = aKey1;
ptrKeys.Append(aKey2);
ptrKeys.Append(aKey3);
// Create new algorithm info object.
CCrAlgInfo* algInfo = CCrAlgInfo::NewLC(
ECrDES3,
*keys,
aIV,
aEncrypt,
ETrue,
aMode);
++pushedToCStack;
switch (aMode)
{
case ECrCBC:
{
if (aEncrypt)
{
C3DESEncryptor* tripleDes = C3DESEncryptor::NewL(*algInfo->iKey);
CleanupStack::PushL(tripleDes);
CModeCBCEncryptor* cbcEncryptor = CModeCBCEncryptor::NewL(tripleDes, aIV);
CleanupStack::Pop(tripleDes); // CModeCBCEncryptor takes care of tripleDes now
CleanupStack::PushL(cbcEncryptor);
CPaddingPKCS7* padding = CPaddingPKCS7::NewL(tripleDes->BlockSize());
CleanupStack::PushL(padding);
CBufferedEncryptor* encryptor = CBufferedEncryptor::NewL(cbcEncryptor, padding);
CleanupStack::Pop(2); // CBufferedEncryptor takes care of freeing
algInfo->iAlgorithmObject = encryptor;
}
else
{
C3DESDecryptor* tripleDes = C3DESDecryptor::NewL(*algInfo->iKey);
CleanupStack::PushL(tripleDes);
CModeCBCDecryptor* cbcDecryptor = CModeCBCDecryptor::NewL(tripleDes, aIV);
CleanupStack::Pop(tripleDes); // CModeCBCEncryptor takes care of tripleDes now
CleanupStack::PushL(cbcDecryptor);
CPaddingPKCS7* padding = CPaddingPKCS7::NewL(tripleDes->BlockSize());
CleanupStack::PushL(padding);
CBufferedDecryptor* decryptor = CBufferedDecryptor::NewL(cbcDecryptor, padding);
CleanupStack::Pop(2); // CBufferedDecryptor takes care of freeing
algInfo->iAlgorithmObject = decryptor;
}
break;
}
case ECrCFB:
{
return KCrCrypto | KCrUnknownMode;
}
case ECrECB:
{
return KCrCrypto | KCrUnknownMode;
}
case ECrOFB:
{
return KCrCrypto | KCrUnknownMode;
}
default:
{
return KCrCrypto | KCrUnknownMode;
}
}
CleanupStack::PushL(algInfo->iAlgorithmObject);
++pushedToCStack;
// Append new algorithm info object into member set.
iAlgorithmInfos->AppendL(algInfo);
CleanupStack::Pop(pushedToCStack);
delete keys;
keys = NULL;
return KCrOK;
}
// -----------------------------------------------------------------------------
// CCrCrypto::InitCryptRC2L
// Initialize encryption or decryption with RC2 algorithm.
// -----------------------------------------------------------------------------
TCrStatus CCrCrypto::InitCryptRC2L(
const TDesC8& aKey,
const TDesC8& aIV,
TBool aEncrypt, // ETrue
TInt aEffectiveKeyLen, // If 0 given, key len is used.
TCrSymmMode aMode, // ECrCBC
TPaddingRule /*aPadRule */) // ECrPKCS1
{
// Number of items pushed to CleanupStack
TUint pushedToCStack = 0;
// Create new algorithm info object.
CCrAlgInfo* algInfo = CCrAlgInfo::NewLC(
ECrRC2,
aKey,
aIV,
aEncrypt,
ETrue,
aMode);
++pushedToCStack;
// If given effective key length is zero, use key length.
if (aEffectiveKeyLen == 0)
{
aEffectiveKeyLen = algInfo->iKey->Length() * 8;
}
// Create cipher object.
switch (aMode)
{
case ECrCBC:
{
if (aEncrypt)
{
CRC2Encryptor* rc2 = CRC2Encryptor::NewL(*algInfo->iKey, aEffectiveKeyLen);
CleanupStack::PushL(rc2);
CModeCBCEncryptor* cbcEncryptor = CModeCBCEncryptor::NewL(rc2, aIV);
CleanupStack::Pop(rc2); // CModeCBCEncryptor takes care of rc2 now
CleanupStack::PushL(cbcEncryptor);
CPaddingPKCS7* padding = CPaddingPKCS7::NewL(rc2->BlockSize());
CleanupStack::PushL(padding);
CBufferedEncryptor* encryptor = CBufferedEncryptor::NewL(cbcEncryptor, padding);
CleanupStack::Pop(2); // CBufferedEncryptor takes care of freeing
algInfo->iAlgorithmObject = encryptor;
}
else
{
CRC2Decryptor* rc2 = CRC2Decryptor::NewL(*algInfo->iKey, aEffectiveKeyLen);
CleanupStack::PushL(rc2);
CModeCBCDecryptor* cbcDecryptor = CModeCBCDecryptor::NewL(rc2, aIV);
CleanupStack::Pop(rc2); // CModeCBCEncryptor takes care of rc2 now
CleanupStack::PushL(cbcDecryptor);
CPaddingPKCS7* padding = CPaddingPKCS7::NewL(rc2->BlockSize());
CleanupStack::PushL(padding);
CBufferedDecryptor* decryptor = CBufferedDecryptor::NewL(cbcDecryptor, padding);
CleanupStack::Pop(2); // CBufferedDecryptor takes care of freeing
algInfo->iAlgorithmObject = decryptor;
}
algInfo->iMode = ECrCBC;
break;
}
case ECrCFB:
{
return KCrCrypto | KCrUnknownMode;
}
case ECrECB:
{
return KCrCrypto | KCrUnknownMode;
}
case ECrOFB:
{
return KCrCrypto | KCrUnknownMode;
}
default:
{
return KCrCrypto | KCrUnknownMode;
}
}
CleanupStack::PushL(algInfo->iAlgorithmObject);
++pushedToCStack;
// Append new algorithm info object into member set.
iAlgorithmInfos->AppendL(algInfo);
CleanupStack::Pop(pushedToCStack);
return KCrOK;
}
// -----------------------------------------------------------------------------
// CCrCrypto::InitDigestL
// Initialize message digest with MD2 algorithm.
// -----------------------------------------------------------------------------
TCrStatus CCrCrypto::InitDigestL(TCrAlgorithm aAlgorithm)
{
// Number of items pushed to CleanupStack
TUint pushedToCStack = 0;
// Create new algorithm info object.
CCrAlgInfo *algInfo = CCrAlgInfo::NewLC(aAlgorithm);
pushedToCStack++;
switch(aAlgorithm)
{
case ECrSHA1:
{
// Create digest object.
algInfo->iAlgorithmObject = CSHA1::NewL();
break;
}
case ECrMD5:
{
// Create digest object.
algInfo->iAlgorithmObject = CMD5::NewL();
break;
}
case ECrMD2:
{
// Create digest object.
algInfo->iAlgorithmObject = CMD2::NewL();
break;
}
case ECrSHA:
{
// Create digest object.
algInfo->iAlgorithmObject = CSHA::NewL();
break;
}
default:
{
return KCrCrypto | KCrNotSupportedAlg;
}
}
CleanupStack::PushL(algInfo->iAlgorithmObject);
pushedToCStack++;
// Append new algorithm info object into member set.
iAlgorithmInfos->AppendL(algInfo);
CleanupStack::Pop(pushedToCStack);
return KCrOK;
}
// -----------------------------------------------------------------------------
// CCrCrypto::InitDigestHMACL
// Initialize message digest with HMAC algorithm.
// -----------------------------------------------------------------------------
TCrStatus CCrCrypto::InitDigestHMACL(
const TDesC8& aKey,
TCrAlgorithm aDigestAlg)
{
// Number of items pushed to CleanupStack
TUint pushedToCStack = 0;
TCrAlgorithm hmacDigest;
switch (aDigestAlg)
{
case ECrSHA1:
{
hmacDigest = ECrHMAC_SHA1;
break;
}
case ECrMD5:
{
hmacDigest = ECrHMAC_MD5;
break;
}
default:
{
return KCrCrypto | KCrNotSupportedAlg;
}
}
// Create new algorithm info object.
CCrAlgInfo *algInfo = CCrAlgInfo::NewLC(hmacDigest);
pushedToCStack++;
CMessageDigest* digest = 0;
switch(aDigestAlg)
{
case ECrSHA1:
{
// Create digest object.
digest = CSHA1::NewL();
break;
}
case ECrMD5:
{
// Create digest object.
digest = CMD5::NewL();
break;
}
case ECrMD2:
{
// Create digest object.
digest = CMD2::NewL();
break;
}
case ECrSHA:
{
// Create digest object.
digest = CSHA::NewL();
break;
}
default:
{
return KCrCrypto | KCrNotSupportedAlg;
}
}
CleanupStack::PushL(digest);
++pushedToCStack;
// Create digest object.
algInfo->iAlgorithmObject = CHMAC::NewL(aKey, digest);
CleanupStack::PushL(algInfo->iAlgorithmObject);
++pushedToCStack;
// Append new algorithm info object into member set.
iAlgorithmInfos->AppendL(algInfo);
CleanupStack::Pop(pushedToCStack);
return KCrOK;
}
// -----------------------------------------------------------------------------
// CCrCrypto::ProcessL
// Process given source data with initialized crypto operations.
// If symmetric crypto is initialized sets to aTrg encrypted
// or decrypted data without last portion. If aProcessFinalBlock
// is ETrue, appends also last portion. If only digest algorithm
// is initialized, aTrg is not used.
// -----------------------------------------------------------------------------
TCrStatus CCrCrypto::ProcessL(const TDesC8& aSrc, TDes8& aTrg)
{
TInt i, size;
TUint pushedToCStack = 0;
CCrAlgInfo* algInfo = 0;
size = iAlgorithmInfos->Count();
for (i = 0; i < size; i++)
{
algInfo = (*iAlgorithmInfos)[i];
// Message digest algorithm
if (algInfo->iType < ECrLAST_DIGEST)
{
// Casting to right type
CMessageDigest* digest = STATIC_CAST(
CMessageDigest*, algInfo->iAlgorithmObject);
// Store the digest to buf
//algInfo->iDigest = HBufC8::NewL(digest->HashSize());
TPtr8 ptr = algInfo->iDigest->Des();
ptr.Copy(digest->Hash(aSrc));
}
// Symmetric crypto algorithm
else if (algInfo->iType > ECrLAST_DIGEST && algInfo->iType < ECrLAST_SYMM_CRYPTO)
{
// Casting to right type
CSymmetricCipher* cipherSymm =
STATIC_CAST(CSymmetricCipher*, algInfo->iAlgorithmObject);
// This is not incremental
cipherSymm->ProcessFinalL(aSrc, aTrg);
CleanupStack::Pop(pushedToCStack);
}
else
{
return KCrCrypto | KCrNotSupportedAlg;
}
}
return KCrOK;
}
// -----------------------------------------------------------------------------
// CCrCrypto::FinalCryptL
// Finalize symmetric algorithms objects.
// -----------------------------------------------------------------------------
TCrStatus CCrCrypto::FinalCryptL(TDes8& aTrg)
{
TInt i, size;
CCrAlgInfo *algInfo = 0;
TCrStatus status = KCrOK;
size = iAlgorithmInfos->Count();
for (i = 0; i < size; i++)
{
algInfo = (*iAlgorithmInfos)[i];
if (algInfo->iType > ECrLAST_DIGEST &&
algInfo->iType < ECrLAST_SYMM_CRYPTO)
{
TUint8 pushedToCStack = 0;
CSymmetricCipher* cipherSymm =
STATIC_CAST(CSymmetricCipher*, algInfo->iAlgorithmObject);
HBufC8 *lastBlock =
HBufC8::NewLC(algInfo->iLastPortion->Size());
pushedToCStack++;
TPtr8 ptrLastBlock = lastBlock->Des();
if (cipherSymm->MaxFinalOutputLength(ptrLastBlock.Size()) > 0)
{
cipherSymm->ProcessFinalL(ptrLastBlock, aTrg);
}
CleanupStack::Pop(pushedToCStack);
delete lastBlock;
lastBlock = 0;
delete cipherSymm;
cipherSymm = 0;
algInfo->iAlgorithmObject = 0;
}
}
return status;
}
TCrStatus CCrCrypto::FinalDigest(TDes8& aTrg)
{
TInt i, size;
CCrAlgInfo *algInfo = 0;
TCrStatus status = KCrOK;
size = iAlgorithmInfos->Count();
for (i = 0; i < size; i++)
{
algInfo = (*iAlgorithmInfos)[i];
if ((algInfo->iType < ECrLAST_DIGEST) && (status == KCrOK))
{
aTrg.Copy(*algInfo->iDigest);
CMessageDigest* digest = STATIC_CAST(
CMessageDigest*, algInfo->iAlgorithmObject);
delete digest;
digest = 0;
algInfo->iAlgorithmObject = 0;
}
}
return status;
}
// -----------------------------------------------------------------------------
// CCrCrypto::RemoveLastBlock
// Checks that aOriginSrc length is multiple of the block size, If not,
// removes data from the end so that it is multiple of the block size.
// Parameters: aSrc Original data to remove the last block
// aBlockSize Size of the block
// alginfo Pointer to object where last portion is
// stored.
// Return Values: true if size of aOriginSrc is bigger than blockSize
// otherwise false. If false the whole aOriginSrc is stored
// to alginfo.
// -----------------------------------------------------------------------------
TInt CCrCrypto::RemoveLastBlock(
TDesC8& aSrc,
const TInt aBlockSize,
CCrAlgInfo* algInfo)
{
TUint number_of_blocks = aSrc.Size() / aBlockSize;
TUint size_of_last_block = aSrc.Size() % aBlockSize;
TUint size_of_checked;
// If aOriginSrc's size is smaller or equal than aBlocksize
if (number_of_blocks == 0 || (number_of_blocks == 1 && size_of_last_block == 0))
{
*algInfo->iLastPortion = aSrc;
return false;
}
else if (size_of_last_block == 0 && number_of_blocks > 0)
{
// 3des fix begins:
// Don't do anything if size of data already is multiple of
// blocksize. Otherwise padding will be ruined. Return true anyway.
if(!algInfo->iEncrypt)
{
size_of_checked = aSrc.Size() - aBlockSize;
*algInfo->iLastPortion = aSrc.Right(aBlockSize);
aSrc = aSrc.Left(size_of_checked);
}
// 3des fix ends.
return true;
}
else
{
size_of_checked = aSrc.Size() - size_of_last_block;
*algInfo->iLastPortion = aSrc.Right(size_of_last_block);
aSrc = aSrc.Left(size_of_checked);
return true;
}
}
// -----------------------------------------------------------------------------
// CCrCrypto::DeriveKeyPKCS12L
// Derives key(s) or IV vector from password, salt and iterarion count.
// Return Values: KCrOK
// KCrNotSupportedAlg
// KCrUndefinedLibrary
// KCrUnknownLibrary
// KCrUnknownMode
// KCrErrorGeneral
// -----------------------------------------------------------------------------
TCrStatus CCrCrypto::DeriveKeyPKCS12L(
const TDesC8& aPassword,
const TDesC8& aSalt,
const TInt aIterationCount,
TCrAlgorithm aHashFunc,
const TUint8 aID,
const TInt aNumberOfBytes,
TDes8& aTrg) // Output data, possible keys and IV
{
TInt remainder = 0;
TInt rounds = 0;
TInt pushedToCStack = 0;
TInt inputSize = MesDigestInputSizeL(aHashFunc);
TInt outputSize = MesDigestOutputSizeL(aHashFunc);
// Step 1: Construct D by concatenating copies of ID.
// Construct a string D
HBufC8* D_buf = HBufC8::NewLC(inputSize);
++pushedToCStack;
TPtr8 D_ptr = D_buf->Des();
TInt i(0);
for (i = 0; i < inputSize; ++i)
{
D_ptr.Append(aID);
}
// Step 2, 3, 4:
TInt s_length = 0;
TInt p_length = 0;
s_length = inputSize * ((aSalt.Size() + inputSize - 1) / inputSize);
p_length = inputSize * ((aPassword.Size() + inputSize - 1) / inputSize);
HBufC8* I_buf = HBufC8::NewLC(s_length + p_length);
++pushedToCStack;
TPtr8 I_ptr = I_buf->Des();
if (aSalt.Size() != 0)
{
rounds = s_length / aSalt.Size();
for (i = 0; i < rounds; ++i)
{
I_ptr.Append(aSalt);
}
remainder = s_length % aSalt.Size();
if (remainder != 0)
{
I_ptr.Append(aSalt.Ptr(), remainder);
}
}
if (aPassword.Size() != 0)
{
rounds = p_length / aPassword.Size();
for (i = 0; i < rounds; ++i)
{
I_ptr.Append(aPassword);
}
remainder = p_length % aPassword.Size();
if (remainder != 0)
{
I_ptr.Append(aPassword.Ptr(), remainder);
}
}
// Step 5: Set c.
TInt c = 0;
c = (aNumberOfBytes + outputSize - 1) / outputSize;
// Step 6: Loop
TCrStatus status = KCrCrypto | KCrErrorGeneral;
CCrCrypto* hash = 0;
HBufC8* B_buf = HBufC8::NewLC(inputSize);
++pushedToCStack;
TPtr8 B_ptr = B_buf->Des();
HBufC8* A_buf = HBufC8::NewLC(D_ptr.Size() + I_ptr.Size());
++pushedToCStack;
TPtr8 A_ptr = A_buf->Des();
TInt j = 0;
RInteger B_int;
RInteger Ij_int;
TInt N = aNumberOfBytes;
rounds = inputSize / outputSize;
remainder = inputSize % outputSize;
for (i = 0; i < c; ++i)
{
A_ptr.Zero();
A_ptr.Append(D_ptr);
A_ptr.Append(I_ptr);
hash = CCrCrypto::NewLC();
for (TInt ii = 0; ii < aIterationCount; ++ii)
{
status = hash->InitDigestL(aHashFunc);
status = hash->ProcessL(A_ptr, A_ptr);
A_ptr.Zero();
status = hash->FinalDigest(A_ptr);
hash->Reset();
}
CleanupStack::PopAndDestroy(); // hash
if (outputSize < N)
{
aTrg.Append(A_ptr);
}
else
{
aTrg.Append(A_ptr.Ptr(), N);
}
if (outputSize >= N)
{
status = KCrOK;
break;
}
N -= outputSize;
rounds = inputSize / A_ptr.Size();
remainder = inputSize % A_ptr.Size();
for (j = 0; j < rounds; ++j)
{
B_ptr.Append(A_ptr);
}
if (remainder != 0)
{
B_ptr.Append(A_ptr.Ptr(), remainder);
}
B_int = RInteger::NewL(B_ptr);
CleanupStack::PushL(B_int);
B_int += 1;
for (j = 0; j < I_ptr.Size(); j += inputSize)
{
Ij_int = RInteger::NewL(I_ptr.Mid(j, inputSize));
CleanupStack::PushL(Ij_int);
Ij_int += B_int;
I_ptr.Replace(j, inputSize, Ij_int.BufferLC()->Right(inputSize));
CleanupStack::PopAndDestroy(2); // Ij_int, BufferLC
}
CleanupStack::PopAndDestroy(); // B_int
}
CleanupStack::PopAndDestroy(pushedToCStack);
return status;
}
// -----------------------------------------------------------------------------
// CCrCrypto::MesDigestInputSizeL
// Returns input size of the message digest algorithm.
// Return Values: Input size of the message digest algorithm in bytes.
// If unknown algorithm, leaves with KErrNotSupported.
// -----------------------------------------------------------------------------
TInt CCrCrypto::MesDigestInputSizeL( TCrAlgorithm aDigestAlg )
{
switch(aDigestAlg)
{
case ECrSHA1:
case ECrMD5:
case ECrMD2:
{
return KDigestInputSize;
}
default:
{
User::Leave( KErrNotSupported );
return -1; // keeps compiler happy
}
}
}
// -----------------------------------------------------------------------------
// CCrCrypto::MesDigestOutputSizeL
// Returns output size of the message digest algorithm.
// Parameters: aDigestAlg message digest algortihm
// Return Values: Output size of the message digest algorithm in bytes.
// If unknown algorithm, leaves with KErrNotSupported.
// -----------------------------------------------------------------------------
TInt CCrCrypto::MesDigestOutputSizeL( TCrAlgorithm aDigestAlg )
{
switch(aDigestAlg)
{
case ECrSHA1:
{
return KCrLongDigestLength;
}
case ECrMD5:
case ECrMD2:
{
return KCrMediumDigestLength;
}
default:
{
User::Leave( KErrNotSupported );
return -1; // keeps compiler happy
}
}
}
// End Of Line