First hands on with an Amazon Kindle 3

For Christmas 2010, and on a whim, I bought myself the 3G model of the Kindle 3. It finally arrived this morning, so it’s a late Christmas present.

I guess I’m a bit late getting one of these since they’ve been out for a while. The thing is, I normally try not to buy books. Don’t get me wrong, I’m always reading something, but usually it’s books that friends have lent me (hi Trent). I don’t like accumulating books because I’m always moving house (or country) and books are another thing to lag around. Right now I have a shelf of books at my parent’s house that has been sitting there for more than 5 years.

Lately I’ve been buying programming books though and those are usually very large and heavy. I left my copy of Code Complete 2 in NZ and I would have like to have brought it with me.

I’ve also been reading books on my iPhone through iBooks. So far only free classics, like Sherlock Holmes etc. Programming books in PDF format are too small for the iPhone.

I’d also been reading PDF ebooks on my netbook, by rotating the PDFs counter-clockwise in Acrobat reader, and then physically turning my netbook on its side. Very lo-tech. I’ve only been doing this at home though!

So iBooks on the iPhone and PDFs on the netbook were beginning to convince me that ebooks are the way forward. So I took the plunge with the Kindle.

IMG_2304 (Small)

Above: my 3 generations of ebook reader – netbook, Kindle 3 and iPhone.

File Formats

Kindle supports Amazon’s DRM native Kindle file format, .AZW. Yuck, I didn’t realise it was DRM until now. The good thing about reading .AZW ebooks (i.e. those you buy Amazon.com) is that you can choose the font size, line spacing etc. This means that .AZW files don’t really have page numbers, because depending on your zoom level and your line spacing settings a book could have more or less pages. Instead, each paragraph has a position number, and the little progress bar tells you how far through the book you are. Academics and students are up in arms over the lack of page numbers, because it means they can’t easily use ebooks in citations for academic writing.

PDF

Another format it supports is PDF files. This was important to me as mentioned earlier, because I have PDFs of a number of programming books that I wanted to try to read on the Kindle.

When viewing PDFs on the Kindle, the page numbers of the PDF are respected. But the downside of that is you can’t set your own font size and line spacing. The Kindle 3 is pretty small, about the size of a DVD cover. In fact I can fit the Kindle inside a DVD cover, which could make for an interesting and cheap carry case. And with all that bezel and the keyboard, the screen itself is even smaller. So when you view a PDF on that small screen in portrait mode the font size is really tiny. If you zoom in to make it more readable then you’ll have to constantly horizontal scroll which isn’t practical. A workaround is to put the Kindle into landscape mode, then the text is more readable and you only have to vertically scroll once to read a page.

IMG_2292 (Small)

Above: A bit too small to read comfortably…

IMG_2295 (Small)

Above: But in landscape mode it’s OK.

I only got the thing today so haven’t put any long hours in reading PDFs on it but I’ll update this post in a few weeks once I have.

Packaging

Kudos to Amazon for their “Certified Frustration Free” packaging. Pull one tab and the recyclable box opens, and there it is. No more “wrap rage”:

Worth it?

Now that I think about it, if ever there was a genre of books that quickly goes out of date, it’s programming books. So perhaps I shouldn’t have bought the Kindle for reading programming books on and instead just bought the books I want to read and then just leave them behind whenever I move house!

But I think it’ll still be worth it for buying and storing non-programming books with. Now I can build up my (ebook) library without worrying about accumulating luggage.

Update

Well it’s been a couple of weeks now, and as far as studying programming books cover to cover it works fine. But in terms of replacing the stack of programming reference books on my desk, it’s a no go. It takes too long to find anything.

SSRS the report definition is not valid hexadecimal value 0x00

I had this weird error the other day, when all my report definition .rdl files seemed to get corrupted somehow. I’m not sure what caused it but it might have been something to do with Tortoise SVN.

The error was something like “[rsInvalidReportDefinition] SSRS the report definition is not valid. Details: ‘.’, hexadecimal value 0x00 is an invalid character”. A screenshot is below:

