C Programlama

AES Algoritması Nasıl Çalışıyor ? Birlikte Gerçekleyelim – 1


Tarihçe

Matematik temelli yaklaşımlara sahip bir protokol, görece modern teknolojiler veyahut bir cihazın içerisinde koşan yazılım modüllerine bakıldığında ilgili yaklaşımın yıllar öncesinde bir standarta bindirildiğini görmek çokta zor olmasa gerek. AES’e gebe kalınmadan önce DES(Data Encryption Standart) kullanılmaktaymış. Ancak bu standart numerik olarak düşük şifreleme anahtar boyutuna sahip olduğu için(sadece 56-bit) düzenlenen DES Challenge 3’de 22 saatte DES tarafından şifrelenmiş bir metin kırılabilmiş. Bunun üzerine NIST (National Institute of Standards and Technology) 1997 yılında bir çağrıya çıkıyor. Bu çağrı sonucunda toplanan yaklaşımlardan bazıları gereksinimleri karşılamıyor ve 5 yaklaşım 1999 yılında bir nevi final sahnesine çıkıyor. 2000 yılında Rijndael isimli kişinin yaklaşımı 1.sıraya yükseliyor ve 2001 yılında bu yaklaşım standart haline getiriliyor. Ortaya DES’den daha hızlı çalışan, farklı şifreleme anahtar boyutuna(128, 192, 256) sahip bir şifreleme algoritması meydana geliyor.

Hakkında

  • Bu algoritma 128, 192, 256 bit şifreleme anahtar boyutunu desteklemekte. Eğer bir işlemde sizden AES-128 kullanılması talep ediliyorsa, şifreleme anahtar boyutunuz 128bit, AES-192 ise 192bit olmak zorunda.
  • Bu algoritma simetrik bir algoritmadır, simetrik demek şifrelemek ve şifrenin çözülmesi işlemleri aynı şifreleme anahtarı ile yapılıyor demektir. Bir yerde başka bir şifreleme algoritması için asimetrik ifadesine denk gelirseniz, ilgili şifreleme algoritmasında şifreleme ve şifrenin çözülmesi farklı şifreleme anahtarı ile yapılmakta.
  • Şifreleme blok blok yapılmakta. Her blok boyutu 128bit olarak alınmaktadır.

Terminolojik İfadeler

  • Block Chiper : Şifreleme yapılırken kullanılan yöntemde bit bit değil blok blok bir şifreleme yapılıyorsa kullanılan terimdir.
  • Plaintext : Şifreleme yapılacak ifadedir.
  • Key : Şifreleme yapılırken kullanılacak şifreleme anahtarı olarak kullanılacak ifadedir.
  • Round : Şifreleme sürecinde yapılan iterasyon sayısı. Key boyutuna göre değişiklik göstermektedir. 128 için 10 round, 192 için 12 round, 256 için 14 round yapılmaktadır. Bazı kaynaklarda sırasıyla 11, 13, 15 şeklinde görülebilir. İlk round verilen key ile aynı ifade ile sonuçlandığı için.
  • Round Key : Her round sonrasında, ilgili round için toplamda 128bit uzunluğunda her biri 4byte uzunluğuna sahip 4 farklı subkey oluşturulur. Matematiksel ifadesi ‘w’ olarak görebilirsiniz. İlk round sonucunda elde edilen round key ifadesi, sisteme verilen key ile aynı değere sahiptir.
  • Substitution Box (S-Box) : Şifreleme yapılırken kullanılan look-up tablosudur. Forward ve Inverse S-Box olarak 2 farklı tablo mevcuttur.
  • Round Constant : İlgili round’ın numerik değerine göre daha önceden hesaplanmış sabit ifadedir. Look-up tablosu üzerinden değerler alınabilir. Detaylarını göreceğiz.
  • State : Şifrelenecek 4×4’lük 16 byte içeren bir bloktur.
  • Chipher Text : Şifrelenmiş metin.

Şifreleme Aşamaları

1. Şifreleme Anahtarının Açılımı (Key Expansion)

Bu aşamada orjinal key alınarak, her round sonrasında yeni bir round-key oluşturulur. 128bit key için Round Key oluşturma işleminin adımları şu şekildedir. 192 ve 256 bit bir tık daha komplike olacaktır. Daha öncesinde her round sonrasında 128b round-key oluşturulduğundan ve AES-128 için toplam 11 round-key olduğundan bahsetmiştik. Bu durumda 11 x 128b = 176 byte uzunluğuna sahip bir dizi bizim için yeterli olacaktır.

Tüm seri boyunca kullanacağımız key : “AES BLOG YAZIMIZ” olacaktır. Her karakter bir byte olacak şekilde düşünülmüştür. Bu anahtarımızı hex formatına dönüştürelim;

  1. İlk round sonrasında oluşan round-key(w0, w1, w2, w3) chipker key ile aynı değere sahiptir.

  2. Diğer roundlar hesaplanırken eğer sub round-key 4’ün katı değilse şu formül uygulanır;

    w[ i ] = w[i – 1] ^ w[i – 4]

    Örnek : İkinci round’a ait i = 6 sub round-key hesaplanırken;
    w[6] = w[5] ^ w[2] şeklinde olacaktır.

  3. Eğer i % 4 == 0 ise burada süreç değişecektir.

    1. Öncelike Rotword işlemi yapılır. Bu işlemde w[i -1] değeri bir byte sola kaydırılır. Kaydırma işlemi lineer olarak değil, circular olarak gerçekleştirilir.
    2. Yapılan Rotword işlemi sonrasında elde edilen byte’lar S-BOX tablosuna parametre olarak gönderilir. Ve yeni bir dizi elde edilmiş olur.
    3. Yeni elde edilen dizi, ilgili round’ın numerik değerine ait round sabiti ile xor işlemi tabi tutulur.
    4. Elde edilen son değer ile w(i -4) değeri xor işlemine tabi tutulur.

    Örnek : w[4] sub round-key değerini hesaplamak isteyelim ve elimizde ki w[3] : (20, 46, 75, 67) ve w[0] : (54, 68, 61, 74) olsun.
    Circular Kaydırma : (46, 75, 67, 20) olacaktır.
    Byte Substitution (S – Box) : (B7, 5A, 9D, 85) olacaktır (Yukarda bulunan S-BOX tablosuna gönderilerek dönen değerlerden oluşturuldu).
    Round Sabitinin Eklenmesi : Bu round 1. round olduğu için (2 gibi düşünebilir ancak daha önce belirttiğim gibi ilk round direkt olarak chiper key ile eşleştirildiği için 0. round olarak varsayılmakta.) (01, 00, 00, 00) ^ (B7, 5A, 9D, 85) işleminin sonucu (B6, 5A, 9D, 85) olacaktır.

    Son adım ise w[i – 4], i = 4 için w[0] değeri ile bulduğumuz sonucu xor işlemine tabi tutalım. w[4] : (E2, 32, FC, F1) olacaktır.


