Jakub Florczyk - Blog o programowaniu .NET i Android

Programista praktyczny

Month: September, 2009

HMACSHA1 (HMAC-SHA1) w Compact Framework

Ostatnio w wolnych chwilach pracuje nad protokołem OAuth dla Pocket Blip-a wraz z Filipem Tepperem z Blip.pl, który był tak miły i udostępnił mi jedno konto testowe. Problemy pojawiły się już na początku bo w bibliotekach Compact Framework brakuje implementacji HMAC-SHA1. Chciałem sprawę rozwiązać przez użycie OPENNETCF, które mają ten algorytm zaimplementowany, ale niestety pojawiały się błędy których nijak nie mogłem rozwiązać, bo sypały je biblioteki CF-a.

Dlatego jak w poprzednim artykule o PointF rozwiązałem problem używając Reflector-a i dzięki drobnym poprawkom klasa działa lepiej i się nie sypie jak wersja z OPENNETCF:

namespace System.Security.Cryptography
{
    public abstract class KeyedHashAlgorithm : HashAlgorithm
    {
        // Fields
        protected byte[] KeyValue;

        // Methods
        protected KeyedHashAlgorithm()
        {
        }

        public static KeyedHashAlgorithm Create()
        {
            return Create("System.Security.Cryptography.KeyedHashAlgorithm");
        }

        public static KeyedHashAlgorithm Create(string algName)
        {
            return (KeyedHashAlgorithm)CryptoConfig.CreateFromName(algName);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.KeyValue != null)
                {
                    Array.Clear(this.KeyValue, 0, this.KeyValue.Length);
                }
                this.KeyValue = null;
            }
            base.Dispose(disposing);
        }

        // Properties
        public virtual byte[] Key
        {
            get
            {
                return (byte[])this.KeyValue.Clone();
            }
            set
            {
                if (base.State != 0)
                {
                    throw new CryptographicException("Cryptography_HashKeySet");
                }
                this.KeyValue = (byte[])value.Clone();
            }
        }
    }

    public abstract class HMAC : KeyedHashAlgorithm
    {
        // Fields
        private int blockSizeValue = 0x40;
        internal HashAlgorithm m_hash1;
        internal HashAlgorithm m_hash2;
        private bool m_hashing;
        internal string m_hashName;
        private byte[] m_inner;
        private byte[] m_outer;

        // Methods
        protected HMAC()
        {
        }

        public static HMAC Create()
        {
            return Create("System.Security.Cryptography.HMAC");
        }

        public static HMAC Create(string algorithmName)
        {
            return (HMAC)CryptoConfig.CreateFromName(algorithmName);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.m_hash1 != null)
                {
                    this.m_hash1.Clear();
                }
                if (this.m_hash2 != null)
                {
                    this.m_hash2.Clear();
                }
                if (this.m_inner != null)
                {
                    Array.Clear(this.m_inner, 0, this.m_inner.Length);
                }
                if (this.m_outer != null)
                {
                    Array.Clear(this.m_outer, 0, this.m_outer.Length);
                }
            }
            base.Dispose(disposing);
        }

        protected override void HashCore(byte[] rgb, int ib, int cb)
        {
            if (!this.m_hashing)
            {
                this.m_hash1.TransformBlock(this.m_inner, 0, this.m_inner.Length, this.m_inner, 0);
                this.m_hashing = true;
            }
            this.m_hash1.TransformBlock(rgb, ib, cb, rgb, ib);
        }

        protected override byte[] HashFinal()
        {
            if (!this.m_hashing)
            {
                this.m_hash1.TransformBlock(this.m_inner, 0, this.m_inner.Length, this.m_inner, 0);
                this.m_hashing = true;
            }
            this.m_hash1.TransformFinalBlock(new byte[0], 0, 0);
            byte[] hashValue = this.m_hash1.Hash;
            this.m_hash2.TransformBlock(this.m_outer, 0, this.m_outer.Length, this.m_outer, 0);
            this.m_hash2.TransformBlock(hashValue, 0, hashValue.Length, hashValue, 0);
            this.m_hashing = false;
            this.m_hash2.TransformFinalBlock(new byte[0], 0, 0);
            return this.m_hash2.Hash;
        }

        public override void Initialize()
        {
            this.m_hash1.Initialize();
            this.m_hash2.Initialize();
            this.m_hashing = false;
        }

        internal void InitializeKey(byte[] key)
        {
            this.m_inner = null;
            this.m_outer = null;
            if (key.Length > this.BlockSizeValue)
            {
                base.KeyValue = this.m_hash1.ComputeHash(key);
            }
            else
            {
                base.KeyValue = (byte[])key.Clone();
            }
            this.UpdateIOPadBuffers();
        }

        private void UpdateIOPadBuffers()
        {
            int num;
            if (this.m_inner == null)
            {
                this.m_inner = new byte[this.BlockSizeValue];
            }
            if (this.m_outer == null)
            {
                this.m_outer = new byte[this.BlockSizeValue];
            }
            for (num = 0; num < this.BlockSizeValue; num++)
            {
                this.m_inner[num] = 0x36;
                this.m_outer[num] = 0x5c;
            }
            for (num = 0; num < base.KeyValue.Length; num++)
            {
                this.m_inner[num] = (byte)(this.m_inner[num] ^ base.KeyValue[num]);
                this.m_outer[num] = (byte)(this.m_outer[num] ^ base.KeyValue[num]);
            }
        }

        // Properties
        protected int BlockSizeValue
        {
            get
            {
                return this.blockSizeValue;
            }
            set
            {
                this.blockSizeValue = value;
            }
        }

        public string HashName
        {
            get
            {
                return this.m_hashName;
            }
            set
            {
                if (this.m_hashing)
                {
                    throw new CryptographicException("Cryptography_HashNameSet");
                }
                this.m_hashName = value;
                this.m_hash1 = HashAlgorithm.Create(this.m_hashName);
                this.m_hash2 = HashAlgorithm.Create(this.m_hashName);
            }
        }

        public override byte[] Key
        {
            get
            {
                return (byte[])base.KeyValue.Clone();
            }
            set
            {
                if (this.m_hashing)
                {
                    throw new CryptographicException("Cryptography_HashKeySet");
                }
                this.InitializeKey(value);
            }
        }
    }

    public class HMACSHA1 : HMAC
    {
        public HMACSHA1(byte[] key)
            : this(key, false)
        {
        }

        public HMACSHA1(byte[] key, bool useManagedSha1)
        {
            base.m_hashName = "SHA1";
            if (useManagedSha1)
            {
                base.m_hash1 = new SHA1Managed();
                base.m_hash2 = new SHA1Managed();
            }
            else
            {
                base.m_hash1 = new SHA1CryptoServiceProvider();
                base.m_hash2 = new SHA1CryptoServiceProvider();
            }
            base.HashSizeValue = 160;
            base.InitializeKey(key);
        }
    }

}