When I opened the rdl it looked fine, and when I viewed the source of it in Visual Studio it also looked fine. But the telltale sign was that I could not  delete the last character in the .rdl file, a ‘>’ which closed off the final </Report> tag. In the end the fix was quite simple, all I did was delete that last </Report> tag and type it in again.

Hope this helps someone.

Add a link to another page on Windows Phone 7

Greetings from rainy Thailand, where I am on holiday. And on rainy days like today I’m learning Silverlight on the Windows Phone 7 platform.

If you need to link to another page in your Silverlight application on Windows Phone 7, the easiest way is to use a HyperlinkButton, like so:

 <HyperlinkButton Content="image page" NavigateUri="/ImagePage.xaml" />

It took me a bit of trial and error to realise that I needed to put a “/” at the start of the NavigateUri. MSDN’s help page didn’t have a code sample either.

Microsoft’s Tech Fest 2010 Auckland

A highlight of the year for Microsoft developers is Tech Ed, Microsoft’s only annual developer conference in NZ. Fortunately it’s held just down the road from my work, at Sky City Convention center so I’ve gotten to go to many sessions over the last few years.

The closing party, “Tech Fest” is always a highlight too.  A great venue, free beer, the chance to catch up with former colleagues and university friends, and of course the music. Every year that I’ve been Microsoft has done a great job of booking really good “world famous in NZ” bands to perform. 2007: Evermore. 2008: Opshop. 2009: Katchafire and Elemeno P. and 2010: Midnight Youth and Gin Wigmore.

Midnight Youth at Tech Fest 2010
Midnight Youth at Tech Fest 2010

Midnight Youth did a great job getting the crowd going. They sounded polished and the crowd was loving it. Then Gin came on. Now, I really like Gin’s music and her performance was great, but after being warmed up by a rock band the crowd wanted to party some more. The consensus amongst the punters was that Gin Wigmore should have warmed up the crowd first, and then Midnight Youth it finish off.

Gin Wigmore
Gin Wigmore at Tech fest 2010

Windows XP: password has expired, but you do not have permission to change the password

Since I work in IT, I’m occasionally asked to do tech support for family and ex-girlfriends, even though that’s not my area of expertise. Yesterday a friend had a problem I’d never seen before.

After bootup, she would be presented with the standard Windows XP login screen, with only one user account available for login. She clicks her name, enters her password, and then gets a message “your password has expired, you must enter a new one” or however the exact text goes.

So she enters a new password but then gets “You do not have permission to change your password”, which prevents her from logging in and takes her back to the login screen.

After trying a few different things to no avail, I tried googling the problem on my iPhone. But I couldn’t even find anyone with the same problem, let alone a solution.

Safe mode to the rescue

I tried booting Win XP into safe mode, and luckily at the login prompt I could now choose to login as Administrator (with no password!). Once inside I checked the account my friend was trying to login as, and sure enough the “User cannot change password” box was checked, and the “Password never expires” box was unchecked.

I restored the settings back to how they should be – password never expires and user can change the password. So that fixed the problem, but I was still wondering how those settings got set like that. My friend sheepishly admitted that she’d been playing around with those settings a while ago when she had password problems with her ISP. D’oh!

IE9 Beta renders blurry text

I’m just giving Internet Explorer 9 Beta a try, and my first impression is that the text in web pages looks blurry. See:

That’s the same web page viewed in 3 different browsers. Which one looks the worst to you?

Top – IE9

Middle – Chrome

Bottom – Firefox.

I also tried to create this post using wordpress.com’s post editor in IE9, but it didn’t work – I couldn’t change the cursor position using my mouse, and the image upload dialog box wouldn’t appear. Which is ironic since Microsoft is touting WordPress’s IE9 enhancements.

Update: Scott Hanselman has explained the IE9 blurry text issue.

Make yourself an admin in SQL Server 2008

The same colleague I was helping with their SQL install yesterday made another mistake. She mustn’t have set herself to be the db admin, because we’d get “Access denied” messages when she’d try to connect to her local db using SQL Server Management Studio.