Şimdi ise kendi chiper key değerimizden round-key değerlerimizi oluşturmak için gerekli fonksiyonu oluşturalım.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>


#define getSBoxValue(num) (sbox[(num)])
#define KEY_EXP_SIZE 176
#define Swap4Bytes(val) \
 ( (((val) >> 24) & 0x000000FF) | (((val) >>  8) & 0x0000FF00) | \
   (((val) <<  8) & 0x00FF0000) | (((val) << 24) & 0xFF000000) )

uint8_t round_sub_keys[KEY_EXP_SIZE];


static const uint8_t sbox[256] = {
  //0     1    2      3     4    5     6     7      8    9     A      B    C     D     E     F
  0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
  0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
  0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
  0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
  0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
  0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
  0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
  0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
  0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
  0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
  0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
  0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
  0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
  0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
  0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
  0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };


  static const uint8_t Rcon[11] = {
  0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 };


static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key)
{
  unsigned i, j, k;
  uint8_t tempa[4]; // Used for the column/row operations
  const uint8_t Nr = 10;
  const uint8_t Nk = 4;
  const uint8_t Nb = 4;

  // The first round key is the key itself.
  for (i = 0; i < Nk; ++i)
  {
    RoundKey[(i * 4) + 0] = Key[(i * 4) + 0];
    RoundKey[(i * 4) + 1] = Key[(i * 4) + 1];
    RoundKey[(i * 4) + 2] = Key[(i * 4) + 2];
    RoundKey[(i * 4) + 3] = Key[(i * 4) + 3];
  }

  // All other round keys are found from the previous round keys.
  for (i = Nk; i < Nb * (Nr + 1); ++i)
  {
    {
      k = (i - 1) * 4;
      tempa[0]=RoundKey[k + 0];
      tempa[1]=RoundKey[k + 1];
      tempa[2]=RoundKey[k + 2];
      tempa[3]=RoundKey[k + 3];

    }

    if (i % Nk == 0)
    {
      // This function shifts the 4 bytes in a word to the left once.
      // [a0,a1,a2,a3] becomes [a1,a2,a3,a0]

      // Function RotWord()
      {
        const uint8_t u8tmp = tempa[0];
        tempa[0] = tempa[1];
        tempa[1] = tempa[2];
        tempa[2] = tempa[3];
        tempa[3] = u8tmp;
      }

      // SubWord() is a function that takes a four-byte input word and 
      // applies the S-box to each of the four bytes to produce an output word.

      // Function Subword()
      {
        tempa[0] = getSBoxValue(tempa[0]);
        tempa[1] = getSBoxValue(tempa[1]);
        tempa[2] = getSBoxValue(tempa[2]);
        tempa[3] = getSBoxValue(tempa[3]);
      }

      tempa[0] = tempa[0] ^ Rcon[i/Nk];
    }

    j = i * 4; 
    k = (i - Nk) * 4;
    RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0];
    RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1];
    RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2];
    RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3];
  }
}

/* Check with this online web-site : https://www.cryptool.org/en/cto/aes-step-by-step */

int main(int argc, char* argv[])
{
    uint8_t chiper_key[16] = {0x41, 0x45, 0x53, 0x20, 0x42, 0x4c, 0x4f, 0x47, 0x20, 0x59, 0x41, 0x5a, 0x49, 0x4d, 0x49, 0x5a};

    KeyExpansion(round_sub_keys, chiper_key);

    for (int i = 0; i < 11; ++i)
    {
        printf("Round {%i} -> ", i);

        for (int y = 0; y < 16; ++y)
        {
            static int sub_key_idx = 0;

            if (y % 4 == 0)
            {
                printf("sub-key-w[%i] :", sub_key_idx);
                ++sub_key_idx;
            }

            printf("%02x ",round_sub_keys[i * 16 + y]);
        }

        printf("\r\n");       

    }
}

