Почему .NET CF вызывает исключение при работе с HTTPS-серверами

Пятница, 23 Ноябрь, 2007

Недавно в .NET CF версии 2.0 (SP2 и ранее) и 3.5 был обнаружен баг, приводящий к отказу в запросах HttpWebRequest к https серверам. Ошибка (для веб сервисов) выглядит следующим образом:

The error is “Unable to read data from the transport connection.”
System.Web.Services.dll!System.Web.Services.Protocols.WebClientProtocol.GetWebResponse(System.Net.WebRequest request = {System.Net.HttpWebRequest}) + 0×14 bytes
System.Web.Services.dll!System.Web.Services.Protocols.HttpWebClientProtocol.GetWebResponse(System.Net.WebRequest request = {System.Net.HttpWebRequest}) + 0×7 bytes
System.Web.Services.dll!System.Web.Services.Protocols.SoapHttpClientProtocol.doInvoke(string methodName = “…”, object[] parameters = {Dimensions:[2]}, System.Web.Services.Protocols.WebClientAsyncResult asyncResult = null) + 0×2ce bytes
System.Web.Services.dll!System.Web.Services.Protocols.SoapHttpClientProtocol.Invoke(string methodName = “…”, object[] parameters = {Dimensions:[2]}) + 0×9 bytes

Для стандартного вызова HttpWebRequest лог стека может быть другой, несмотря на то, что ошибка происходит по одной и той же причине.

Несмотря на множество сообщений об ошибке, причина заключается в том, что сервер отправляет NetCF-клиенту пустой шифрованный пакет. Ниже иллюстрация процесса шифрования, происходящего на сервере для https:

Сначала инициализируется буфер, содержащий незашифрованные данные, которые сервер собирается отправить клиенту по сети.

Шаг 1:   незашифрованные данные  

Затем, сервер вызывает EncryptMessage и незашифрованные данные шифруются, оставаясь на том же месте.

Шаг 2: заголовок (header) зашифрованные данные футер (footer)

Длина заголовка — 5 байт, длина футера немного больше. Получившийся пакет имеет большую длину, чем оригинальный незашифрованный. Он отправляется по сети и на выходе разворачивается процессом с помощью функции DecryptMessage.

Проблема с NetCF SSL- стеком возникает в момент, когда сервер шифрует буфер нулевой длины и отправляет его клиенту.

Шаг 1:   0 байт незашифрованных данных  
Шаг 2: заголовок (header) зашифрованное представление буфера нулевой длины футер (footer)

В то время как нешифрованные данные имеют нулевую длину, зашифрованный блок будет иметь длину не равную нулю. Когда этот пакет будет отправлен NetCF-клиенту, текущая версия NetCF дешифрует пакет и вернет буфер нулевой длины вызывающему клиенту. Семантика сетевого метода Read состоит в том, что возврат буфера нулевой длины означает закрытие сокета. Т.к. NetCF после дешифрации «пустого» зашифрованного пакета вернет пустой буфер, вызывающий клиент может интерпретировать это, как сигнал о закрытии сокета и прервет соединение.

Именно это и происходит, когда NetCF веб-сервис (вызываемый через SSL) возвращает пустой зашифрованный пакет. В результате соединение обрывается до момента получения полного ответа и, как следствие, срабатывает исключение.

Что заставляет сервер посылать эти пустые зашифрованные пакеты? Технически, это не запрещено. Все зависит от вашего сервера и его конфигурации. Как следствие, может происходить регулярно, а может никогда не произойти.

К сожалению, нет способа заставить NetCF игнорировать такие пакеты, разве что реорганизовать процесс (по возрастанию сложности):

  • 1. Не использовать SSL для запросов (как следствие, снижение безопасности, т.к. данные отправляются открытым текстом).
  • 2. Переконфигурировать сервер
  • 3. Построить новый веб-сервер, который будет перенаправлять запросы с устройства и обратно.
  • 4. Подождать новых версий NetCF, в которых проблема будет устранена.
  • 5. Написать собственный https клиент, используя нативный код или P/Invoke в NetCF.

[…]

Эндрю Арнот (Andrew Arnott), 19 ноября 2007, 22:18
Why .NET Compact Framework fails to call some HTTPS web servers

Обновился список приложений и библиотек под .NET Compact Framework

Среда, 26 Сентябрь, 2007

Я обновил .NET Compact Framework каталог, добавив туда ряд новых приложений и библиотек, а также слегка поправил существующие пункты.