Fortunately she found this script by Ward Beattie which fixes the problem. She only had to enter MSSQLSERVER as the SQL instance name.

SQL Server 2008 install requires Visual Studio 2008 SP1

I was helping a colleague setup their new Windows 7 developer box today, and we couldn’t install SQL Server 2008. SQL Server’s installation would complain:

A previous release of Microsoft Visual Studio 2008 is installed on this computer. Upgrade Microsoft Visual Studio 2008 to the SP1 before installing SQL Server 2008.

Well, we’d already installed Visual Studio 2008 and Visual Studio 2008 SP1 so why wasn’t it working? Even after a reboot Visual Studio 2008 was reporting that SP1 was already installed.

In the end we had to install VS 2008 SP1 a second time, and after that the SQL Server install worked fine.

What went wrong? The developer had started to install SQL Server 2008 while VS 2008 SP1 was still being installed.

Send an AS2 message with .NET

UPDATE Jan 2011: I’ve written a post about receiving AS2 messages.

A recent project at work required us to generate and send AS2 (Applicability Statement 2) messages. A colleague had written a project years ago using .NET 1.1 and a COM dll for encryption, so I set about porting that to Visual Studio 2008 and .NET 3.5.

I also wanted to use .NET’s System.Security libraries for encryption instead of having to use reflection to call the old COM dll.

It was quite difficult to get the signing and encryption working – in the end I had to resort to reverse engineering, by getting BizTalk to send an AS2 message and intercepting the message content with Fiddler, so that I could see how the AS2 messages and their headers were supposed to look.

Here’s some code which constructs an AS2 message and sends it over HTTP:

using System;
using System.IO;
using System.Net;

namespace WebTestPlugins.AS2Helpers
{
    public struct ProxySettings
    {
        public string Name;
        public string Username;
        public string Password;
        public string Domain;
    }

    public class AS2Send
    {
        public static HttpStatusCode SendFile(Uri uri, string filename, byte[] fileData, string from, string to, ProxySettings proxySettings, int timeoutMs, string signingCertFilename, string signingCertPassword, string recipientCertFilename)
        {
            if (String.IsNullOrEmpty(filename)) throw new ArgumentNullException("filename");

            if (fileData.Length == 0) throw new ArgumentException("filedata");

            byte[] content = fileData;

            //Initialise the request
            HttpWebRequest http = (HttpWebRequest)WebRequest.Create(uri);
           
            if (!String.IsNullOrEmpty(proxySettings.Name))
            {
                 WebProxy proxy = new WebProxy(proxySettings.Name);
               
                NetworkCredential proxyCredential = new NetworkCredential();
                proxyCredential.Domain = proxySettings.Domain;
                proxyCredential.UserName = proxySettings.Username;
                proxyCredential.Password = proxySettings.Password;

                proxy.Credentials = proxyCredential;
               
                http.Proxy = proxy;
            }

            //Define the standard request objects
            http.Method = "POST";

            http.AllowAutoRedirect = true;

            http.KeepAlive = true;

            http.PreAuthenticate = false; //Means there will be two requests sent if Authentication required.
            http.SendChunked = false;

            http.UserAgent = "MY SENDING AGENT";

            //These Headers are common to all transactions
            http.Headers.Add("Mime-Version", "1.0");
            http.Headers.Add("AS2-Version", "1.2"); 

            http.Headers.Add("AS2-From", from);
            http.Headers.Add("AS2-To", to);
            http.Headers.Add("Subject", filename + " transmission.");
            http.Headers.Add("Message-Id", "<AS2_" + DateTime.Now.ToString("hhmmssddd") + ">");
            http.Timeout = timeoutMs;

            string contentType = (Path.GetExtension(filename) == ".xml") ? "application/xml" : "application/EDIFACT";

            bool encrypt = !string.IsNullOrEmpty(recipientCertFilename);
            bool sign = !string.IsNullOrEmpty(signingCertFilename);

            if (!sign && !encrypt)
            {
                http.Headers.Add("Content-Transfer-Encoding", "binary");
                http.Headers.Add("Content-Disposition", "inline; filename=\"" + filename + "\"");
            }
            if (sign)
            {
                // Wrap the file data with a mime header
                content = AS2MIMEUtilities.CreateMessage(contentType, "binary", "", content);

                content = AS2MIMEUtilities.Sign(content, signingCertFilename, signingCertPassword, out contentType);

                http.Headers.Add("EDIINT-Features", "multiple-attachments");

            }
            if (encrypt)
            {
                if (string.IsNullOrEmpty(recipientCertFilename))
                {
                    throw new ArgumentNullException(recipientCertFilename, "if encrytionAlgorithm is specified then recipientCertFilename must be specified");
                }

                byte[] signedContentTypeHeader = System.Text.ASCIIEncoding.ASCII.GetBytes("Content-Type: " + contentType + Environment.NewLine);
                byte[] contentWithContentTypeHeaderAdded = AS2MIMEUtilities.ConcatBytes(signedContentTypeHeader, content);

                content = AS2Encryption.Encrypt(contentWithContentTypeHeaderAdded, recipientCertFilename, EncryptionAlgorithm.DES3);
               

                contentType = "application/pkcs7-mime; smime-type=enveloped-data; name=\"smime.p7m\"";
            }
           
            http.ContentType = contentType;           
            http.ContentLength = content.Length;

            SendWebRequest(http, content);

            return HandleWebResponse(http);
        }