Round {0} -> sub-key-w[0] :41 45 53 20 sub-key-w[1] :42 4c 4f 47 sub-key-w[2] :20 59 41 5a sub-key-w[3] :49 4d 49 5a
Round {1} -> sub-key-w[4] :a3 7e ed 1b sub-key-w[5] :e1 32 a2 5c sub-key-w[6] :c1 6b e3 06 sub-key-w[7] :88 26 aa 5c
Round {2} -> sub-key-w[8] :56 d2 a7 df sub-key-w[9] :b7 e0 05 83 sub-key-w[10] :76 8b e6 85 sub-key-w[11] :fe ad 4c d9
Round {3} -> sub-key-w[12] :c7 fb 92 64 sub-key-w[13] :70 1b 97 e7 sub-key-w[14] :06 90 71 62 sub-key-w[15] :f8 3d 3d bb
Round {4} -> sub-key-w[16] :e8 dc 78 25 sub-key-w[17] :98 c7 ef c2 sub-key-w[18] :9e 57 9e a0 sub-key-w[19] :66 6a a3 1b
Round {5} -> sub-key-w[20] :fa d6 d7 16 sub-key-w[21] :62 11 38 d4 sub-key-w[22] :fc 46 a6 74 sub-key-w[23] :9a 2c 05 6f
Round {6} -> sub-key-w[24] :ab bd 7f ae sub-key-w[25] :c9 ac 47 7a sub-key-w[26] :35 ea e1 0e sub-key-w[27] :af c6 e4 61
Round {7} -> sub-key-w[28] :5f d4 90 d7 sub-key-w[29] :96 78 d7 ad sub-key-w[30] :a3 92 36 a3 sub-key-w[31] :0c 54 d2 c2
Round {8} -> sub-key-w[32] :ff 61 b5 29 sub-key-w[33] :69 19 62 84 sub-key-w[34] :ca 8b 54 27 sub-key-w[35] :c6 df 86 e5
Round {9} -> sub-key-w[36] :7a 25 6c 9d sub-key-w[37] :13 3c 0e 19 sub-key-w[38] :d9 b7 5a 3e sub-key-w[39] :1f 68 dc db
Round {10} -> sub-key-w[40] :09 a3 d5 5d sub-key-w[41] :1a 9f db 44 sub-key-w[42] :c3 28 81 7a sub-key-w[43] :dc 40 5d a1

Çıktımız yukarda bulunduğu gibi olacaktır. Çıktının sağlaması sitesi üzerinden doğrulanabilir.

Kaynaklar

Knuth–Morris–Pratt Algoritması | Mülakatlar [Codility – Hackerrank – Qualified.io]


Bazı yaklaşım metodlarını, düşünce aşamalarını ilgili konularla ilişkilendiren yapılarda pratik yapmak, yeni gelecek problemlerde artık sorunun çözümünü omurilik soğanına aktarmak için birebir. Her şeyin anahtarı pratik ve pratik. Kimisi için 3 pratik, kimisi için 5 pratik ama pratik.

İnsanın da gördüğü, deneyimlediği kadar bir varlık olduğunu düşünürsek bugün ben de sizlere KMP algoritmasını ve beraberinde bir firmanın mülakatında sorulmuş olan bir soruyu KMP kullanarak ve kullanmayarak çözmeye çalışacağım. Karakter gruplarının bir araya geldiği ifadelerde işlem yapmak – arama, silme, değiştirme gibi günün sonunda belli bir indekse dayandırılması gereken işlemler – zahmetli olabiliyor. Ama aranacak ifade özelinde bir lut oluşturabilirsek ve bu oluşturduğumuz lut ile birlikte asıl aradığımız yere gidersek süreçleri iyileştirebilmekteyiz. Dijital bir sistem üzerinde çalıştığımızı düşünürsek iyileştirdiğimiz şey algoritma karmaşıklığı olacaktır.

Yukarda ifade etmeye çalıştığım gibi KMP algoritması iki aşamadan oluşmakta;

  1. Öncelikle pattern(aranacak ifade) için bir lut oluşturmak.
  2. Text içerisinde arama yaparken lut’u kullanmak.

Nasıl bir metin içerisinde arayacağımızı şu an için düşünmeden, elimizde şöyle bir pattern olduğunu varsayalım.

aabaabaaa
pattern

Bu pattern içerisinde görebileceğiniz gibi kendini parçalı halde kendini tekrar eden bazı bölümler bulunmakta. Yani KMP algoritmasını uygulamak isteyeceğiniz yapıların böyle olması önem arz etmekte. Standart arama algoritmalarından daha verimli sonuçlar verme durumunu ortayan çıkaran bu zaten. Çünkü patternin kendisinde oluşturduğunuz lut sayesinde, arama yaparken eşleşmeyen bir karakter sonrasında tüm süreci başa almak yerine pattern içerisinde hangi karaktere dönüş yapacağımızı belirlemiş oluyoruz. Öncelikle bu lut nasıl oluşturuluyor buna bakalım.

Pattern içerisinde gezinirken iki farklı index değeri tutuyoruz. Bunlar i ve j diyelim. j değerimizin başlangıç değeri 0 ve i değerimizin başlangıç değeri ise 1 olarak başlasın. Buna ek olarak pattern ilk indexli elemanın lut içerisinde ki değeri her zaman 0 olarak başlamalı. Şimdi adım adım bu lut’u oluşturalım.

1.Adım

aabaabaaa
000000000

1.adımda i = 1, j = 0 değeri ile başlamakta. Ve tüm lut şu anda 0 olarak işaretlenmiş. Bir sonraki adım içerisinde i ve j’nin sahip olduğu değerler pattern’e index olarak verilir ve karşılaştırılma yapılır. Eğer karşılaşma sonrası eşleşme meydana gelirse, i’nin lut index değeri j + 1 olarak belirlenir i ve j değeri bir arttırılır. Şayet herhangi bir eşleşme bulunmaz ise bizim için j’nin değerinin 0’a eşit olması ya da olmaması önemli. O kısma geleceğiz.

2.Adım

aabaabaaa
010000000

1.adım açıklama kısmında yazdığımız gibi, şöyle bir kod parçacığı oluşmakta.

if (pattern[j] == pattern[i])
{
      table_array[i] = j + 1;
      i++;
      j++;
}

Karşılaştırmaya pattern sonuna gelene kadar devam ediyoruz, 3.adımda pattern[1] ve pattern[2] karakterlerinde bir karşılaştırma yapacağız. Eğer karakterlerin eşit olmaması durumunda şayet j değeri sıfırdan farklı bir değer ise pattern içerisinde bir tekrar olup olmadığını anlayabilmek adına j’nin yeni değerini lut tablosunda mevcut j değerinin index değerinde bulunan ifadeden bir önceki değere eşitliyoruz, yani şöyle bir kod parçacığı oluşmakta.

