Thursday, October 30, 2008

This is the third post in the series on accessing Live Services from Compact .NET Framework applications. So far we have covered authentication and Live Contacts. The next service we are going to discuss is Live Photos.

There are 2 separate APIs available for working with Live Photos. One is WebDAV-based and the other is ATOM API. While both can be used successfully to upload photos, create albums etc, WebDAV is generally considered to be deprecated in favor of more modern SOAP APIs such as ATOM Publishing Protocol.

Photo ATOM documentation tells us that the format of the ATOM request for Photo access is as follows:

[METHOD] /[path to resource] HTTP/1.1
Host: cumulus.services.live.com
User-Agent: [Name of the user agent, typically the client app or service.]
Authentication: DelegatedToken dt="[delegation token]"

We are going to use it with one modification. Instead of Authentication: DelegatedToken dt="[delegation token]" we will add Authorization: WLID1.0 t="[auth token]".

Similar to what we have done previously, the [path to resource] part is constructed as /[live ID]/[Service]/[Path to item] where [live ID] is the WLID of the current account (e.g. someone@live.com), [service] is AtomSpacesPhotos and [Path to item] is a combination of Album and Photo identifier. And example of a folder URL:
https://cumulus.services.live.com/someone@live.com/AtomSpacesPhotos/Folders(231)

An example of a photo URL:
https://cumulus.services.live.com/someone@live.com/AtomSpacesPhotos/Folders(196)/Photos(200)/$value
This one has content type image/jpeg. It returns image binary stream in jpeg format. An HTTP POST to the above url with the request body set to the image binary stream and content type set to image/jpeg will update the image.

To retrieve a list of all top-level folders use https://cumulus.services.live.com/someone@live.com/AtomSpacesPhotos/Folders.

[this is work in progress. to be continued. code samples coming shortly]

10/30/2008 10:37:08 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Wednesday, October 29, 2008

Now that we know how to acquire authentication ticket from Windows Live, we can put it to work accessing the user's contact list. In general a number of requests to the Live Services follow a common pattern. It is an HTTP(s) request to https://cumulus.services.live.com/{user identity}/{Service}/

Depending on the operation the request could be GET, PUT, DELETE, POST or other. User identity is the email address that is the Windows Live ID. Service can be one of the supported services. E.g. for contacts it is LiveContacts.

In order to provide the authentication ticket to the server the caller needs to include a custom HTTP header - "Authorization" and set its value to WLID1.0 t="{base64 auth ticket}" where {base64 auth ticket} is the ticket retrieved from the authentication server.

string url = "https://cumulus.services.live.com/{0}/LiveContacts/";
url = string.Format(url, userID);
string result = SendHttpRequest(ref url, string.Format("WLID1.0 t=\"{0}\"",ticket), "GET", System.Net.HttpStatusCode.OK, "", "", "");
XmlDocument docContacts = new XmlDocument();
docContacts.LoadXml(result);

The code above will retrieve the entire contact list as described here.

In order to create a contact, a POST to https://cumulus.services.live.com/{user identity}/LiveContacts/Contacts is required. The post contents should be an xml message formatted in accordance with Live contacts schema and having appropriate fields filled in. It is wort mentioning that WindowsLiveID field is not just an email address. It has to be a valid Live ID. Another thing to keep in mind is that almost any content-related error will cause HTTP code 403 (supposedly, for security reasons).

To delete a contact a DELETE verb is used. In order to indetify the contact being deleted or updated the application should use contact GUID provided in the ID tag:

<Contact>
   <
ID>ead5572c-9c61-43a2-b71f-f2412e283598</ID
>
   <
AutoUpdateEnabled>false</AutoUpdateEnabled
>
   <
LastChanged>2007-11-19T20:26:57.2230000Z</LastChanged
>
   <
Profiles
>
      <
Personal
>
         <
FirstName>Alex</FirstName
>
         <
LastName>Feinman</LastName
>
         
<Gender>Male</Gender
>
      
</Personal
>
   </
Profiles
>
</Contact
>

The post url will look like this:

https://cumulus.services.live.com/someone@example.com/LiveContacts/contacts/Contact(ead5572c-9c61-43a2-b71f-f2412e283598)