Dla zainteresowanych klasę zmieniłem z miejscach gdzie pobierana jest wartość HashAlgorithm-u (this.m_hash1.Hash) oraz wszędzie tam gdzie znajdują się opisy błedów (za leniwy jestem aby je wyciągać z Resources :) .

PointF w Compact Framework

Jedną z największych bolączek w programowaniu w Compact Framework jest brak implementacji PointF dla platformy Windows Mobile. Mimo braku obsługi w rysowaniu obiektów przy użyciu float Point-a, sama struktura przydaje się w przekształceniach aby uniknąć w nich strat.

Poniżej prezentuje wersję PointF wyciągniętą wprost z “pełnej” wersji przy użyciu Reflector-a:

using System.Globalization;
using System.Runtime.InteropServices;

namespace System.Drawing
{
    [Serializable, StructLayout(LayoutKind.Sequential)]
    public struct PointF
    {
        public static readonly PointF Empty;
        private float x;
        private float y;

        public PointF(float x, float y)
        {
            this.x = x;
            this.y = y;
        }

        public bool IsEmpty
        {
            get
            {
                return ((this.x == 0f) && (this.y == 0f));
            }
        }
        public float X
        {
            get
            {
                return this.x;
            }
            set
            {
                this.x = value;
            }
        }
        public float Y
        {
            get
            {
                return this.y;
            }
            set
            {
                this.y = value;
            }
        }
        public static PointF operator +(PointF pt, Size sz)
        {
            return Add(pt, sz);
        }

        public static PointF operator -(PointF pt, Size sz)
        {
            return Subtract(pt, sz);
        }