if (j != 0)
{
      j = table_array[j - 1];
}

3.Adım

aabaabaaa
010000000

2.Adım içerisinde bahsettiğimiz üzere yapılan karşılaştırma sonrasında j değeri şu anda 0 olmuş durumda çünkü table_array[0] değerimiz 0’dı. i değeri ise 2 olarak durmakta. Bu döngünün bir while içerisinde döndüğünü düşünürsek şu anda pattern[0] ve pattern[2] üzerinde bir karşılaştırma yapılmakta. Burada yeni bir durum çıkmakta. Şayet j değeri 0’a eşit ise lut içerisinde ki i’nin lut index değeri 0 olmakta. Yani şöyle bir kod parçacığı oluşmakta.

if (j != 0) 
{
       j = table_array[j - 1];
}

else
{                
       table_array[i] = 0;
       i++;     
}

Buradan anlayabiliriz ki ilgili index ve öncesinin başlangıcında herhangi bir şekilde tekrar bulunmamakta. Bir adım daha yapacağım ve diğer adımları size bırakacağım.

4.Adım

Karşılaştırmaya devam ediyoruz şu anda j değerimiz 0, i değerimiz ise 3 olmuş durumda. pattern[0] ve pattern[3] arasında bir eşleşme olduğuna göre, 1.adımda ki süreç gerçekleşiyor.

aabaabaaa
010100000

Tüm bu adımlar sonrasında lut şu halini almakta.

010123452

2. aşama olan arama kısmında benzer bir süreç gerçekleşmekte. Eğer j kısmı verilen pattern uzunluğuna eşit ise pattern bulunmuş demektir ve i – j değeri bizim aradığım text içerisinde patternin başladığı indexi döndürmekte. Tüm bu süreçleri ve ilgili örneğin koda dökülmüş haline şuradan erişebilirsiniz.

Şimdi gelelim bir mülakatta sorulmuş bir soruyu çözmeye çalışalım. Bu soruda KMP kullanmak zorunda elbette değiliz. Nedenini zaten lut oluşturduğumuzda görebiliriz.

We are given a string S of length N consisting only of letters ‘A’ and/or ‘B’. Our goal is to obtain a string in the format “A…AB B” (all letters ‘A’ occur before all letters ‘B’) by deleting some letters from S. In particular, strings consisting only of letters ‘A’ or only of letters ‘B’ fit this format.

Examples :

Given S = “BAAABABA”, the function should return 2.

Given S = “BBABAA”, the function should return 3.

Given S = “AABBBB”, the function should return 0.

İnsanın karşısına string algoritmaları ile ilgili bir soru geldiğinde ve belli bir süre içerisinde bir çözüm sağlaması gerektiğinde hatta ve hatta karşı taraf sesli düşünerek biz çözüm istiyorsa fakat daha önce çok fazla pratik yapılmamış ise biraz dumor olabiliyor. Belki daha öncesinde öğrenilen bir yaklaşım mevcut soru için verilen doğruluk, zaman karmaşıklığı gibi çözüm parametreleri açısında iyi olmayabilir ama o yaklaşım sayesinde farklı bir yaklaşım sergileyebiliriz.

Ben kendi çözümümden bahsedeceğim, belki farklı yaklaşımlara sahip olabilirsiniz ki bu çok normal. Bu soruda ilk olarak bb ve ba gibi patternin varlığının olup olmamasını sınamak benim için doğru bir yaklaşım gibi geldi. Bu yaklaşımda önemli olan şu eğer bb patterni mevcut ise şöyle bir şey gelebilir bba ama ba patterni mevcut ise başlangıçta minumum 4 karakterlik bir arayış gerçekleşmesi gerekir. Buna ek olarak bb patterni mevcut ise bu pattern öncesinde ba gibi patternin varlığı gerçekleşmiş ya da devamında gerçekleşecek mi ?

Benim bu soru özelinde gerçekleştirmiş olduğum ve kmp kullandığım yapıyı şuradan görebilirsiniz. Aklınıza gelen bir test vakasını denediğinizde bir problem var ise bildirirseniz çok sevinirim. Peki bu soruda KMP kullanmak mantıklı mı ? Çözümü incelerseniz KMP yaklaşımında ufak tefek bazı değişikler yapıldığını görebilirsiniz. Soruya bir çözüm sağlayıp rahatladıktan sonra tekrar bakıldığında aslında lut tablosu bb patterni için 0 1 ba patterni için 0 0 değerini almakta. Ve arama kısmında bu lut tablosu çokta işlevsel değil. Tekrar düşünüldüğünde ortaya şu yapı çıkmakta. İşte bahsetmeye çalıştığım olay bu aslında KMP algoritmasını bilmiyor olsaydım o konuda bir pratiğim olmasaydı belki de bu yaklaşımı yapamıyor olabilirdim.

Pratik, pratik, pratik yazılımın anahtarı bence bu. Bir sonraki string algoritmasında görüşmek üzere.

Nuvoton from scratch, the bare minimum


I do not use register based programming actively in my daily life. However, if i need to meet a new platform, i try this way. In this way, i can understand whether the datasheet of the relevant platform is good.  To be honest, the datasheet of the Nuvoton products came out poor. I could not find some generalized keywords like “register boundary address ” in the datasheet. Finding relevant addresses and offset values was a waste of time really. Today, we will build a project from scratch. We will use GNU ARM Embedded as toolchain. We will write the linker file. After the compilation and linking process, we will transfer the .bin extension file that we will obtain through the utility to our development card with program named NUMicro ICP Programming Tool. If you have used the stm32 series before you can understand it easily. The development card i have (NuMaker – PFM – M487) using Cortex-M4.

Let’s start with startup file as named startup.c

#include "stdint.h"

#define sp 0x20000800 

#define __IO volatile