Same url format is using for updates. Keep in mind that to update data you need to use PUT verb.

10/29/2008 10:41:37 AM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 

Windows Live Services is an infrastructure (I am trying to avoid words like 'ecosystem') that provides multiple services (contacts, photo albums, blogs, web storage, mail etc) available via federated authentication mechanism. While the majority of these services are exposed to the user as web pages, they are also available to regular applications. Microsoft own such as LIve Mail, Live Writer, Live Gallery as well as 3rd party via  Live Services SDK. I am going to provide a short introduction on accessing some of the Live Services programmatically from Compact .NET Framework applications.

The starting point of any Live Services interaction being authenticating the user, it is only natural that Microsoft has provided a .NET component that handles the authentication for applications. This component however is offered only for desktop applications. Nevertheless behind every Live Service interaction there is a web request of a sort, which suggests that it can be emulated using NETCF code. While there are several authentication options available in Windows Live, there is just one that is really suitable for smart clients - RPS (Relying Party Suites). Underneath it is simply an HTTP POST of some XML data over secure channel. Upon successful request the application receives an encoded authentication ticket valid typically for 24 hours (the exact expiration time is provided with the ticket). To request the ticket an application sends XML message to https://dev.login.live.com/wstlogin.srf. The message uses content type "application/soap+xml" and looks like this:

<s:Envelope
   
xmlns:s = "http://www.w3.org/2003/05/soap-envelope"
   
xmlns:wsse = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
   
xmlns:saml = "urn:oasis:names:tc:SAML:1.0:assertion
   
xmlns:wsp = "http://schemas.xmlsoap.org/ws/2004/09/policy"
   
xmlns:wsu = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
   
xmlns:wsa = "http://www.w3.org/2005/08/addressing"
   
xmlns:wssc = "http://schemas.xmlsoap.org/ws/2005/02/sc"
   
xmlns:wst = "http://schemas.xmlsoap.org/ws/2005/02/trust">

<s:Header>
   <
wlid:ClientInfo xmlns:wlid = "http://schemas.microsoft.com/wlid">
      <
wlid:ApplicationID>10</wlid:ApplicationID>
   </
wlid:ClientInfo>
   <
wsa:Action s:mustUnderstand = "1">http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</wsa:Action>
   <
wsa:To s:mustUnderstand = "1">https://dev.login.live.com/wstlogin.srf</wsa:To>
   <
wsse:Security>
      <
wsse:UsernameToken wsu:Id = "user">
         <
wsse:Username>example@live.com</wsse:Username>
         <
wsse:Password>S3cr3t</wsse:Password>
      </
wsse:UsernameToken>
   </
wsse:Security>
</
s:Header>

<s:Body>
   <
wst:RequestSecurityToken Id = "RST0">
      <
wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>
      <
wsp:AppliesTo>
         <
wsa:EndpointReference>
            <
wsa:Address>http://live.com</wsa:Address>
         </
wsa:EndpointReference>
      </
wsp:AppliesTo>
      <
wsp:PolicyReference URI = "MBI"></wsp:PolicyReference>
   </
wst:RequestSecurityToken>
</
s:Body>

</s:Envelope>

Naturally, in the above XML the wsse:Username and wsse:Password will have to be substituted with actual user-provided credentials. In response we expect a standard SOAP message that has action set to http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue. In the body of this message of particular interest are the following parts:

<wst:Lifetime>
   <
wsu:Created>2008-10-29T14:38:23Z</wsu:Created>
   <
wsu:Expires>2008-10-29T22:38:23Z</wsu:Expires>
</
wst:Lifetime>

and

<wst:RequestedSecurityToken>
   <
wsse:BinarySecurityToken Id="Compact0">t=[very long Base64-encoded string here]</wsse:BinarySecurityToken>
</
wst:RequestedSecurityToken>

The former tells us the ticket validity time range. There is no need to reacquire a ticket until Expires date. The latter contains the actual ticket. Notice that while it has a format t=IKnvriu73nf..., the actual requests sent later to the Live servers will require the Base64 part to be quoted like this: t="IKnvriu73nf..."