        public static PointF operator +(PointF pt, SizeF sz)
        {
            return Add(pt, sz);
        }

        public static PointF operator -(PointF pt, SizeF sz)
        {
            return Subtract(pt, sz);
        }

        public static bool operator ==(PointF left, PointF right)
        {
            return ((left.X == right.X) && (left.Y == right.Y));
        }

        public static bool operator !=(PointF left, PointF right)
        {
            return !(left == right);
        }

        public static PointF Add(PointF pt, Size sz)
        {
            return new PointF(pt.X + sz.Width, pt.Y + sz.Height);
        }

        public static PointF Subtract(PointF pt, Size sz)
        {
            return new PointF(pt.X - sz.Width, pt.Y - sz.Height);
        }

        public static PointF Add(PointF pt, SizeF sz)
        {
            return new PointF(pt.X + sz.Width, pt.Y + sz.Height);
        }

        public static PointF Subtract(PointF pt, SizeF sz)
        {
            return new PointF(pt.X - sz.Width, pt.Y - sz.Height);
        }

        public override bool Equals(object obj)
        {
            if (!(obj is PointF))
            {
                return false;
            }
            PointF tf = (PointF)obj;
            return (((tf.X == this.X) && (tf.Y == this.Y)) && tf.GetType().Equals(base.GetType()));
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        public override string ToString()
        {
            return string.Format(CultureInfo.CurrentCulture, "{{X={0}, Y={1}}}", new object[] { this.x, this.y });
        }

        static PointF()
        {
            Empty = new PointF();
        }
    }
}

Pocket GPW

Po wielu przeciwnościach losu w końcu udało mi się ukończyć pierwszą stabilną wersję Pocket GPW. Mobilną aplikacje Giełdy Papierów Wartościowych, która była pomysłem Vonski-ego w konkursie ogłoszonym na portalu PDA.pl.

    Na początek chciałbym podziękować wszystkim firmom i osobom zaangażowanym w projekt:

  • Dawid Gatti z PDA.pl za to że uwierzył że w samą idee konkursu i go zorganizował na łamach swojego portalu
  • TomTom za ufundowanie naprawdę świetnej nagrody TomTom XL z IQ Route 2.0
  • Piotr Szostak z Streemo.pl za patronat medialny
  • HTC za udostępnienie HTC Touch Pro2 do testów aplikacji
  • adek za cierpliwość przy finalnym testowaniu aplikacji

Aplikacja dostępna jest na portalu Codeplex. Poza plikiem CAB instalatora dostępne jest także pełne źródło aplikacji, dla osób które są zainteresowane jej modyfikacją lub po prostu przejrzeniem.

Program w aktualnej wersji pobiera notowania, pozwala na edycję notatki przy notowaniu oraz zdefiniowanie alarmów dla określonych warunków. Dane są przechowywane w bazie SQL Server CE (plik znajduje się w instalatorze).

    Wymagania:

  • .NET Compact Framework 3.5; do pobrania tutaj
  • Microsoft SQL Server Compact 3.5 dla Windows Mobile; do pobrania tutaj (tutorial jak zainstalować znajduję się tutaj)

Ekrany:

Pocket_GPW_Screen1 Pocket_GPW_Screen2 Pocket_GPW_Screen3
Pocket_GPW_Screen4 Pocket_GPW_Screen5 Pocket_GPW_Screen6
Pocket_GPW_Screen7

Czy to już koniec? Na pewno nie, nadal czekam na sugestie i pomysły dotyczące aplikacji; na pewno każdą przeczytam, choć może nie koniecznie je zaimplementuje ;-)

Rozszerzenia Microsoft XNA Game Studio 3.1 dla Zune HD

Na stronach MSDN od trzech dni dostępne są rozszerzenia XNA Game Studio dla urządzeń Zune HD. Instalatora można pobrać tutaj.

    Rozszerzenia XNA Game Studio 3.1 dostarczają następujące funkcje:

  • Możliwość programowania odtwarzacza Zune HD.
  • Dostęp do funkcji touch API dla Zune HD.
  • Dostęp do API akcelerometra dla Zune HD.

Pocket Demotywatory 1.4