typedef struct
{
    __IO uint32_t MODE;          /* Offset: 0x00/0x40/0x80/0xC0/0x100/0x140/0x180/0x1C0  Port A-H I/O Mode Control                       */
    __IO uint32_t DINOFF;        /* Offset: 0x04/0x44/0x84/0xC4/0x104/0x144/0x184/0x1C4  Port A-H Digital Input Path Disable Control     */
    __IO uint32_t DOUT;          /* Offset: 0x08/0x48/0x88/0xC8/0x108/0x148/0x188/0x1C8  Port A-H Data Output Value                      */
    __IO uint32_t DATMSK;        /* Offset: 0x0C/0x4C/0x8C/0xCC/0x10C/0x14C/0x18C/0x1CC  Port A-H Data Output Write Mask                 */
    __IO uint32_t PIN;           /* Offset: 0x10/0x50/0x90/0xD0/0x110/0x150/0x190/0x1D0  Port A-H Pin Value                              */
    __IO uint32_t DBEN;          /* Offset: 0x14/0x54/0x94/0xD4/0x114/0x154/0x194/0x1D4  Port A-H De-Bounce Enable Control Register      */
    __IO uint32_t INTTYPE;       /* Offset: 0x18/0x58/0x98/0xD8/0x118/0x158/0x198/0x1D8  Port A-H Interrupt Trigger Type Control         */
    __IO uint32_t INTEN;         /* Offset: 0x1C/0x5C/0x9C/0xDC/0x11C/0x15C/0x19C/0x1DC  Port A-H Interrupt Enable Control Register      */
    __IO uint32_t INTSRC;        /* Offset: 0x20/0x60/0xA0/0xE0/0x120/0x160/0x1A0/0x1E0  Port A-H Interrupt Source Flag                  */
    __IO uint32_t SMTEN;         /* Offset: 0x24/0x64/0xA4/0xE4/0x124/0x164/0x1A4/0x1E4  Port A-H Input Schmitt Trigger Enable Register  */
    __IO uint32_t SLEWCTL;       /* Offset: 0x28/0x68/0xA8/0xE8/0x128/0x168/0x1A8/0x1E8  Port A-H High Slew Rate Control Register        */
    __IO uint32_t RESERVE0[1];
    __IO uint32_t PUSEL;         /* Offset: 0x30/0x70/0xB0/0xF0/0x130/0x170/0x1B0/0x1F0  Port A-H Pull-up and Pull-down Enable Register  */

} GPIO_T;

#define PERIPH_BASE                 (( uint32_t)0x40000000)     /*!< Perip. Control Register */
#define GPIOH_BASE                  (PERIPH_BASE + 0x041C0UL)   /*!< PORT H Control Register */
#define PH                          ((GPIO_T *)  GPIOH_BASE)    /*!< Casting to PORT H Control Register */
#define GPIO_MODE_OUTPUT            (0x1UL)                     /*!< Output Mode ideinitializer */
#define BIT0                        (0x00000001UL)              //< Bit 0 mask of an 32 bit integer

int main(void);

uint32_t *vector_table[2] __attribute__  ((section("vectors"))) = 
{
    (uint32_t*) sp, //stack pointer
    (uint32_t*) main,
};

int main()
{    
    PH -> MODE |= BIT0; //PH0 Output Mode
    
    while(1)
    {
        PH -> DOUT = 0x00; //low PH-0.bit
        for (int i = 0; i < 50000; ++i); //for delay
        PH -> DOUT = 0x01; //high PH-0.bit
        for (int i = 0; i < 50000; ++i); //for delay
    }       
}


Since the stack is a downward-going structure and the ram start address is 0x20000000. I’ve set a small stack. Now we can compile this file, so tiny : just 84 bytes.

arm-none-eabi-gcc -I. -c -fno-common -O0 -g -mcpu=cortex-m4 -mthumb startup.c
MEMORY
{
    ram (rwx) : ORIGIN = 0x20000000, LENGTH = 16K
    rom (rx)  : ORIGIN = 0x00000000, LENGTH = 40K
}

SECTIONS
{
    .  = 0x0;         /* From 0x00000000 */
    .text :
    {
        *(vectors)    /* Vector table */
        *(.text)      /* Program code */
        *(.rodata)    /* Read only data */
    } >rom

    .  = 0x20000000;  /* From 0x20000000 */
    .data :
    {
        *(.data)      /* Data memory */
    } >ram AT > rom

    .bss :
    {
        *(.bss)       /* Zero-filled run time allocate data memory */
    } >ram AT > rom
}


I did not write the original ram and rom sizes, I did not need that much, so I used my existing linker file. Lets linking.

arm-none-eabi-ld -Tnuvoton.ld -nostartfiles -o startup.elf startup.o

Converting the binary file.

arm-none-eabi-objcopy -Obinary startup.elf startup.bin


After uploading the file to our development card with the numicro program, we can see that the red led on the card flashes.

Best Regards,
Volkan

Gömülü Cihazlar ve Big-O Notasyonu


Literatürde bulunan bir çok farklı algoritmanın, farklı yazılım dillerinde implementasyonlarını yapmak mümkün. Bu algoritmaların koşacağı birbirinden farklı sistem kaynaklarının olduğunu düşündüğümüzde ise bu algoritmaların koştuğu sistem üzerindeki davranışını değil de, söz konusu olan algoritmanın karmaşıklığının maliyetini belirlemek için kullandığımız bir notasyon olan BIG-O notasyonunu bugün bir örnekle ele alacağım ve bu konuyu güzel bir şekilde ele alan bir udemy eğitiminden bahsedeceğim.

Çeşitli veri yapılarının ve operasyonların algoritma karmaşıklıklarına dair derli toplu bir yer arıyorsanız şuraya bakabilirsiniz.

Bugün şu soruyu irdeleyeceğiz,

N elemandan oluşan bir dizide negatif karşılığı bulunan tüm pozitif sayıları bulunuz.
Örneğin [2, 5, 1, -1 , -3, 7, 4 , -2] dizisi için sonuç [2, 1] olmalıdır.

