下一版本的 Microsoft .NET Framework(將隨附當前代號為“Orcas”的下一版 Visual Studio® 提供)的程序集分為兩組,內部稱為“red bits”和“green bits”。red bits 包括先前在 .NET Framework 2.0 和 3.0 中提供的所有庫(例如,mscorlib.dll 和 system.dll)。為了確保使 Visual Studio“Orcas”具有高度的向後兼容性,所以嚴格限制了對 red bits 中的內容的更改。
green bits 程序集是新型庫,它包括的是附加的類,這些類在 red bits 程序集之上運行。本專欄中列出的大多數類都屬於 green bits 程序集(例如,system.core.dll),只有幾個類涉及對 red bits 程序集進行的有限的更改。有關更多背景信息,請查看 Soma Somasegar 的博客和 Jason Zander 的博客。
本專欄中介紹的類將通過 2007 年 1 月 Visual Studio“Orcas”社區技術預覽 (CTP) 提供。CLR 對新庫的貢獻包括:
新的插件承載模式,這部分內容已在前兩期的“CLR 完全介紹”中進行了討論
支持加密算法集 Suite B(與美國國家安全局 (NSA) 的規定相同)
支持大整數
高性能集合
支持匿名和命名管道
改善了時區支持
精簡型讀取器鎖/寫入器鎖類
更好地與 Windows® 事件跟蹤 (ETW) 功能集成,ETW 包括 ETW 提供程序和 ETW 跟蹤偵聽器 API
在本專欄中,我們將討論新的 Suite B 加密功能、對大整數的支持、集合類和管道功能。
支持 Suite B
Visual Studio“Orcas”的一個主要目標是添加對加密算法集 Suite B 的支持。為了支持 Suite B,平台必須支持:
高級加密標准 (AES),用於加密的密鑰長度可達到 128 位和 256 位
安全哈希算法(SHA-256 和 SHA-384),用於進行哈希運算
橢圓曲線數字簽名算法 (ECDSA),使用 256 位和 384 位素模曲線進行簽名
橢圓曲線 Diffie-Hellman (ECDH),使用 256 位和 384 位素模曲線交換密鑰/秘密協議
您可以在 NSA 發布的概述(網址為:www.nsa.gov/ia/industry/crypto_suite_b.cfm)中找到有關 Suite B 的詳細信息。
除了這些標准之外,我們還希望支持聯邦信息處理標准 (FIPS) 認證的這些算法的實現方法。從 .NET Framework 1.0 版開始,我們就已經支持 AES(通過 RijndaelManaged 類)、SHA-256 和 SHA-384(通過使用 SHA256Managed 和 SHA384Managed 類),但是我們的實現方法並未經過 FIPS 認證。
第一步,我們在 Windows 中為 FIPS 認證的 AES、SHA-256 和 SHA-384 實現方法添加了托管代碼打包程序。在 Visual Studio“Orcas”中,它們顯示為新的 AesCryptoServiceProvider、SHA256CryptoServiceProvider 和 SHA384CryptoServiceProvider 類。它們都在 System.Security.Cryptography 命名空間中。
所有平台上都有這些算法,以下平台還提供了基本的 Windows 加密服務提供程序:Windows XP 及更高版本提供了 AES,Windows Server® 2003 及更高版本提供了 SHA 算法。總的來說,它們的工作原理與它們在托管代碼中的對應部分完全一致,因此開發人員通常可以互換使用,並不會引起任何行為改變。唯一的不同時,對於每個 AES 規范,AesCryptoServiceProvider 僅支持 128 位的固定塊大小。
CngKey 類
為了支持新的基於 CNG 的托管加密類,我們添加了一個 CngKey 類對 CNG 密鑰的存儲和使用進行抽象。在當今的加密 API (CAPI) 中,CNG 密鑰與密鑰容器的工作原理類似,使您可以安全地存儲密鑰對或公鑰並使用簡單的字符串名稱對其進行引用。在使用 ECDsaCng 和 ECDiffieHellmanCng 類時,您可以使用 CngKey 對象。
您還可以使用 CngKey 類直接執行多種操作,包括打開、創建、刪除和導出密鑰。如果您沒有所需操作的托管 API,則可以直接調用 Win32® API,使用相應的密鑰句柄。
橢圓曲線加密
接下來我們為 ECDSA 和 ECDH 添加類。這些類打包了新的下一代加密 (CNG) 實現方法,在 Windows Vista™ 中予以提供。(有關我們如何支持 CNG 類的詳細信息,請參閱側欄“CngKey 類”。)橢圓曲線加密是非對稱加密,這意味著它使用公鑰和私鑰對執行操作(與 RSA 類似)。但是橢圓曲線加密通常比傳統公鑰加密算法的計算效率要高,在密鑰長度增加時更是如此。實際上,NSA 建議采用長度為 15,360 位的 RSA 密鑰來保護 256 位的對稱密鑰,而建議只用 521 位的橢圓曲線密鑰來保護同樣是 256 位的對稱密鑰。
您可以在 NSA 的文章“橢圓密鑰加密示例”中,找到有關使用橢圓曲線加密的優點的詳細信息。
ECDSA
ECDSA 的工作原理與大多數簽名算法類似:使用私鑰進行簽名,使用公鑰進行驗證。新的 ECDsaCng 類包含 ECDSA 的實現,其模式與其他托管加密類相同。假設您要對某些數據進行簽名(假定您的密鑰對存儲在名為 MyKey 的 CNG 密鑰中):
// Returns the signature byte[] SignMyData (byte[] data) { ECDsaCng signingAlg = new ECDsaCng(CngKey.Open("MyKey")); return signingAlg.SignData(data); }
假如現在您要驗證 Jane 的簽名(她的公鑰存儲在名為 JanesKey 的 CNG 密鑰中)。您應執行以下操作:
// Returns whether or not the signature could be verified bool VerifyJanesSignature(byte[] data, byte[] signature) { ECDsaCng signingAlg = new ECDsaCng(CngKey.Open("JanesKey")); return signingAlg.VerifyData(data, signature); }
ECDsaCng 類使您還可以在簽名或驗證時設置不同的選項(主要通過類的屬性進行設置)。例如在默認情況下,ECDsaCng 類在簽名和驗證過程中使用 SHA-256 哈希算法。如果要使用其他算法,您只需在簽名之前更改 HashAlgorithm 屬性即可。假定要選擇 SHA-512。MyKey 的簽名示例將如下所示:
// Returns the signature byte[] SignMyData (byte[] data) { ECDsaCng signingAlg = new ECDsaCng(CngKey.Open("MyKey")); signingAlg.HashAlgorithm = CngAlgorithm.Sha512; return signingAlg.SignData(data); }
而 JanesKey 的驗證示例為:
// Returns whether or not the signature could be verified bool VerifyJanesSignature(byte[] data, byte[] signature) { ECDsaCng signingAlg = new ECDsaCng(CngKey.Open("JanesKey")); signingAlg.HashAlgorithm = CngAlgorithm.Sha512; return signingAlg.VerifyData(data, signature); }
ECDH
ECDH 是一個密鑰交換算法,有時是指秘密協議算法。此算法用於通過與另一方交換公鑰來得到一個密鑰(該密鑰常用於通過對稱算法進行加密或解密)。例如,假定有兩個用戶 Alice 和 Bob 希望得到一個密鑰。他們都已經有了橢圓曲線密鑰對(公鑰和私鑰)。為了使用 ECDH 得到密鑰,他們應執行以下步驟:
Alice 將其公鑰發送給 Bob。Bob 必須確保收到了 Alice 的密鑰且密鑰未被修改,該密鑰無需保密發送。
Bob 將其公鑰發送給 Alice。同樣地,Alice 必須確保收到了 Bob 的密鑰且密鑰未被修改,該密鑰也無需保密發送。
此時,Alice 和 Bob 可以生成相同的機密值,前提是他們都收到了對方的公鑰並有自己的密鑰對。然後,可將此機密值用於生成用於對稱算法的密鑰。
其代碼非常簡單。以下示例表示 Alice 生成了一個 256 位的密鑰(假設她的密鑰對存儲在名為 MyKey 的 CNG 密鑰中):
// Returns a 256 bit secret key that is shared with the other party bool Generate256BitKey(string otherParty) { ECDiffieHellmanCng keyExchAlg = new ECDiffieHellmanCng(CngKey.Open("MyKey")); byte [] myPublicKey = keyExchAlg.PublicKey.ToByteArray(); ... // send myPublicKey to other party byte[] otherPartysPublicKeyBytes = ...; // get other party's key ECDiffieHellmanCngPublicKey otherPartysPublicKey = ECDiffieHellmanCngPublicKey.FromByteArray( otherPartysPublicKeyBytes, CngKeyBlobFormat.EccPublicBlob); return keyExchAlg.DeriveKeyMaterial(otherPartysPublicKey); }
此時,Alice 有一個 256 位的對稱密鑰,可用於與另一方 (Bob) 進行數據加密和秘密通信。但是,我必須再次強調,您必須確保在與上例類似的情況下,收到了來自對方的真正的公鑰,而不是收到來自冒名頂替者的所謂公鑰。這通常需要通過某種形式的驗證來保證。
與 ECDsaCng 類一樣,這裡使用的 ECDiffieHellmanCng 類也可以通過屬性進行自定義設置。例如,通過這些屬性指定密鑰生成函數的各個參數,您可以使用不同的密鑰生成函數。
支持大整數
能夠表示很大的整數數字是客戶的另一個要求,我們的下一版 .NET Framework 將實現對大整數的支持。您是否曾經需要一個非常大的數字(大得連 .NET Framework 中的任何其他基本類型都無法表示)?現在您可以使用新的 BigInteger 類型,該類型使您可以使用任意大小的整數,直至可以達到內存的極限。現在讓我們先來看看您可能會需要大數字的某些情形。
涉及太空距離的天文計算需要能夠計算非常巨大的數字。為了避免使用這些大數字,天文學家通常以光年為單位計算距離。因為一光年約等於 5,878,625,373,183.61 英裡(或 9,460,730,472,580.8 千米),那麼星球之間距離的實際英裡數(更不用說以英尺或英寸為單位進行計算)遠遠超過 64 位的表示范圍。
大整數還用於統計學和數論中。但並不是所有的數學家都需要計算如此巨大的整數,請參考 John Edensor Littlewood 的大數法則(該法則說如果樣本中的數字足夠大,則可能會出現各種奇怪的事情)和他的一些其他著作。
此外,很多加密算法需要使用非常大的質數。這些數字大得遠遠無法用目前的基本類型來表示。
新 BigInteger 類位於新的 System.Numeric 命名空間中。它支持標准數值類型的大多數運算符,例如加法 (+)、減法 (-) 和乘法 (*)。圖 1 中的示例代碼顯示了如何使用 BigInteger 類進行階乘計算。圖 2 顯示了輸入 1000 所得的示例輸出。
Figure 2 1000 的階乘程序輸出
402387260077093773543702433923003985719374864210714632543799910 429938512398629020592044208486969404800479988610197196058631666 872994808558901323829669944590997424504087073759918823627727188 732519779505950995276120874975462497043601418278094646496291056 393887437886487337119181045825783647849977012476632889835955735 432513185323958463075557409114262417474349347553428646576611667 797396668820291207379143853719588249808126867838374559731746136 085379534524221586593201928090878297308431392844403281231558611 036976801357304216168747609675871348312025478589320767169132448 426236131412508780208000261683151027341827977704784635868170164 365024153691398281264810213092761244896359928705114964975419909 342221566832572080821333186116811553615836546984046708975602900 950537616475847728421889679646244945160765353408198901385442487 984959953319101723355556602139450399736280750137837615307127761 926849034352625200015888535147331611702103968175921510907788019 393178114194545257223865541461062892187960223838971476088506276 862967146674697562911234082439208160153780889893964518263243671 616762179168909779911903754031274622289988005195444414282012187 361745992642956581746628302955570299024324153181617210465832036 786906117260158783520751516284225540265170483304226143974286933 061690897968482590125458327168226458066526769958652682272807075 781391858178889652208164348344825993266043367660176999612831860 788386150279465955131156552036093988180612138558600301435694527 224206344631797460594682573103790084024432438465657245014402821 885252470935190620929023136493273497565513958720559654228749774 011413346962715422845862377387538230483865688976461927383814900 140767310446640259899490222221765904339901886018566526485061799 702356193897017860040811889729918311021171229845901641921068884 387121855646124960798722908519296819372388642614839657382291123 125024186649353143970137428531926649875337218940694281434118520 158014123344828015051399694290153483077644569099073152433278288 269864602789864321139083506217095002597389863554277196742822248 757586765752344220207573630569498825087968928162753848863396909 959826280956121450994871701244516461260379029309120889086942028 510640182154399457156805941872748998094254742173582401063677404 595741785160829230135358081840096996372524230560855903700624271 243416909004153690105933983835777939410970027753472000000000000 000000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000000000000000000 000000000000000000000000000000000000000000000000
Figure 1 使用 BigInteger 進行階乘計算
using System.Numeric; static BigInteger Factorial(BigInteger x) { BigInteger result = 1; for (BigInteger i = 2; i <= x; i++) result *= i; return result; } static void Main(string[] args) { BigInteger x = BigInteger.Parse(Console.ReadLine()); Console.WriteLine(Factorial(x).ToString()); }
通過類的靜態方法,BigInteger 可以進行很多其他數學運算。這些方法包括:
Abs(返回給定 BigInteger 的絕對值)
Compare(比較兩個 BigInteger)
Divide(返回兩個 BigInteger 的商)
DivRem(返回兩個 BigInteger,分別為商和余數)
Equals(如果兩個 BigInteger 的值相同則返回 true)
GreatestCommonDivisor(返回兩個 BigInteger 的最大公約數)
ModPow(返回值等於,一個 BigInteger 以另一個 BigInteger 為指數的冪,然後對第三個數求模;指數不能為負)
Pow(返回一個 BigInteger 以另一個 BigInteger 為指數的冪;指數不能為負)
Remainder(返回兩個 BigInteger 相除所得到的余數)
我們會在 BCL 團隊博客 blogs.msdn.com/bclteam 上發布有關使用和擴展 BigInteger 類的更多信息。
我們應該注意到當前 BigInteger 的某些設計有所取捨。在 Visual Studio“Orcas”的 .NET Framework 中添加這個新數值類型非常重要。此外,有一點很重要,這個新的基本類型必須提前提供,以便其他 green bits 功能能夠使用。所以,我們決定此版本不完全支持其他數值類型所支持的全部功能,如,打印、格式化和分析等。因此,僅支持十進制 (D)、general (G) 和十六進制 (X) 格式。
BigInteger 類是新基類的一個例子,我們希望很多其他庫類型可以用於特定應用程序。我們要介紹的下一個類型是一個新集合,它是專為了迎合流行趨勢而添加的:這就是“集合類”。
高性能集合
新的 HashSet 類是 System.Collections.Generic 命名空間中的一個高性能泛型集合。它是包含唯一元素的無序集合。HashSet 具有所有標准的集合方法(例如,添加、刪除和包含)並提供了多種集合操作(包括,並集、交集和對稱差)。
以下示例代碼顯示了 HashSet 如何使用整數:
HashSet<int> set1 = new HashSet<int>(); set1.Add(1); set1.Add(3); set1.Add(5); set1.Add(3); // set1 already contains 3; it isn't added twice // set1 contains 1,3,5 HashSet<int> set2 = new HashSet<int>(new int[] { 2, 4, 6 }); // set2 contains 2,4,6 set2.UnionWith(set1); // set2 contains 1,2,3,4,5,6
注意添加四個項(1、3、5 和 3)之後的變化,set1 最後只包含三個元素(1、3 和 5)。此行為是有意設計的。HashSet 僅包含唯一元素。一旦將某個項添加到該集合中,將無法再成功添加該項。Add 方法實際上返回一個布爾值,該值表示項是否已成功添加到集合中。以上示例可以重寫如下:
HashSet<int> set1 = new HashSet<int>(); bool added = set1.Add(1); // added is true added = set1.Add(3); // added is true added = set1.Add(5); // added is true added = set1.Add(3); // added is false // set1 contains 1,3,5
第一個示例中,通過將整數數組 ({ 2, 4, 6}) 傳遞到 HashSet 構造函數來初始化 set2(將泛型 IEnumerable 作為參數)。如果 Ienumerable 中有重復元素,則在初始化 HashSet 時會將其忽略。例如,如果使用包含 2、4、2 和 6 的數組初始化 set2,則集合最後只會包含三個元素(2、4 和 6),因為重復的元素被忽略了。
HashSet 提供的集合操作(UnionWith、IntersectWith、ExceptWith 和 SymmetricExceptWith)不會將另一個 HashSet 對象作為參數,實際上它們將泛型 Ienumerable 作為參數。這意味著,集合操作可與任何其他集合一起使用。圖 3 顯示了集合操作如何可以與任何實現泛型 IEnumerable 接口的集合一起使用。由於構造函數重載采用了 Ienumerable,所以會忽略重復的元素。(圖 4 中簡要地列出了 UnionWith、IntersectWith、ExceptWith 和 SymmetricExceptWith 的行為。)
Figure 4 集合操作
集合操作 說明 示例 UnionWith(IEnumerable<T> other); 修改 HashSet 以便包含 HashSet 或其他集合所包含的所有元素。 //set1 包含 1、2、3 和 4//set2 包含 3、4、5 和 6 set1.UnionWith(set2);//set1 包含 1、2、3、4、5 和 6 IntersectWith(IEnumerable<T> other); 修改 HashSet 以便包含 HashSet 和其他集合所包含的所有元素。 //set1 包含 1、2、3 和 4//set2 包含 3、4、5 和 6 set1.IntersectWith(set2);//set1 包含 3 和 4 ExceptWith(IEnumerable<T> other); 刪除 HashSet 中所有的其他集合所包含的元素。 //set1 包含 1、2、3 和 4//set2 包含 3、4、5 和 6 set1.ExceptWith(set2);//set1 包含 1 和 2 SymmetricExceptWith(IEnumerable<T> other); 修改 HashSet 以便包含 HashSet 或其他集合包含的(而不是兩者均包含的)所有元素。 //set1 包含 1、2、3 和 4//set2 包含 3、4、5 和 6 set1.SymmetricExceptWith(set2);//set1 包含 1、2、5 和 6Figure 3 集合操作和泛型 IEnumerable
List<int> oddNumbers = new List<int>(); oddNumbers.Add(1); oddNumbers.Add(3); oddNumbers.Add(5); HashSet<int> numberSet = new HashSet<int>(); numberSet.UnionWith(oddNumbers); // numberSet contains 1,3,5 int[] evenNumbers = { 2, 4, 6, 2, 6, 4, 2, 4, 6, 2 }; numberSet.UnionWith(evenNumbers); // duplicates are ignored // numberSet contains 1,2,3,4,5,6 numberSet.ExceptWith(oddNumbers); // numberSet contains 2,4,6
除了表中列出的操作之外,HashSet 還提供了很多其他操作。其中包括 IsSubsetOf、IsProperSubsetOf、IsSupersetOf 和 IsProperSupersetOf。有了這些操作,HashSet 能夠支持大多數通常對集合執行的數學運算。
正如您所見,學會使用新集合非常簡單。HashSet 與其他泛型集合完美結合,彌補了集合 API 中最後一個不足。
管道
在本專欄中我們最後要介紹的類是管道類。對於 Visual Studio“Orcas”,我們已提供了對 .NET Framework 的匿名和命名管道的支持。管道用於在同一計算機或同一網絡上的兩個或多個進程之間實現進程間通訊 (IPC)。在 System.IO.Pipes 命名空間中找到的新類型幾乎可以提供 Windows 提供的所有管道功能。
匿名管道用於父進程和子進程之間的通訊。這些單向管道未命名,並且必須在同一台計算機上本地使用。圖 5 顯示了用於將字符串從父進程發送到子進程的代碼。
Figure 5 匿名管道
父進程
using (Process process = new Process()) { process.StartInfo.FileName = "child.exe"; using (AnonymousPipeServerStream pipeStream = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable)) { process.StartInfo.Arguments = pipeStream.GetClientHandleAsString(); process.StartInfo.UseShellExecute = false; process.Start(); pipeStream.DisposeLocalCopyOfClientHandle(); using (StreamWriter sw = new StreamWriter(pipeStream)) { sw.AutoFlush = true; sw.WriteLine(Console.ReadLine()); } } process.WaitForExit(); }
子進程
using (PipeStream pipeStream = new AnonymousPipeClientStream(PipeDirection.In, args[0])) { using (StreamReader sr = new StreamReader(pipeStream)) { string temp; while ((temp = sr.ReadLine()) != null) { Console.WriteLine(temp); } } }
與匿名管道相比,命名管道提供的功能更多。對於初學者而言,它們是全雙工的,並且可以對網絡使用。命名管道支持單一名稱、基於消息的通訊、異步 I/O 以及模擬等的多個服務器示例。圖 6 顯示了如何使用一個命名管道將兩個字符串從一個進程發送到另一個進程。
Figure 6 命名管道
服務器進程
using (NamedPipeServerStream pipeStream = new NamedPipeServerStream("mypipe")) { pipeStream.WaitForConnection(); using (StreamWriter sw = new StreamWriter(pipeStream)) { sw.AutoFlush = true; sw.WriteLine(Console.ReadLine()); sw.WriteLine(Console.ReadLine()); } }
客戶端進程
using (NamedPipeClientStream pipeStream = new NamedPipeClientStream("mypipe")) { pipeStream.Connect(); using (StreamReader sr = new StreamReader(pipeStream)) { string temp; while ((temp = sr.ReadLine()) != null) { Console.WriteLine(temp); } } }
命名管道還支持基於消息的通訊(如圖 7 所示)。這使得讀進程可以讀取由寫進程發送的各種長度的消息。圖 7 中,兩個字符串消息從服務器進程發送到了客戶端進程。示例包含兩個幫助器類(MessageWriter 和 MessageReader),它們執行從基本命名管道流讀取字符串消息或將字符串消息寫入基本命名管道流的實際工作。
Figure 7 基於消息的通訊
幫助器類
class MessageWriter { NamedPipeServerStream m_pipeStream; Encoding m_encoding; public MessageWriter(NamedPipeServerStream pipeStream, Encoding encoding) { m_pipeStream = pipeStream; m_encoding = encoding; } public void WriteMessage(string message) { Byte[] buffer; buffer = m_encoding.GetBytes(message); m_pipeStream.Write(buffer, 0, buffer.Length); } } class MessageReader { NamedPipeClientStream m_pipeStream; Decoder m_decoder; Byte[] m_buffer = new Byte[10]; Char[] m_charBuffer = new Char[10]; StringBuilder m_stringBuilder = new StringBuilder(); public MessageReader(NamedPipeClientStream pipeStream, Encoding encoding) { m_pipeStream = pipeStream; m_decoder = encoding.GetDecoder(); } public string ReadMessage() { // Clear the StringBuilder m_stringBuilder.Length = 0; // Read the message int bytesRead; do { bytesRead = m_pipeStream.Read(m_buffer, 0, m_buffer.Length); int numChars = m_decoder.GetChars(m_buffer, 0, bytesRead, m_charBuffer, 0); m_stringBuilder.Append(m_charBuffer, 0, numChars); } while (!m_pipeStream.IsMessageComplete); // Return the message or null if the pipe has been closed if (bytesRead != 0) { m_decoder.Reset(); return m_stringBuilder.ToString(); } else return null; } }
服務器進程
using (NamedPipeServerStream pipeStream = new NamedPipeServerStream("messagepipe", PipeDirection.InOut, 1, PipeTransmissionMode.Message)) { pipeStream.WaitForConnection(); MessageWriter mw = new MessageWriter(pipeStream, Encoding.UTF8); mw.WriteMessage("Hello World!"); mw.WriteMessage("Named Pipes Are Cool!"); }
客戶端進程
using (NamedPipeClientStream pipeStream = new NamedPipeClientStream("messagepipe")) { pipeStream.Connect(); pipeStream.ReadMode = PipeTransmissionMode.Message; MessageReader mr = new MessageReader(pipeStream, Encoding.UTF8); string message; while ((message = mr.ReadMessage()) != null) { Console.WriteLine(message); } }
正如您所見,新管道類型是一流的托管類型,使得在托管代碼內能夠很方便地實現 IPC。任何熟悉流的用戶在學習使用管道時都不會有太大困難。
總結
本專欄中討論的類是 Visual Studio“Orcas”中部分新的 green bits 功能。要嘗試使用這些類型,請從 msdn2.microsoft.com/en-us/vstudio/aa700831.aspx 下載最新的 CTP。然後到 BCL 團隊的博客做客,告訴我們您的想法。
將您想詢問的問題和提出的意見發送至:[email protected] [email protected].