Of course the application should be prepared to deal with authentication failures. A logon failure (bad password) will result in a message that looks like this:

  <S:Body>
    <S:Fault>
      <S:Code>
        <S:Value>S:Sender</S:Value>
        <S:Subcode>
          <S:Value>wst:FailedAuthentication</S:Value>
        </S:Subcode>
      </S:Code>
      <S:Reason>
        <S:Text xml:lang="en-US">Authentication Failure</S:Text>
      </S:Reason>
      <S:Detail>
        <psf:error>
          <psf:value>0x80048821</psf:value>
          <psf:internalerror>
            <psf:code>0x80041012</psf:code>
            <psf:text>The entered and stored passwords do not match.</psf:text>
          </psf:internalerror>
        </psf:error>
      </S:Detail>
    </S:Fault>
  </S:Body>
</S:Envelope>

Notice that it conveniently includes human-readable failure reason as well as a hex error code.

The code to request the ticket would look like this (sans error handling):

public static string AcquireTicket(string userName, string password)
{
   const string url = @"https://dev.login.live.com/wstlogin.srf";
   HttpWebRequest request = WebRequest.Create(url)as HttpWebRequest ;
   request.Method = "POST";
   request.ContentType = "application/soap+xml; charset=UTF-8";
   request.Timeout = 10 * 1000; // Wait for at most 10 seconds
   byte[] bytes = System.Text.Encoding.UTF8.GetBytes(string.Format( soapEnvelope, userName, password));
   request.ContentLength = bytes.Length;
   Stream reqStream = request.GetRequestStream();
   reqStream.Write(bytes, 0, bytes.Length);
   reqStream.Close();
   WebResponse response;
   response = request.GetResponse();
   string xml;
   using (System.IO.StreamReader reader = new System.IO.StreamReader(response.GetResponseStream()))
      xml = reader.ReadToEnd();
   response.Close();
   XmlDocument document = new XmlDocument();
   document.LoadXml(xml);
   XmlNamespaceManager nsManager = new XmlNamespaceManager(document.NameTable);
   nsManager.AddNamespace("wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
   XmlNode node = document.SelectSingleNode(@"//wsse:BinarySecurityToken/text()", nsManager);
   if (node == null)
      return null; // The wsse:BinarySecurityToken element is missing. Examine the xml for error information
   else
      return node.Value;
}

To be continued: 

10/29/2008 9:13:32 AM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  | 
 Thursday, February 28, 2008

It is sometimes said that the reason Soviet Union has produced so many outstanding scientists is that a lot of attention was being paid to introducing kids to science early and in a way that kept them interested. Indeed, a large amount of popular science books has been printed over the years as well as translations made of the best ones offered in the rest of the world. Martin Gardner and Richard Feinman, absolutely fascinating Robert Wood biography, Smullyan, Soviet authors Perelman and Makovetsky - all of those made me fall in love with physics, mathematics, mechanics, chemistry as a kid. And of course these books were fairly accurate as they were either authored or reviewed by respected professionals.

Times change and these days people find that you cannot blindly trust books you buy. Quite recently a very lively discussion took place in one of the LiveJournal communities (very popular in Russia). It was about a book recently printed in Moscow called "Unabridged Encyclopedia of Astronomy". The book that contains 25000 articles, has many entries that make even a casual student of science do a doubletake (some of it could be explained by the fact that the entire author team somehow did not have a single specialist in astronomy or even general physics among them).

Here are some gems (translated from original Russian as closely as possible)

"Gravitational waves - are emitted by electrical charge oscillating in space"

"Barnard star - a stationary star with visual magnitude of 9.5m... Known for being fast-moving..."

"Visible radiation - radiation that is not only visible to the naked eye, but to the special astronomical equipment and devices..."

"Visible light - light being radiated by a heated body..."

"Escape velocity - [is] defined as speed required for a man-made satellite to reach the Earth orbit. Equals 12km/s"

"Galactic Cannibalism (Extragalactic Astronomy) - a part of Astronomy dealing with celestial bodies (stars, galaxies, quasars etc) that exist outside our Galaxy"

"Ultraviolet radiation - radiation emitted by the Sun and stars"

"Interference - wave oscillation produced by the light source generates so called spherical wave fronts"

"Polar Star - the main star L of Ursa Minor constellation and the brightest star of the northern hemisphere"

"Rigel - the brightest star in the constellation of Orion and in the entire sky"

"Lynx - one of the constellations of the southern hemisphere"

"Triton - a constellation discovered by Lassell in 1846. It's mass is calculated at 2.14x10^22 kg"

The next one is tricky. It makes no sense at all in Russian, so be prepared for the same in English translation.

"Phase angle - an angle situated at a distance from the Sun to the Moon as well as from the Moon to the Earth"

"Fundamental Astronomy - modern physical-mathematical discipline growing interdependent with advances in science and technology"

Fortunately, some astute readers (one of them employed by Moscow planetarium - must know her astronomy, eh?) were able to spot it and raise some ruckus. As they were not able to get any response from the publisher, someone suggested to make a formal complaint to the russian authorities, invoking Consumer Protection Act. We'll see how it goes.

Life | Rant | Science
2/28/2008 3:22:35 AM (Pacific Standard Time, UTC-08:00)  #    Comments [3]  | 
 Tuesday, February 26, 2008

Raffaele Rialdi pointed out that attempting to run RTF Host from Compact Framework 3.5 Power Toys on an emulator produces the following error:

This is caused by RTF getting confused because the default CoreCon transport on the emulator is DMA (DeviceDMA.dll). Here are the steps to add emulator as a manual Tcp connection. Not that many will need it, but it is useful for a demo

1. Start Emulator using Device Emulator Manager.
2. Configure Network and Storage card folder
3. Copy to storage card folder the following file: "C:\Program Files\Common
Files\microsoft shared\CoreCon\1.0\Target\wce400\armv4i\TCPConnectionA.dll"
4. In File ExplorerNavigate to \Windows\Corecon1.1. If you don't see it
there, connect to the emulator from Studio
5. Launch ClientShutdown (you should see a guid-named folder to appear)
6. Copy \Storage Card\TcpConnectionA.dll to \Windows
7. Go to \Windows and delete DeviceDMA.dll (if you can't, you forgot to
launch ClientShutdown)
8. Go back to \Windows\Corecon1.1 and launch ConmanClient.exe
9. Launch RTF Host. Enjoy

2/26/2008 6:46:07 AM (Pacific Standard Time, UTC-08:00)  #    Comments [1]  | 
 Saturday, December 29, 2007

Cyberlink was kind enough to let me evaluate the latest version of their PowerDVD Ultra product - about a year ago. Yet I procrastinated. Now, a year later I found myself in posession of a brand-new Media Center machine (does the phrase "Dude, you are getting a Dell" sound familiar?), which is connected to a brand new 1080P Samsung LCD TV. I've slightly upgraded it with an LG GGC-H20L Blu-Ray/HD-DVD Reader and realized, how close I to being finally able to watch HD DVDs on a Media Center machine (instead of Xbox 360). This is where I remembered about Cyberlink generous offer.

After a painless 100MB download (you can buy all of the Cyberlink products electronically, directly on their web site, and depending on the speed of your connection, you will get to install it in 5-10 min), I ran the setup and was shortly rewarded with the activation dialog. I am not big fan of software activation (Windows or any other), but as far as the activation experiences go, this one was seamless and easy.

PowerDVD comes with a companion application called BluRay Advisor (there is also nearly identical HD DVD Advisor). This application checks your system for being ready to play HD content. The last time I ran it a year ago, I got a message saying that my display card was underpowered, my CPU - barely sufficient (P IV 3.6GHz) and my TV entirely unsuitable due to the lack of HDCP support. This time I had a green light on every item, except the video driver - the Advisor did not recognize the driver version and indicated the status as unknown.

My TV uses so called 10' setup, meaning that normally there is no keyboard or mouse connected to it (well, there is a Media Center keyboard, just in case, but I rarely use it). Earlier versions of PowerDVD did not fare well with the Media Center remote, but the current one does not seem to have any problems. I was able to control it perfectly well, except of going into settings and such. For day-to-day needs, such as watching DVD MCE Remote gives you all control you need.

PowerDVD fully supports HD DVD menu system. My experience with it did not differ from Xbox 360 one.

There are few minor annoyances - nothing critical, but I have to mention them:
 - when starting playback, Vista pops a message saying that the application is not compatible with Windows Aero and that Windows will switch into basic mode.
 - I was not able to figure out how to select the disk to play. The machine has a regular DVD drive, an HD DVD drive and a DVD changer. By default the player picks the first of them. I'm sure there is a setting somewhere, but I wish it were more accessible. As it is I have to open Computer window and select the disc I want to play
- The status overlay that's shown in the upper right corner when you are controlling volume, is way too small. From the couch it is nearly impossible to read.

I want to emphasize, that the above list is of really minor issues. The application is very good in every way and does the job of playing DVDs and HD DVDs nearly perfectly. The picture quality is superb, there are no artifacts or playback delays, missed frames and other unpleasant occurences that might mire your viewing experience.

In conclusion I wanted to mention the auto update feature. It is not something new - Microsoft, Adobe, Intel and quite a few others have it. Nevertheless it is gratifying to know that you don't have to check the web site for updates constantly (and who doesn't love updates) - it's done for you.