Bu soru karşımıza bir mülakat esnasında gelebileceği gibi, bu tarz soruların üzerinde düşünmenin bizi daha dinç tuttuğunu düşünüyorum. Bu soruyu gördüğümüzde aklımızda hemen farklı yaklaşımlar belirebilir. Benim aklıma HackerRank sitesinde soruları çözerken kazandığım bir idiom gelmekte ve elemanların değerlerinin başka bir dizi üzerinde index olarak yapılandırması yolunu tercih ediyorum. Eğer daha önce HackerRank gibi platformlara göz gezdirmediyseniz ve problem çözmeyi seviyorsanız mutlaka bakın. Hem kodlama pratiği açısından oldukça güzel bir site hemde teknik mülakatlarda bulanacağımız zaman bize artı kattığını düşünüyorum uzun vadede.

Kodlamaya başlamadan önce size verilen veri setinden daha fazlasını düşünmek zorundasınız, yukarda gördüğünüz gibi 0’a tümleyen negatif sayıların mutlak karşılığı olan pozitif sayılar daha önce gelmekte. Kurduğunuz algoritma bunun tersi durumları handle edebiliyor mu ? Yine aynı şekilde yaklaşımınız mutlak değerleri eşit olan fakat aynı işaretli sayılar için nasıl davranıyor ? Benim yukarda bulunan soru için çözümüm bu şekilde oldu, eksik gördüğünüz noktalar ya da denediğiniz test durumları için yanlış bir sonuç verirse lütfen bilgilendirin. Eğer mobilden bu yazıyı okuyorsanız şuradan kodun biçimlendirilmiş haline ulaşabilirsiniz.

//NOT : SIZE verilen dizideki en yüksek değerden daha yüksek seçilmiştir.
#include <stdio.h>
#include <stdlib.h>

#define SIZE 10

const int test_array[SIZE] = {-2, 5, 1, -1, -3, -7, 7, 2, 3, 5};

int main(void) {

  int emulate_array[SIZE] = { 0 };
 
  for (int i = 0; i < SIZE; ++i)
  {
    //Dizide aynı sayının tekrarı olması durumu göz önünde alındı. Örnek dizi de -7 -7 ya da  5 5 gibi aynı ranka sahipse elenir.
    if (test_array[i] < 0 && emulate_array[abs(test_array[i])] == 1)
    {
      printf("\r\n Bulundu = %d", abs(test_array[i]));      
    }

    else if (test_array[i] > 0 && emulate_array[test_array[i]] == -1)
    {
      printf("\r\n Bulundu = %d", abs(test_array[i]));
    }   

    if (test_array[i] > 0)
    {      
      emulate_array[test_array[i]] = 1;
    } 

    //Negatif sayi dizide daha önce gelebilir.
    else
    { 
      emulate_array[abs(test_array[i])] = -1;
    }
  }
  
  return 0;
}

Sergilediğimiz bu yaklaşımı biraz daha irdeleyecek olursak, farkettiğiniz gibibellek kullanımı(space complexity) artmış durumda. Ve algoritmamızın karmaşıklığı şu anda O(n) durumunda ve bu hoş. Burada aklıma gelen ilk şey şu oldu bu yazdığım algoritma bir mikrodenetleyici içeren sistem üzerinde mi koşacak ? Yarın öbür gün veri setinin boyutunda gerçekleşebilecek bir artış benim donanımda kullandığım çipin tahsis edemeyeceği bir bellek boyutu durumuna gelebilir mi ? Farklı yaklaşımlar neler olabilir ve onların algoritma karmaşıklığının daha fazla olması fakat bellek kullanımının daha az olması sistemim için daha mı tercih edilebilir bir yöntem ? Veri setinin büyümesi sonucunda algoritma karmaşıklığı diğer sistemleri bloklar mı ? Bu ve benzer bir çok soru aklımıza gelebilir ve gelmesi önemli bence.

Anlatmaya çalıştığım gibi bu soruyu yapılabilecek farklı yaklaşım yöntemleri mevcut, belki öncesinde diziyi buble, quick sort gibi farklı maliyetlere sahip algoritmalar ile sıralayabilir ve sonrasında da işlem yapabilirsiniz. Benim yöntemim doğru diğer yaklaşımlar yanlış demek çok mümkün değil ve yanlış bir bakış açısı olur.

Yazının başında belirttiğim gibi bu konuları içerisinde barındıran henüz bitirmedim ama geldiğim noktaya kadar yeni şeyler öğrendiğim ve mevcut öğrendiklerimi tazelediğim bir udemy eğitiminin promosyonlu linkine buradan erişebilirsiniz. Bir eğitim alırken dikkat ettiğim bir konu var, ben tecrübeye para ödemeyi seçiyorum, zira udemy üzerinde internet üzerinden topladığı bilgileri henüz kendi süzgecinden geçirmeden paylaşan bir çok birey var. Bu bana çok doğru gelmiyor. Eğitim linkini bıraktığım videoları hazırlayan kişilerin hem kendileri aday pozisyonunda hemde adayları değerlendirdiği bir pozisyonda bulunduğunu düşünürsek kazandıkları deneyimler için bu kadar cüzzi bir miktar ödemek bence çok iyi. Eğer öğrenciyseniz ve bu eğitimi almak bütçeniz için uygun değilse benimle .edu uzantılı mail adresinizden iletişime geçebilirsiniz.

Kolaylıklar dilerim.

STM32 SD KART ÜZERİNDEN BOOT ETMEK


Merhabalar, daha önce burada ve burada bulunan yazılarımı okumadıysanız, bakmanızda fayda olabilir. Daha önce o başlıklarda bahsetmiş olduğum kısımlara tekrar tekrar değinmeyeceğim için kopukluklar yaşanabilir.