        private static HttpStatusCode HandleWebResponse(HttpWebRequest http)
        {
            HttpWebResponse response = (HttpWebResponse)http.GetResponse();
           
            response.Close();
            return response.StatusCode;
        }

        private static void SendWebRequest(HttpWebRequest http, byte[] fileData)
        {
            Stream oRequestStream = http.GetRequestStream();
            oRequestStream.Write(fileData, 0, fileData.Length);
            oRequestStream.Flush(); 
            oRequestStream.Close();
        }
    }
}

And here’s the code which signs and/or encrypts the message:

using System;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;

namespace WebTestPlugins.AS2Helpers
{
    public static class EncryptionAlgorithm
    {
        public static string DES3 = "3DES";
        public static string RC2 = "RC2";
    }

    public class AS2Encryption
    {
        internal static byte[] Encode(byte[] arMessage, string signerCert, string signerPassword)
        {
            X509Certificate2 cert = new X509Certificate2(signerCert, signerPassword);
            ContentInfo contentInfo = new ContentInfo(arMessage);

            SignedCms signedCms = new SignedCms(contentInfo, true); // <- true detaches the signature
            CmsSigner cmsSigner = new CmsSigner(cert);

            signedCms.ComputeSignature(cmsSigner);
            byte[] signature = signedCms.Encode();

            return signature;
        }

        internal static byte[] Encrypt(byte[] message, string recipientCert, string encryptionAlgorithm)
        {
            if (!string.Equals(encryptionAlgorithm, EncryptionAlgorithm.DES3) && !string.Equals(encryptionAlgorithm, EncryptionAlgorithm.RC2))
                throw new ArgumentException("encryptionAlgorithm argument must be 3DES or RC2 - value specified was:" + encryptionAlgorithm);

            X509Certificate2 cert = new X509Certificate2(recipientCert);

            ContentInfo contentInfo = new ContentInfo(message);

            EnvelopedCms envelopedCms = new EnvelopedCms(contentInfo,
                new AlgorithmIdentifier(new System.Security.Cryptography.Oid(encryptionAlgorithm))); // should be 3DES or RC2

            CmsRecipient recipient = new CmsRecipient(SubjectIdentifierType.IssuerAndSerialNumber, cert);

            envelopedCms.Encrypt(recipient);

            byte[] encoded = envelopedCms.Encode();

            return encoded;
        }