12/29/2007 4:17:35 PM (Pacific Standard Time, UTC-08:00)  #    Comments [1]  | 
 Wednesday, December 26, 2007

This one has been actually gleaned through a usenet post, but needs a wider coverage

Trying to use dwAlphaConst member of DDOVERLAYFX structure to specify overall overlay transparency results in E_INVALIDARG error

As explained here, there is a clever bug in the parameter checking logic that results in the API soundly rejecting any combination of dwAlphaConst and dwAlphaConstBitDepth, unless dwAlphaConst == 1 << dwAlphaConstBitDepth. This is not fixed as of WM 6 AKU4. Interestingly enough the actual value of dwAlphaConstBitDepth seems to be ... ignored, once the parameter check is done. The bit depth used is always 8 bit.

  • The net result of this bug is that the allowed values for the dwAlphaConstBitDepth/dwAlphaConst and the resulting opacity are
    1/2 - 0.78%
    2/4 - 1.6%
    3/8 - 3.1%
    4/16 - 6.2%
    5/32 - 12.5%
    6/64 - 25%
    7/128 - 50%

Not using DDOVER_ALPHACONSTOVERRIDE gives you of course 100% (opaque overlay). In my experience anything under 5 (12.5%) is too small to be useful, but YMMV. Here is the code snippet:

if ( fUseAlpha )
{
    if ( dwAlpha > 8 ) // Bug in ddraw
        dwAlpha = 8;
    if ( dwAlpha < 8 )
    {
        dwUpdateFlags |= DDOVER_ALPHACONSTOVERRIDE;
        ovfx.dwAlphaConst = 1 << dwAlpha;
        ovfx.dwAlphaConstBitDepth = dwAlpha;
    }
}

12/26/2007 1:50:12 AM (Pacific Standard Time, UTC-08:00)  #    Comments [1]  | 
Call to IDirectDrawSurface::Blt returns E_INVALIDARG

While possible causes for this error are numerous, I wanted to point out a specific one that is not mentioned anywhere: either source or destination rectangle is empty ( zero width or height )

12/26/2007 1:25:03 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 

It's been a while. Things were a bit hectic. Nevertheless I am back.

These days I am working on a rather interesting project involving DirectDraw on Windows Mobile 6. Without going into the project details, I wanted to share some experiences. So far, a number of time I would run into an issue, and of course not it would not be explained/documented/covered anywhere where the Google search takes you. It struck me that I must be not the first one to hit these issues, and if I list them here, chances are that Google search will be more productive for the next poor sod to plow through underdocumented (to put it mildly), convoluted (again, to put it mildly) and unforgiving API such as DirectDraw. I am going to have multiple posts with keywords facilitating search. If a month from now you will find out that I produced all of one post (this one seems to be on track so far. Chances for it not making it are slim), don't judge me too hard. I am away from home for the 3rd week in a row, having missed Christmas, but should be back for the New Year. Ok, here goes.