Перейдите по этому линку, для просмотра нашего каталога приложений.

Каталог содержит приложения, которые я нашел через поисковики или получил по электронной почте от разработчиков. Мы постоянно ищем новые приложения и библиотеки, чтобы добавить их в наш каталог.

Найденные нами приложения и библиотеки, мы выкладываем в нашем блоге (т.е. здесь), чтобы было видно какой тип приложений построен на .NET Compact Framework. Каталог ежемесячно посещают тысячи людей. Поместив ссылку в нашем каталоге, вы получите дополнительное средство для рекламы вашего приложения и, конечно же, вашей компании. Кроме того, мы получаем кандидатов на проверку минимальной совместимости с новыми версиями .NET Compact Framework, находящимися в разработке. Это помогает нам прорабатывать совместимость с уже существующими приложениями (как ваше), одновременно продолжая работу над новыми версиями .NET Compact Framework.

Если ваше приложение отсутствует в списке – я с удовольствием добавлю его туда. Отправьте мне е-мэйл на cfapp@microsoft.com и не забудьте указать название приложения, на какой платформе оно работает, требуемая версия NETCF и ссылка на сайт приложения. Если вам нужен product support обратитесь, пожалуйста, в MSDN Feedback Center.

.NET Compact Framework Application and Libraries List Updated
Майкл Липп (Michael Lipp), Senior SDET
Вторник, 25 сентября 2007г. 13:53

Позиционирование контролов и рабочий каталог приложения в Windows Mobile

Понедельник, 9 Июль, 2007

Разговор пойдет о .NET CF 1.1. В .NET, назовем его desktop, для позиционирования контролов мы используем (чаще всего) layout контейнеры. В .NET CF такой возможности нет, поэтому придется позиционировать «ручками».

Позиционирование контролов

Размер рабочего пространства приложения определяется с помощью
Rectangle workArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;

Но(!), т.к. в Windows Mobile главное меню приложение расположено внизу экрана, верхняя позиция рабочего пространства равна (0,0), а вот с нижней позицией возникает небольшая проблема - именно из-за пресловутого главного меню, - не хочет оно выдавать свою высоту.

Помня, что в WinAPI есть некая функция SystemParametersInfo(), я поискал аналог в .NET CF. И нашел. Вот что получается, если я, например, хочу занять все рабочее пространство (за исключением главного меню приложения) текстовым полем:

       private System.Windows.Forms.TextBox mainText;

           Rectangle workArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;
           mainText.Size = new System.Drawing.Size(
               workArea.Width,
               workArea.Height - System.Windows.Forms.SystemInformation.MenuHeight);

Остальное, думаю, объяснять не надо.

Рабочий каталог приложения

Здесь ничего более умного, чем взять полное имя сборки и «выдрать» оттуда имя каталога мне не пришло:

           appPath = Path.GetDirectoryName(
               
Assembly.GetExecutingAssembly().GetModules()[0].FullyQualifiedName);

Примечания
Функция SystemParametersInfo() в WindowsCE (Mobile) API никуда не пропала. Читаем MSDN:

OS Versions: Windows CE 2.0 and later.
Header: Winuser.h.
Link Library: Coredll.lib

Поддержка буфера обмена в .NET CF

Среда, 4 Июль, 2007