        internal static byte[] Decrypt(byte[] encodedEncryptedMessage, out string encryptionAlgorithmName)
        {
            EnvelopedCms envelopedCms = new EnvelopedCms();

            // NB. the message will have been encrypted with your public key.
            // The corresponding private key must be installed in the Personal Certificates folder of the user
            // this process is running as.
            envelopedCms.Decode(encodedEncryptedMessage);

            envelopedCms.Decrypt();
            encryptionAlgorithmName = envelopedCms.ContentEncryptionAlgorithm.Oid.FriendlyName;

            return envelopedCms.Encode();
        }

    }
}

For completeness you’ll also need the AS2MimeUtilities class. Disclaimer: I didn’t write this class, it’s legacy hungarian-notated goodness.

using System;
using System.Text;

namespace WebTestPlugins.AS2Helpers
{
    /// <summary>
    /// Contains a number of useful static functions for creating MIME messages.
    /// </summary>
    public class AS2MIMEUtilities
    {
        public const string MESSAGE_SEPARATOR = "\r\n\r\n";
        public AS2MIMEUtilities()
        {
        }

        /// <summary>
        /// return a unique MIME style boundary
        /// this needs to be unique enought not to occur within the data
        /// and so is a Guid without - or { } characters.
        /// </summary>
        /// <returns></returns>
        protected static string MIMEBoundary()
        {
            return "_" + Guid.NewGuid().ToString("N") + "_";
        }

        /// <summary>
        /// Creates the a Mime header out of the components listed.
        /// </summary>
        /// <param name="sContentType">Content type</param>
        /// <param name="sEncoding">Encoding method</param>
        /// <param name="sDisposition">Disposition options</param>
        /// <returns>A string containing the three headers.</returns>
        public static string MIMEHeader(string sContentType, string sEncoding, string sDisposition)
        {
            string sOut = "";

            sOut = "Content-Type: " + sContentType + Environment.NewLine;
            if (sEncoding != "" )
                sOut += "Content-Transfer-Encoding: " + sEncoding + Environment.NewLine;

            if (sDisposition != "" )
                sOut += "Content-Disposition: " + sDisposition + Environment.NewLine;

            sOut = sOut + Environment.NewLine;

            return sOut;
        }

        /// <summary>
        /// Return a single array of bytes out of all the supplied byte arrays.
        /// </summary>
        /// <param name="arBytes">Byte arrays to add</param>
        /// <returns>The single byte array.</returns>
        public static byte[] ConcatBytes(params byte[][] arBytes)
        {
            long lLength = 0;
            long lPosition = 0;

            //Get total size required.
            foreach(byte[] ar in arBytes)
                lLength += ar.Length;

            //Create new byte array
            byte[] toReturn = new byte[lLength];
               
            //Fill the new byte array
            foreach(byte[] ar in arBytes)
            {
                ar.CopyTo(toReturn,lPosition);
                lPosition += ar.Length;
            }

            return toReturn;
        }