Ku mojemu zaskoczeniu Pocket Demotywatory spotkały się ze sporym zainteresowaniem (na Freeware Pocket PC ponad 1000 pobrań). Dlatego postanowiłem rozbudować funkcjonalność programu.

    Zmiany w wersji 1.4:

  • dodana funkcja zapisywania obrazka
  • dodana funkcja wysyłania obrazka via MMS / Email
  • dodana funkcja czyszczenia cache-u
  • kilka drobnych poprawek związanych z obsługą

pocket demotywatory 1.4

Klienta w postaci pliku CAB można pobrać tutaj.

Pocket Demotywatory

W wolnej chwili napisałem prostego klienta www.demotywatory.pl na Windows Mobile. Aplikacja pobiera demoty z pierwszej strony i wyświetla je w postaci listy. Każdego demota można kliknąć aby wyświetlił się obrazek z funkcjami powiększania, pomniejszania, dopasowania do ekranu i rzeczywistego rozmiaru.

pocket_demotywatory

Klienta w postaci pliku CAB można pobrać tutaj. Póki co jest to wersja beta, więc proszę o ewentualne uwagi i sugestie.

.NET Compact Framework PictureBox w rozdzielczości VGA

W jednym z poprzednich wpisów opisywałem identyfikację rozdzielczości VGA na urządzeniach Windows Mobile. Pisałem, że kontrolki .NET CE same dopasowują się do aktualnej wartości CurrentAutoScaleDimensions. Tak też działa PictureBox, jednak zawartość kontrolki nie skaluje się automatycznie. A więc jak rozwiązać ten problem?

Zacznijmy od początku. PictureBox w rozdzielczości QVGA:

PictureBox_QVGA

Wszystko wygląda dobrze, ale zmieńmy skin kontrolki na rozdzielczość VGA:

PictureBox_VGA

Jak widać przy standardowym ustawieniu kontrolki grafika pozostała bez zmian. Najprostszym rozwiązaniem tego problemu jest zmiana SizeMode PictureBox na StretchImage, aby grafika wypełniła dostępne miejsce:

PictureBox_VGA-Stretch

Teraz wszystko wygląda poprawnie. W przypadku używania tego rozwiązania pamiętaj o tym, żeby wielkość kontrolki dopasować do wielkości osadzanej grafiki, aby uniknąć niepoprawnego skalowania.

Managed wrapper dla Windows Mobile 6.5 Gesture API

Dwójka programistów Microsoft Ron Buckton i Alex Yakhnin przygotowała wrappera dla Windows Mobile 6.5 Gesture API dla .NET Compact Framework, który do tej pory dostępny był tylko dla programistów C++.

Bibliotekę można pobrać tutaj. Alex i Ron przygotowali także dwa webcast-y opisujące sposób użycia wrapper-a, które można obejrzeć tu i tu. Polecam bo Gesture samo w sobie jest świetną biblioteką a efekty są bardzo ciekawe przy małym nakładzie pracy.

Identyfikacja rozdzielczości VGA na urządzeniach Windows Mobile

Coraz więcej urządzeń Windows Mobile dostępnych na rynku posiada ekrany o wysokich rozdzielczościach (VGA i więcej). Jeżeli właściwość AutoScaleMode na formie jest ustawiona na Dpi, twoje kontrolki zostaną dopasowane automatycznie do aktualnej rozdzielczości. Jednakże jeżeli pewne elementy rysujesz ręcznie jak np. grafiki albo używasz ImageList nie zostaną one dopasowane. A więc jak rozwiązać ten problem?

Odpowiedź jest bardzo prosta. Problem ten można rozwiązać poprzez odczytanie właściwości CurrentAutoScaleDimensions form-a używając poniższego extension-a

  public static bool IsHighResolution(this Form form)
  {
       SizeF currentScreen = form.CurrentAutoScaleDimensions;
       if (currentScreen.Height == 192)
            return true;

       return false;
  }

Dla wszystkich rozdzielczości poniżej WQVGA CurrentAutoScaleDimensions zwraca wartość 96; powyżej otrzymujemy 192.

Poniżej prosty przykład powiększania grafik w ImageList aby dopasować je automatycznie do wielkości ekranu.

  if (this.IsHighResolution())
  {
    this.imageList1.ImageSize = new Size(32 * 2, 32 * 2);
  }