Раньше для использования функций копирования/вставки на Pocket PC я использовал эмуляцию нажатия клавиш. После установки одного из последних сервис паков для .NET Compact Framework (я думаю, это был SP3), такой способ перестал работать. Я не смог дальше использовать Ctrl+C, Ctrl+V, Ctrl+X на клавиатуре :-( Устранение функционала при установке сервис паков нужно запретить законом. Я выбросил мой старый код и использовал API для получения полнофункциональной поддержки копи-паста в моем .NET Compact Framework приложении. Я решил оставить его простым в использовании…(прим. переводчика: а я решил сделать его универсальным и выкинул, на мой взгляд, лишнее).

Примечание: Не забудьте засеривать(disable) соответствующие пункты меню (копировать/вставить/и т.п.) если в фокусе нет текстовых полей (прим. переводчика: или в буфере обмена нет данных, или в текстовом поле нечего копировать/вырезать…). Сделайте это, например, так:

       private void editItem_Popup(object sender, System.EventArgs e)
       {
           pasteItem.Enabled = Clipboard.IsDataAvailable;
           copyItem.Enabled = mainText.SelectionLength > 0;
           cutItem.Enabled = mainText.SelectionLength > 0;
           selAllItem.Enabled = mainText.Text.Length > 0;
       }

Примечание от переводчика: В .NET CF 1.0 функционал буфера обмена не включен, т.к. он относится к WinCE, а не к .NET. По сути, в Palm like терминах, такое использование - хак. Как и любой другой, этот хак накладывает определенные ограничения. Один из них – невозможность использования копи-паста на десктоп машине (попробуйте и полюбуйтесь на эксепшн). Так теряется одно из преимуществ .NET CF – работать как на WinCE, так и на PC.

Исходный код класса Clipboard

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace PockEd
{
   
/// <summary>
   /// The Clipboard class is based on code from
   /// http://www.opennetcf.org/Forums/topic.asp?TOPIC_ID=125
   /// </summary>

   public class Clipboard
   {
       #region Win32 API ————————————————————
       
//See PlatformSDK/Include/WinUser.h
       private const uint CF_TEXT          =  1;
       
private const uint CF_UNICODETEXT   = 13;
       
//

       [DllImport(”Coredll.dll”)]
       
private static extern bool OpenClipboard(IntPtr hWndNewOwner);

       [DllImport(”Coredll.dll”)]
       private static extern bool CloseClipboard();

       [DllImport(”Coredll.dll”)]
       private static extern bool EmptyClipboard();

       [DllImport(”Coredll.dll”)]
       private static extern bool IsClipboardFormatAvailable(uint uFormat);

       [DllImport(”Coredll.dll”)]
       private static extern IntPtr GetClipboardData(uint uFormat);

       [DllImport(”Coredll.dll”)]
       private static extern IntPtr SetClipboardData(uint uFormat, IntPtr hMem);

       [DllImport(”Coredll.dll”)]
       private static extern IntPtr LocalAlloc(int uFlags, int uBytes);
       #endregion ——————————————————————-

       #region Internal support functions
       
private static IntPtr hMem = IntPtr.Zero;      

       /// <summary>
       /// Copy data from string to a pointer
       /// </summary>
       /// <param name=”dest”></param>
       /// <param name=”src”></param>
       private unsafe static void CopyToPointer(IntPtr dest, string src)
       {
           
int x;
           
char* pDest = (char*) dest.ToPointer();

           for (x = 0; x < src.Length; x++)
           {
               pDest[x] = src[x];
           }

           pDest[x] = ‘0′;  // Add the null terminator.
       }

       /// <summary>
       /// Convert a pointer to string
       /// </summary>
       /// <param name=”src”></param>
       /// <returns></returns>
       private unsafe static string ConvertToString(IntPtr src)
       {
           
int x;
           
char* pSrc = (char*) src.ToPointer();
           StringBuilder sb =
new StringBuilder();

           for (x = 0; pSrc[x] != ‘0′; x++)
           {
               sb.Append(pSrc[x]);
           }

           return sb.ToString();
       }
       #endregion 

       #region Public methods
       
/// <summary>
       /// Check if data is available on the clipboard
       /// </summary>
       public static bool IsDataAvailable
       {
           
get
           {                      
               
return IsClipboardFormatAvailable(CF_UNICODETEXT);
           }
       }

       /// <summary>
       /// Set data on the clipboard
       /// </summary>
       /// <param name=”strText”></param>
       /// <returns></returns>
       public unsafe static bool SetData(string strText)
       {
           
if (OpenClipboard(IntPtr.Zero) == false)
           {
               
return false;
           }

           hMem = LocalAlloc(0, ((strText.Length + 1) * sizeof(char)));

           if (hMem.ToInt32() == 0)
           {
               
return false;
           }

           CopyToPointer(hMem, strText);
           EmptyClipboard();
           SetClipboardData(CF_UNICODETEXT, hMem);
           CloseClipboard();

           return true;
       }

       /// <summary>
       /// Get data from the clipboard
       /// </summary>
       /// <returns></returns>
       public static string GetData()
       {
           IntPtr hData;
           
string strText;

           if (IsDataAvailable == false)
           {
               
return null;
           }

           if (OpenClipboard(IntPtr.Zero) == false)
           {
               
return null;
           }

           hData = GetClipboardData(CF_UNICODETEXT);
           if (hData.ToInt32() == 0)
           {
               
return null;
           }

           strText = ConvertToString(hData);
           CloseClipboard();

           return strText;
       }
         #endregion
   }
}

.NET CF Clipboard support and SP3
Egil Hogholt, среда, 2-е Марта 2005г. 15:59