Bu yazıda bahsetmeyi çok istediğim ama bahsetmeyeceğim şunlar var :

  • Secure Bootloader (Bilmediğim bir konu)
  • Başlıktan da anlaşılacağı gibi sd kart üzerinden boot edeceğiz ama o dosya sd karta nasıl geldi ? (Bundan başka bir yazıda OTA ile ilgili üretmeyi planlığım içerikte bahsedeceğim babajım)

Bu yazıda bahsedeceğim ve olası çözümlerini implemente ettiğimi düşündüğüm şunlar var :

  • Dosyanın bütünlüğünü(integrity) kontrol ettiğimiz bir yapı.
  • Cihazın anlık enerji kesintilerinde yazma işlemlerinin yarım kalmasına karşın durumu handle edebilmesi. Daha önce yukarda belirttiğim linklerde bulunan st’nin kendi sistem bootloader programlarında bu bir problemdi. (Burada bir çok case olduğu için, ya şöyle bir şey olursa diye aklınıza bir şey gelirse ve o durum atlanmışsa bildirirseniz çok sevinirim. Kahve ısmarlarım bende)
  • Yazılımın versiyonun kontrolü işlemi.

Enerjin düşük ise ve 30 saniye vaktin var ise şuraya tıklayarak hayata dönmene yardımcı olabilirim.

Bildiğiniz, karşılaştığınız gibi bir cihazın kutulandıktan ya da fiziksel olarak sizden ayrıldıktan sonra yazılımın doğası gereği yapılan yeni geliştirmelere her zaman açık olması gerekebiliyor.

Uygulamanın yapmaya çalıştığı işi şu resim ile özetleyebiliriz.

ST’nin flash bölümünde birden çok ve farklı boyutlarda olmak üzere sektörler mevcut. Kullandığım mikrodenetleyici STM32F446xx serisine ait. Ve bu seri için 8 farklı sektör mevcut. Ben uygulama kodumu 128kb olarak parçalanmış 7.sektörde çalıştırırken, bootloader uygulamamı ise 0.sektörde konumlandıracağım. Bu konumlandırma işleminin KEIL IDE’si üzerinde nasıl gerçekleştirileceğini daha önce yukarda belirttiğim linklerde bahsetmiştim.

İlgili dosyanın metadatası ve proje özelinde oluşturduğum fonksiyonlar şu şekilde.

#ifndef BOOT_H_INCLUDED
#define BOOT_H_INCLUDED

#include "stdint.h"

#define ADDR_FLASH_SECTOR_0     ((uint32_t)0x08000000) /* Base @ of Sector 0, 16 Kbytes */
#define ADDR_FLASH_SECTOR_1     ((uint32_t)0x08004000) /* Base @ of Sector 1, 16 Kbytes */
#define ADDR_FLASH_SECTOR_2     ((uint32_t)0x08008000) /* Base @ of Sector 2, 16 Kbytes */
#define ADDR_FLASH_SECTOR_3     ((uint32_t)0x0800C000) /* Base @ of Sector 3, 16 Kbytes */
#define ADDR_FLASH_SECTOR_4     ((uint32_t)0x08010000) /* Base @ of Sector 4, 64 Kbytes */
#define ADDR_FLASH_SECTOR_5     ((uint32_t)0x08020000) /* Base @ of Sector 5, 128 Kbytes */
#define ADDR_FLASH_SECTOR_6     ((uint32_t)0x08040000) /* Base @ of Sector 6, 128 Kbytes */
#define ADDR_FLASH_SECTOR_7     ((uint32_t)0x08060000) /* Base @ of Sector 7, 128 Kbytes */

#define APP_ADDRESS (ADDR_FLASH_SECTOR_7)						 		/* Image Location */

#define FLASH_USER_START_ADDR   (APP_ADDRESS)   /* Start @ of user Flash area */

#define FLASH_USER_END_ADDR     ((uint32_t)0x0807FFFF)    /* End @ of user Flash area */
																					 
#define VERSION_ADDR ((uint32_t)(FLASH_USER_END_ADDR - 3))

typedef struct meta_data meta_data_t;
struct meta_data
{
	uint8_t crc_val[4];
	
	uint8_t version[4];
};	

void jump_to_app(void);

int erase_flash_sector(void);

#endif
#include "stm32f4xx.h"                  // Device header
#include "boot.h"

static uint8_t get_sector(uint32_t address)
{	
	uint8_t sector = 0;
  
  if((address < ADDR_FLASH_SECTOR_1) && (address >= ADDR_FLASH_SECTOR_0))
  {
    sector = FLASH_SECTOR_0;  
  }
  else if((address < ADDR_FLASH_SECTOR_2) && (address >= ADDR_FLASH_SECTOR_1))
  {
    sector = FLASH_SECTOR_1;  
  }
  else if((address < ADDR_FLASH_SECTOR_3) && (address >= ADDR_FLASH_SECTOR_2))
  {
    sector = FLASH_SECTOR_2;  
  }
  else if((address < ADDR_FLASH_SECTOR_4) && (address >= ADDR_FLASH_SECTOR_3))
  {
    sector = FLASH_SECTOR_3;  
  }
  else if((address < ADDR_FLASH_SECTOR_5) && (address >= ADDR_FLASH_SECTOR_4))
  {
    sector = FLASH_SECTOR_4;  
  }
  else if((address < ADDR_FLASH_SECTOR_6) && (address >= ADDR_FLASH_SECTOR_5))
  {
    sector = FLASH_SECTOR_5;  
  }
  else if((address < ADDR_FLASH_SECTOR_7) && (address >= ADDR_FLASH_SECTOR_6))
  {
    sector = FLASH_SECTOR_6;  
  }	
  else /* (Address < FLASH_END_ADDR) && (Address >= ADDR_FLASH_SECTOR_11) */
  {
    sector = FLASH_SECTOR_7;
  }

  return sector;
}