Call to IDirectDrawSurface::Blt returns DDERR_SURFACEBUSY

The documentation says to check for other threads accessing your surface at the same time. The group search suggests to search for mismatching Lock/Unlock calls. The actual cause turned to be a missed call to IDirectDrawSurface::ReleaseDC (after a successfull call to IDirectDrawSurface::GetDC). If you think about it, internally these calls must be using Lock/Unlock, so cudos to Group search and boo to MSDN documentation

 

12/26/2007 1:20:39 AM (Pacific Standard Time, UTC-08:00)  #    Comments [0]  | 
 Friday, June 01, 2007

I was quite surprised to learn that apparently I have missed an important new feature of Windows Mobile 6 (Professional)- inking and ink serialization support. Gone are the days when a developer had to tinker with oh-so-temperamental InkX control. Now everyone and his brother can take advantage not only of high-quality precision ink support with smoothing and serialization, but also of handwriting recognition built into Windows Mobile 6 Professional. To quote the documentation - "It provides a rich inking experience, through high quality curve–fitted ink with anti-aliasing, transparent ink, and highlighter ink. It provides an API for Ink collection, data management, rendering, and recognition. It also provides Ink controls to support the note–taking scenario."

Another important feature is interoperability and serialized data format compatibility with ink support on Tablet PC.

While all of this is nice, there is slight bit of bad news - using this rich set of goodies requires C++. There are 2 ways to use ink in Windows Mobile 6 - InkCanvas control and an COM automation library. InkCanvas control offers the ability to use a regular Win32 control (similar to InkX) to write, highlight, collect ink etc via a set of Windows messages. COM automation library on the other hand allows accessing the entire set of features offered by WISP. And there is no managed wrapper for the time being.

As a public service, we at OpenNETCF.com are proud to offer WISPLite managed wrapper. The wrapper offers the entire WISPLite functionality (although not every method of every interface has been tested). There is InkControl class, which wraps InkCanvas, and a OpenNETCF.WindowsMobile.Ink namespace that contains imported COM interfaces. Some of the interfaces do not wrap cleanly, so a bit of coding is needed.

Here is what the demo app looks like (warning, before running it, change line 132 in Form1.cs to be

inkControl1.SetPenStyle((float)trackBar1.Value, penColor, penType); )

Saving ink data to a file:

using (SaveFileDialog fd = new SaveFileDialog())
{
  fd.Filter =
"Ink files (*.isf)|*.isf|All files (*.*)|*.*";
 
if (fd.ShowDialog() == DialogResult.OK)
  {
   
byte[] data = (byte[])inkControl1.GetInkData(IC_INKENCODING.BINARY);
    FileStream stm = File.OpenWrite(fd.FileName);
    stm.Write(data, 0, data.Length);
    stm.Close();
  }
}

Getting ink as bitmap and retrieving recongnition result:

pbPreview.Image = inkControl1.GetInkDataAsBitmap();
lblReco.Text = inkControl1.RecognizedText;

In conclusion I'd like to ask to report problems with this wrapper to this blog's comments.

6/1/2007 2:20:22 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [3]  | 
 Tuesday, April 17, 2007

Sometimes when trying to connect Visual Studio t a device, especially to a custom CE platform, it is helpful to see if there are any problems reported by the CoreCon components on the device side. CoreCon components (ConmanClient, trasnport DLLs, edbgtl.dll and edm.exe) all have an integrated logging facility, which can be used by a developer for troubleshooting.

To enable Corecon debug log you can set the following under HKLM\Software\Microsoft\VSD\Logging

VSD_LogEnabled: DWORD:1,0

VSD_LogToDebugger: DWORD:1,0

VSD_LogToConsole: DWORD: 1,0

VSD_LogToFile: DWORD:1,0

VSD_LogLevel: DWORD - set to at least 4, up to 9

VSD_LogFile: REG_SZ (default VSDLogFile.txt)

Keep in mind that the ConMan log can be quite chatty, so enable it sparingly and only when needed

4/17/2007 12:12:44 PM (Pacific Daylight Time, UTC-07:00)  #    Comments [0]  |