        /// <summary>
        /// Create a Message out of byte arrays (this makes more sense than the above method)
        /// </summary>
        /// <param name="sContentType">Content type ie multipart/report</param>
        /// <param name="sEncoding">The encoding provided...</param>
        /// <param name="sDisposition">The disposition of the message...</param>
        /// <param name="abMessageParts">The byte arrays that make up the components</param>
        /// <returns>The message as a byte array.</returns>
        public static byte[] CreateMessage(string sContentType, string sEncoding, string sDisposition, params byte[][] abMessageParts)
        {
            int iHeaderLength=0;
            return CreateMessage(sContentType, sEncoding, sDisposition, out iHeaderLength, abMessageParts);
        }
        /// <summary>
        /// Create a Message out of byte arrays (this makes more sense than the above method)
        /// </summary>
        /// <param name="sContentType">Content type ie multipart/report</param>
        /// <param name="sEncoding">The encoding provided...</param>
        /// <param name="sDisposition">The disposition of the message...</param>
        /// <param name="iHeaderLength">The length of the headers.</param>
        /// <param name="abMessageParts">The message parts.</param>
        /// <returns>The message as a byte array.</returns>
        public static byte[] CreateMessage(string sContentType, string sEncoding, string sDisposition, out int iHeaderLength, params byte[][] abMessageParts)
        {
            long lLength = 0;
            long lPosition = 0;

            //Only one part... Add headers only...
            if (abMessageParts.Length==1)
            {
                byte[] bHeader = ASCIIEncoding.ASCII.GetBytes(MIMEHeader(sContentType, sEncoding, sDisposition));
                iHeaderLength = bHeader.Length;
                return ConcatBytes(bHeader, abMessageParts[0]);
            }
            else
            {
                // get boundary and "static" subparts.
                string sBoundary = MIMEBoundary();
                byte[] bPackageHeader = ASCIIEncoding.ASCII.GetBytes(MIMEHeader(sContentType + "; boundary=\"" + sBoundary + "\"", sEncoding, sDisposition));
                byte[] bBoundary = ASCIIEncoding.ASCII.GetBytes(Environment.NewLine + "--" + sBoundary + Environment.NewLine);
                byte[] bFinalFooter = ASCIIEncoding.ASCII.GetBytes(Environment.NewLine + "--" + sBoundary + "--" + Environment.NewLine);

                //Calculate the total size required.
                iHeaderLength = bPackageHeader.Length;

                foreach(byte[] ar in abMessageParts)
                    lLength += ar.Length;
                lLength += iHeaderLength + bBoundary.Length*abMessageParts.Length +
                    bFinalFooter.Length;

                //Create new byte array to that size.
                byte[] toReturn = new byte[lLength];
               
                //Copy the headers in.
                bPackageHeader.CopyTo(toReturn, lPosition);
                lPosition += bPackageHeader.Length;

                //Fill the new byte array in by coping the message parts.
                foreach(byte[] ar in abMessageParts)
                {
                    bBoundary.CopyTo(toReturn, lPosition);
                    lPosition += bBoundary.Length;

                    ar.CopyTo(toReturn,lPosition);
                    lPosition += ar.Length;
                }

                //Finally add the footer boundary.
                bFinalFooter.CopyTo(toReturn, lPosition);

                return toReturn;
            }
        }

        /// <summary>
        /// Signs a message and returns a MIME encoded array of bytes containing the signature.
        /// </summary>
        /// <param name="arMessage"></param>
        /// <param name="bPackageHeader"></param>
        /// <returns></returns>
        public static byte[] Sign(byte[] arMessage, string signerCert, string signerPassword, out string sContentType)
        {
            byte[] bInPKCS7 = new byte[0];

            // get a MIME boundary
            string sBoundary = MIMEBoundary();

            // Get the Headers for the entire message.
            sContentType = "multipart/signed; protocol=\"application/pkcs7-signature\"; micalg=\"sha1\"; boundary=\"" + sBoundary + "\"";
           
            // Define the boundary byte array.
            byte[] bBoundary = ASCIIEncoding.ASCII.GetBytes(Environment.NewLine + "--" + sBoundary + Environment.NewLine);
               
            // Encode the header for the signature portion.
            byte[] bSignatureHeader = ASCIIEncoding.ASCII.GetBytes(MIMEHeader("application/pkcs7-signature; name=\"smime.p7s\"", "base64", "attachment; filename=smime.p7s"));
   
            // Get the signature.
            byte[] bSignature = AS2Encryption.Encode(arMessage, signerCert, signerPassword);
           
            // convert to base64
            string sig = Convert.ToBase64String(bSignature) + MESSAGE_SEPARATOR;
            bSignature = System.Text.ASCIIEncoding.ASCII.GetBytes(sig);

            // Calculate the final footer elements.
            byte[] bFinalFooter = ASCIIEncoding.ASCII.GetBytes("--" + sBoundary + "--" + Environment.NewLine);

            // Concatenate all the above together to form the message.
            bInPKCS7 = ConcatBytes(bBoundary, arMessage, bBoundary,
                bSignatureHeader, bSignature, bFinalFooter);

            return bInPKCS7;
        }
    }
}

Anyway, I hope this helps someone out there because I couldn’t find a sample of how to send an AS2 message in .NET.