void jump_to_app(void)
{
	uint32_t* image_loc_addr = (uint32_t*)APP_ADDRESS;
	
	uint32_t stack_pointer = image_loc_addr[0];
	
	uint32_t jump_addr = image_loc_addr[1];
		
	typedef void (*jump_func)(void);
	
	jump_func jump = (jump_func) jump_addr;
	
	HAL_RCC_DeInit();
	
	HAL_DeInit();
	
	SysTick -> CTRL = 0;
	SysTick -> LOAD = 0;
	SysTick -> VAL = 0;
	
	__set_MSP(stack_pointer);
	
	jump();	
	
}

int erase_flash_sector(void)
{
	FLASH_EraseInitTypeDef EraseInitStruct;
	
	int ret = 1;
	
	uint8_t sec = get_sector(FLASH_USER_START_ADDR);
	
	uint8_t number_of_sector = get_sector(FLASH_USER_END_ADDR) - sec + 1;
	
	EraseInitStruct.TypeErase = TYPEERASE_SECTORS;
	EraseInitStruct.VoltageRange = VOLTAGE_RANGE_3;
	EraseInitStruct.Sector = sec;
	EraseInitStruct.NbSectors = number_of_sector;
	
	uint32_t sector_error;
	
	if (HAL_FLASHEx_Erase(&EraseInitStruct, &sector_error) != HAL_OK)
	{
		ret = -1;
	}	
	
	return ret;
}

ST ailesinin işlemcilerinde flasha yazabileceğiniz data tipleri ya da gerilim seviyesine göre yazabileceğiniz data tipleri her zaman standart olmuyor gördüğüm kadarıyla. (Daha önce tecrübe ettim). Bu yüzden kendi kullandığınız işlemcinin datasheetinde şu tabloyu kontrol etmenizde fayda var.

    if ((fr = f_mount(&fs, SDPath, 1)) != FR_OK)
	{
		goto exit;
	}	
	
	if ((fr = f_open(&file, "test.bin", FA_READ)) == FR_OK)
	{
		//4 offset for CRC
		if (f_size(&file) - 4 > (FLASH_USER_END_ADDR - FLASH_USER_START_ADDR))
		{
			f_close(&file);
			
			goto exit; 
		}
		
		uint8_t meta_data_buf[8] = { 0 };

		uint32_t crc_val = 0;
	
		unsigned int number_of_bytes_read = 0;
		
		f_lseek(&file, (f_size(&file) - sizeof(struct meta_data)));
				
		f_read(&file, meta_data_buf, sizeof(meta_data_buf), &number_of_bytes_read);
		
		meta_data_t* mdb = (meta_data_t*) meta_data_buf;		
		
		f_lseek(&file, 0);
		
		do 
		{
			unsigned int number_of_bytes_read = 0;
			
			uint8_t buffer[4] = { 0 };
			
			fr = f_read(&file, buffer, sizeof(buffer), &number_of_bytes_read);
		
			for (int i = 0; i < 4; ++i)
			{
				crc_val = get_crc32(crc_val, buffer[i]); 
			}
					
		} while (number_of_bytes_read > 0 && f_tell(&file) + sizeof(struct meta_data) + 4 <= f_size(&file) && fr == FR_OK);
		
		uint32_t match_crc = (mdb -> crc_val[0] << 24) | 
												 (mdb -> crc_val[1] << 16) | 
												 (mdb -> crc_val[2] << 8)  | 
												 (mdb -> crc_val[3]);
		
		uint32_t version =   (mdb -> version[0] << 24) | 
												 (mdb -> version[1] << 16) | 
												 (mdb -> version[2] << 8)  | 
												 (mdb -> version[3]);
		
		/* CRC is valid and version is ok */
		if (crc_val == match_crc && ((version > (*(uint32_t*)VERSION_ADDR)) || ((*(uint32_t*)VERSION_ADDR) == 0xFFFFFFFF)))
		{
			fr = f_lseek(&file, 0);
			
			HAL_FLASH_Unlock();
			
			if (erase_flash_sector() < 0)
			{
				goto exit;
			}		
			
			static unsigned int address_offset = 0;
			
			do 
			{	
				uint8_t buffer_flash[4] = { 0 };

				unsigned int number_of_bytes_read = 0;
				
				uint32_t written_data = 0;
				
				fr = f_read(&file, buffer_flash, sizeof(buffer_flash), &number_of_bytes_read);
				
				written_data = *(uint32_t*)buffer_flash;
				
				HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, APP_ADDRESS + address_offset, written_data);
				
				if (written_data != (*(__IO uint32_t*)(APP_ADDRESS + address_offset)))
				{
					/* Whatever you want depend on your logic */
					while(1);
				}
				
				address_offset = address_offset + 4;
			
			} while (number_of_bytes_read > 0 && f_tell(&file) + sizeof(struct meta_data) + 4 <= f_size(&file) && fr == FR_OK);
						
			HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, VERSION_ADDR , version);
			
			f_close(&file);	
			
			HAL_FLASH_Lock();
			
			//TODO: Delete related file if you want
			
			goto exit;
						
		}//(crc_val == match_crc)
		
		else
		{
			goto exit;		
		}
	}
	
	else
	{
		goto exit;
	}
	
	exit:
		
		if (*(uint32_t*)APP_ADDRESS != 0xFFFFFFFF && *(uint32_t*)VERSION_ADDR != 0xFFFFFFFF)
		{
			jump_to_app();
		}
			
		else
		{
			/* Whatever you want depend on your logic */
			while(1);
			//NVIC_SystemReset();
		}

Edit : Flowchartta belirtilmemiş ama ilgili dosyanın boyutu, uygulamanın çalışacağı boyuttan yüksek olması durumunda yazma işlemi yapılmamakta.

Bu konuda Türkçe kaynak oluşturmak için bu yazıyı paylaştığımı belirtmek ister ve bu yapıyı kullanırken karşılacağınız sorunlar olur ise paylaşmanızı rica ederek, yazıyı sonlandırıyorum. İşleriniz rast gitsin hocamlar.