Bağlı Listeler, Manzara, Tımer, Onur Saylak


Veri yapılarına yönelik içeriklerin bir çoğu herhangi bir kullanım şekli (use-case) senaryosuna değinmek yerine sadece ne olduğuna dair genel bir bilgi veriyor. Bu tarz öğrenme şekli benimle çok uyuşmadığı ve öğrenme metodu benim gibi olan bireylerin varlığı için temel veri yapılarından olan bağlı listeyi (linked list) kendimce bir tane kullanım senaryosuna yedirerek bir yazı yazmak istedim.

Yukarda bulunan görsel üzerinden bağlı liste için genel terminolojiyi konuşabiliriz.

  1. Hepsi kendi başına bir birey (node <-> düğüm)
  2. En solda bulunan arkadaş baş düğümümüz (head, root node),
  3. En sağda duran ve halinden çok memnun gözükmeyen arkadaşımız ise son düğümümüz (tail node).
  4. Bu 4 arkadaşın bir araya gelmesi ile oluşan yapıyı ise bağlı liste (linked list) olarak adlandırabiliriz. Daha spesifik isimlendirecek olursak bu tek yönlü bir bağlı listedir(single linked list). (Çift taraflı bağlı listenin (double linked list) bu görsele göre nasıl olacağını bir canlandırabilirsiniz.)

Gelelim bir kullanım senaryosuna, donanımın bize sağladığı bir timer mekanizmasını kullanarak kendi yazılımsal timer modülümüzü yazmaya çalışacağız. Parkura çıktığımızı ve kronometremizi tetiklediğimizi düşünelim(System tick olarak düşünebiliriz.) Yaldır yaldır koşmaya başladık (askeri lise mülakatında öyle koşmuştum, tam tamına 400 metre) bu sürecinin sonunda neyi hedeflediğimizi bir çıkaralım.

  1. Bitirmek istediğimiz bir süre olması gerekiyor, [Interval]
  2. Hedeflediğimiz sürede parkuru bitirdiğimizde devam mı edeceğiz yoksa tamam kardeşim diyerek yere mi yığılıyoruz [Single Shot or Auto Reloaded Timer]
  3. Sürenin sonunda almamız gereken bir aksiyon var mıdır ? Bir su içelim ? [Callback when the timer expired]
  4. Koşunun ortasında bakmak isteyeceğimiz diğer bir olay ise kalan zaman. [remaining time]
  5. Koşuya ne zaman başladığımız [starting timestamp]

Koda dökmek istersek;

typedef enum timer_type
{
  SHOT,
  LOOP
}timer_type_e;

typedef struct timer_s
{   
  struct timer_s* timer_list; ///< Next entry in the single linked list
  timer_type_e type; ///< Single Shot or auto - reloaled
  void *arg; ///< Placeholder for argument will pass when the timer is expired.
  void (*exp_func)(void *); ///< Call the callback function When the timer is expired
  ut32_timer remaining_time_to_expire; ///< Remaining time to the fire!
  ut32_timer starting_timestamp; ///<Placeholder for starting timestamp
  ut32_timer interval; ///< milisecond
}timer_t;

Parkurda koşmaya devam ediyoruz ve sen yaparsın diyen çocuğun(bkz) babasıda koşmaya başladı. Koşmaya yeni başlayan bireyi tutmak ve halihazırda koşan birisine bağlamak için kullandığımız nokta ise tam olarak şurası.

struct timer_s* timer_list;

İlk koşmaya başlayan kişiyi referans alarak diğer koşan bireyleri de tutmaya başladık. Peki ilk koşan kişiyi kim tutacak ? Hadi gelin;

typedef struct timer_mngmnt_s
{
  timer_t* root; ///< Handle root
  ut8_timer count_of_active_timers; ///< Total number of the running timers;
}timer_mngmnt_t;

static timer_mngmnt_t tmr_mngmnt = {.root = NULL, .count_of_active_timers = 0};

Şimdi parkur başına bir tane koşucuyu alalım ve yukarda bahsettiğimiz hedeflerini bize bir söylesin.

void init_timer(timer_t *tmr, timer_type_e type, void *arg, void(*exp_func)(void*), ut32_timer interval)
{
  tmr -> timer_list = NULL;
  tmr -> type = type;
  tmr -> exp_func = exp_func;
  tmr -> arg = arg;
  tmr -> remaining_time_to_expire = 0;
  tmr -> interval = interval;
  tmr -> starting_timestamp = 0;
}

Koşmaya başlasın.

static void timer_add(timer_t *tmr)
{
  if (NULL == tmr_mngmnt.root)
  {
    tmr_mngmnt.root = tmr;
  }
  else 
  {
    timer_t *pos = tmr_mngmnt.root;
    
    while (NULL != pos -> timer_list)
    {      
      pos = pos -> timer_list;
    }
    
    pos -> timer_list = tmr;
  }

  ++tmr_mngmnt.count_of_active_timers;
}

Eğer parkur boş ise koşan ilk kişiyi baş düğüm olarak belirledik. Şayet halihazırda parkurda koşan diğer kişiler var ise parkura ilk çıkan kişi üzerinden en son çıkanı bulduk ve onaşiii (onunla ilişkilendirdik yazmak istedim ama kedi klavyeye bastı ve anı kalması adına silmiyorum.)

Tabi koşmaya başlayan bireyler hayatlarının sonlarına kadar koşmayacaklar ve burada iki senaryo mevcut.

  1. Kişi istediği zaman koşmayı bırakabilir.
  2. Kişi zaten parkuru tek sefer koşmak için gelmiştir.

Şimdi şayet bu kişiyle ilişkilendirilmiş bir kişi var ise bu adam parkurdan ayrılırsa ben onu sonra nereden bulacağım ? O yüzden bu kişi parkurdan ayrılmadan yapmam gerekenler var.

Diyelim parkurda 3 kişi var. (1 -> 2 -> 3) Ve 2. arkadaşımız ayrılmak istiyor. Bu durumda 1 -> 3 arasında ilişkiyi kurmalıyım. Nasıl mı ?

static void unlink_timer(timer_t *tmr)
{
  //Check timer is root ?
  //Ex: root (t1) -> t2 -> t3
  // (t1) == (unlinked_timer)
  // root -> t2 -> t3
  if (tmr_mngmnt.root == tmr)
  {
    tmr_mngmnt.root = tmr -> timer_list;
    --tmr_mngmnt.count_of_active_timers;
    return;
  }

  timer_t *prev = tmr_mngmnt.root;

  for (timer_t* pos = tmr_mngmnt.root; NULL != pos; pos = pos -> timer_list)
  {
    // Ex; root (t1) -> t2 -> t3
    // (t1 -- t2) == unlinked_timer
    // t1 -> t3
    if (prev -> timer_list == tmr)
    {
      prev -> timer_list = tmr -> timer_list;
      --tmr_mngmnt.count_of_active_timers;
      break;
    }    
    //Update to previous one
    prev = pos;
  }  
}

Koşan bireylerin parkura çıkmadan önce belirledikleri hedefleri bir göz gezdirelim.(linked list traverse) Ve bu hedefler doğrultusunda aksiyonlar alalım.

void timer_pool(void)
{
  for (timer_t* pos = tmr_mngmnt.root; NULL != pos; pos = pos -> timer_list)
  {
    if ((get_systick() - pos -> starting_timestamp) >= pos -> interval)
    {
      /* Call expire func */
      if (pos -> exp_func != NULL)
      {
        pos -> exp_func(pos -> arg);
      }

      if (pos -> type == SHOT)
      {
        unlink_timer(pos);
      }
      /* Reload the timer */
      else if (pos -> type == LOOP)
      {
        pos -> starting_timestamp = get_systick();
      }
    }
  }
}

Projenin Atmega328p üzerinde örnek çalışmasını görmek için github reposuna gelebilirsiniz.

Bu yazının ilhamı olan manzarama teşekkür ederek yazıyı sonlandırmak isterim.

Durmuş Kapı Kapanmıyor,Hay Aksi !


Not : Bu yazıyı 5 yıl önce yazmışım, biriken taslakları temizlerken gördüm. Yayına alayım dedim 🙂

Bir okul düşün  Türkiye’nin 5. büyük kampüsünde, Çukurova Üniversitesindesindesin. Tırlar GELİYOR ,sarı , samimi , kendini ait hissettiğin tırlar ; 5 girişimci dünyayı değiştirecekmiş. Ya nazım usta birer kebap uzat arkadaşlara , acılı bir de şalgam ver , neymiş dertleri bu 5 girişimcinin öğrenelim. Tırlar  KURULUYOR  o da ne 3 Boyutlu yazıcı mı lan  o ? , ellemeyelim abi bozulur diyorsun içinden(Ama durur musun hiç ). Tanımıyorsun ki dünyayı değiştircekler onu biliyorsun. İlk geceden izin istiyorsun ve biliyorsunki içerde montajı yapılmamış bir tane daha var, onu yapayım en azından bozamam diyorsun, içinden tabi. Yapıyorsun az çok ama gözün çalışanda. Şu neymiş bu neymiş derken onu sök bunu değiştir o da ne çalışmıyor ! . Oğlum ne yaptın adamlar dünyayı değiştircekler ! sen yazıcılarını bozuyorsun diyorsun tabi yine içinden. Bozduğun gibi tekrar çalıştıyorsun fakat yine bozuyorsun bu sefer yazılımıyla oynamışsın aha ! Neyse ki Stijn geliyor , öğretiyor. Envai çeşit eğitimler alıyorsun, yatırımcı sunumundan tutta ,arduino eğitimine kadar. O Türkiye’nin 5.büyük kampüsü var ya  3 tane Sarı Konteynıra sığıyor ! Şaşırıyorsun, zaman geçiyor tabi. Tırlar TOPLANIYOR bir başka üniversiteyi konteynıra sığdırmaya gidiyor. Bir bakıyorsun Şile’deler. Haydi diyip sende gidiyor, kuruluyorsun oraya. Hala aynı sarı, samimi, müthiş insanlar. Sunumda  Memet abi , 5 girişimci dünyayı değiştirebilir mi diyor ! , sonra Mustafa abi herkesin içindeki dünyadan bahsediyor. Değiştiriyorsunuz ulan işte diye haykırmak istiyorsunuz. Değiştirir diyorsunuz. Eğitim sisteminin ortasındaki koca çatlağın merhemi olan bir oluşum diyorsun. Liseliler gelecekmiş 1 hafta daha kalayım en iyisi diyorsun. Nerden bilebilirsin onlar giderken ağlayacaksın, nerden bileceksin hepsi sana bir şeyler katmış. Siz benim neler çektiğimi ner.. şaka şaka 🙂 ,

Durmuş kapı kapanmıyor abi Adana’da gayette rahat kapanıyordu , “aşağı doğru çek aşağı , sonra hafiften kendine doğru çek”  .

-Çikırt

-Kapandı abi hadi gidelim , sabah Teknoparka gideceğin erken uyanmak lazım.

Farkında değilsin zaten saat sabah olmuş , Böyle bir yer işte InnoCampus.

Çok yaşayın siz dünyayı değiştirmek isteyen insanlar..

Hesap Makineleri Bizi Nasıl Görür ? [Fetişist Makineler]


Kentsel dönüşüme kafa tutan, ne döndüğünü tam kestiremediğimiz bir mahallenin çıkmaz sokağında kaçak katlı bir yapının kapısı kilit tutmayan zemin katında kendinden 2+1 olup, 3+1’e çevirdiğimiz bahçeli bir ev. Sıkça aksattığımız kira, eve geldiğimizde patileri ıslanmış kedimiz Pablo, çoğu zaman çarşafsız bir şekilde duran yer yatağım. Sadece bende kişneyen ve çıplak ayakla gezebileceğim odam. Ve tabi iki tane hayat arkadaşım skolyozlu Süleyman ve sadece konuşursa Maraşlı olduğuna ikna olabildiğim Can. Saatlerce, sıklıkla sabahlara kadar e-dilencilikle aldığım modülleri, geliştirme kartlarını çalışmak için kullandığım plastik düğün sandalyesi ve masası. Düşününce öğrencilik dönemine ait en temiz özeti burası verdi galiba. Uzatmalarını oynadığım okulun son döneminde Ozan ile kaldığımız bir evde var tabi. Ama apartman orası. Ama saatlerce Ozan’ın potansiyelimi hissetmem için benimle konuştuğu bir daireyi içerisinde barındıran apartman. Alt paragrafı yazarken aklıma gelen InnoCampus ortamıda listede zirveye aday.

Pablo

Yağ ağbi ne kıymetli malın varmış a****, dursun bizde bu ver sen bana ile başlayan bir diyalog sonrasında sahibi olduğunu hissettiğim Bim’de satışı yapılan Corby marka döner kanatlı insansız hava aracı(!?). İş eğitimi dersinin hocası tarafından aranan atölyenin paspas çocuğu durur mu, olum adamlar nasıl da yapmış, hangi işlemciyi kullanmışlar, hangi wifi çipini kullanmışlar derken parçalanmış bir insansız hava aracına (geri toplanamayan) dönüşüyor. Parçalarken ulan ufacık kamera kartı, ufacık işlemci ağbi nasıl yapıyorlar acaba diye sorguladığım bir kart. Sonrasında ana devreye bağlantısını izleyerek + ve – uçlarını bulup kuvvetle muhtemel ayağımın altına yapışmış olduğunu bildiğim bir jumper kablosunu lehimlemekle sonlanıyor sövüşlerim ve Ananın ki diye devam ediyor, enerji vermem ile birlikte telefonda wifi yayınlarını listediğim zaman gördüğüm isim ile. Uygulamasını açıp bağlanıyorum ve BINGO. Bizim evin camlarının sunduğu kadar netlikte bir görüntü. Surat ifademi herhalde en iyi şu dakikalar açıklar. (Bkz : Link ). Şükür seviyesinde insanız, Murat Göğebakan gibi zor sanattır hasretlik demek mümkün değil belki ama zor sanattır çaresizlik demekte hakkımız zira bölümü sonuncu bitirmişiz neticede. Hele bir kere uzatmayı gör ha derdi Murat Göğebakan abim paragrafın burasında. Çık diyorum aklımdan çık bu kamerayı hesap makinesi içerisine gömmeyi (Demiyorum elbette, nereden geldiğini bilmediğim hesap makinesini parçalamıştım bile).

Bizde laboratuvar sınavları gruplu olur ve bir önceki grup kapıdan çıkarken milli maç için sahaya inen Tuncay Şanlı gibi girerdik laboratuvara. Sadece o çıkan grup ile aynı sorular geldiğini bildiğimiz diğer grup oyuncularıyla konuşmak yasaktı ve sadece 10-15 saniye yüzlerini görür ya da görmezdik. Ama anlardım o sınavı batırıp batıramayacağımı (Hoş batırmadığım sınav çok çok az oldu). Peki ya bizim gibi bitik tayfadan insanlar benim hesap makinesini alarak sınava girse ve bizde kapıda beklerken wifi ağına bağlanarak kağıdın üzerinde bulunan sonrasında yüzleşeceğimiz o soruları görsek nasıl olurdu ? (Allah’ım lütfen sabahın 8.30’unda sınava giren ilk grup içinde olmayayım…). Ya abi o değilde bu NFT işlerine kafam vallahi basmıyor, Refik Anadol’un eseri 1.2 milyon dolara satılmış NFT lobisi var diyolaa…

Hayde…

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

STM32 Uzaktan Gömülü Yazılım Güncelleme


Merhabalar, bu içeriği bootloader serisinin son yazısı olarak düşünebiliriz. Şayet direkt olarak bu yazıyla karşılaştıysanız ancak konu kümülatif bir şekilde ilerlediği için, şuradan diğer içerikleri incelemeniz sizin için faydalı olacaktır. Daha önce Duru isminde bir non-blocking, kullanımı rahat ve farklı MCU ailelerinden üyelerine port edilmesi mümkün ve rahat olan bir framework çalışmamı yayınlamıştım. Bugün ki uygulamamız yine o yapı üzerinde çalışacaktır.

Ne yapacağımızı daha rahat anlamak adına aşağıda bulunan görseli oluşturdum. Görsel Modemin (UC20 QUECTEL) ayağa kaldırılması gibi süreçleri içermiyor. Periyodik olarak backend tarafında yeni bir yazılım olup olmadığını sorguladığımız kısım özelinden başlıyor.

Belirlediğim formata göre sunucu üzerinde dosya şu şekilde barınmakta.

 can_%u.%u.%u.bin 
      |  | +---- bugfix-id
      |  + ----- minor-id
      + -------- major-id

Dosyanın hazırlanma sürecinde derleyici üzerinde yapılması gerekenlerden daha önce bahsetmiştim. Şimdi ise dosyanın içeriğinden bahsedeyim. ‘projeadı.bin’ dosyası oluştuktan sonra bu dosyayı Hex Editor programı üzerinden açarak dosyanın CRC32’sini buradan hesaplayarak ekliyorum ve ardından yayına verilecek versiyon damgasını 4 byte şeklinde ekliyorum.

Şimdi yazılım aşamasında Duru için gerekli yapıyı hazırlayalım, bunun için öncelikle ilgili callbackleri hazırlayalım. Ayrıca ilgili AT komutlarının kullandığınız modeme göre değişiklik gösterebileceğini unutmayınız.

/* İlgili sunucuya modem üzerinden ftp kanalı açılır */
uint8_t ftp_connect(void* self)
{	
	gsm_machine_debug("\r\n Host Connected !! \r\n");

	struct command_machine *cm = (struct command_machine*)self;
	
	char cr = {0x0D};
	
	char cmd[60] = { 0 };
	
	snprintf(cmd, sizeof(cmd), "AT+QFTPOPEN=\"%s\",%i%c","ftpservername", 21, cr);

	cm -> uart_tx_cb((uint8_t*)cmd, strlen(cmd));
	
	return true;
}
/* Sunucu üzerinde bulunan dosyalar listelenir, ilgili dosyanın olması durumunda yeni versiyon olup olmadığı flash üzerinde saklanılan adresten sorgulanır */
uint8_t parse_list_file (void* self, const char* buf, char* record_buf)
{
	if (self != NULL && strlen(buf) > 0)
	{
		char* ret = NULL;
		
		ret = strstr(buf, "CONNECT\r\n");
		
		if (ret != NULL)
		{
			ret = ret + sizeof("CONNECT\r\n");
			
			int i = 0;
			
			memset(gsm_uart.update_file_name, 0, sizeof(gsm_uart.update_file_name));
			
			while (*ret != '\r')
			{
					gsm_uart.update_file_name[i] = *ret++;
					i++;
			}

			ret = strstr(gsm_uart.update_file_name, "can_");
			
			if (ret != NULL)
			{
				ret = ret + sizeof("can");
				
				sscanf(ret, "%u.%u.%u", &gsm_uart.major, &gsm_uart.minor, &gsm_uart.bugfix);
				
				uint32_t current_version = *(uint32_t* )VERSION_ADDR;
				
				if (current_version == 0xFFFFFFFF)
				{
					is_any_new_firmware = true;
				}
				
				else
				{
					uint8_t major = current_version >> 24;
					uint8_t minor = current_version >> 16;
					uint8_t bug_fix = current_version >> 8;			
					
					char cv[10] = {0};
				
					snprintf(cv, sizeof(current_version), "%u.%u.%u", major, minor, bug_fix);
				
					int ret_version = comp_version(cv, ret);
					
					if (ret < 0)
					{
						is_any_new_firmware = true;
					}
					
					//Yeni update degil ise direkt olarak çik.
					else
					{
						struct command_machine* cm = (struct command_machine*)self;
						cm -> command_index = cm -> command_index + 3;
					}
				}
			}
		}	
	}
		
	return 0;
}
/* Dosya Modemin RAM bölgesine indirilir */
uint8_t ftp_get_file(void* self)
{
	
	gsm_machine_debug("\r\n Host Connected !! \r\n");

	struct command_machine *cm = (struct command_machine*)self;

	char cmd[60] = { 0 };
	
	snprintf(cmd, sizeof(cmd), "AT+QFTPGET=\"%s\",\"RAM:can.bin\"\r\n", gsm_uart.update_file_name);

	cm -> uart_tx_cb((uint8_t*)cmd, strlen(cmd));
	
	return true;
}
/* Dosyanın boyutu sunucudan istenilir */
uint8_t ftp_get_size(void* self)
{
	
	gsm_machine_debug("\r\n Host Connected !! \r\n");

	struct command_machine *cm = (struct command_machine*)self;
		
	char cmd[60] = { 0 };	
	
	snprintf(cmd, sizeof(cmd), "AT+QFTPSIZE=\"%s\"\r\n", gsm_uart.update_file_name);

	cm -> uart_tx_cb((uint8_t*)cmd, strlen(cmd));
	
	return true;
}
extern bool is_any_new_firmware;
extern float file_size;

/* Dosyanın boyutu ve dosya sorgulanır */
uint8_t ftp_file_size_cb(void* self, const char* buf, char* record_buf)
{
	if (self != NULL && strlen(buf) > 0)
	{
		char* ret = NULL;
		
		ret = strstr(buf, "OK\r\n\r\n");
		
		if (ret != NULL)
		{
			if (strstr(ret, "627") || strstr(ret, "550"))
			{
				is_any_new_firmware = false;
			}
			
			else 
			{
				ret = strstr(ret, "0,");
				
				if (ret)
				{
					ret = ret + 2;
					
					file_size = atof(ret);
					
					is_any_new_firmware = (file_size > 0) ? true : false;
				}
			}
		}
	}
	
	return 0;
}
/* Versiyonlar karşılaştırılır */
static int comp_version (const char* cv_version1, const char* version2 ) {
    unsigned major1 = 0, minor1 = 0, bugfix1 = 0;
	
    unsigned major2 = 0, minor2 = 0, bugfix2 = 0;
	
    sscanf(cv_version1, "%u.%u.%u", &major1, &minor1, &bugfix1);
	
    sscanf(version2, "%u.%u.%u", &major2, &minor2, &bugfix2);
	
    if (major1 < major2) return -1;
    if (major1 > major2) return 1;
    if (minor1 < minor2) return -1;
    if (minor1 > minor2) return 1;
    if (bugfix1 < bugfix2) return -1;
    if (bugfix1 > bugfix2) return 1;
    return 0;
}
/* Eğer herhangi bir güncelleme yok ise sorgulama durdulur */
int reset (void* self)
{
	if (self != NULL)
	{		
		struct command_machine* cm = (struct command_machine*)self;
		
		cm -> command_index = cm -> command_index + 3;
		
		//machine_stop_current(self);
	}
	
	return 0;	
}

Gelelim ilgili komutları ve komut pencerelerini hazırlamaya,

struct command ftp_open = {"FTP", "QFTPOPEN: 0,0\r\n", NULL, NULL, NULL, 2000, &ftp_connect };

struct command ftp_set_current_dir = {"AT+QFTPCWD=\"/\"\r\n", "QFTPCWD: 0,0", NULL, NULL, NULL, 3000, NULL };

struct command ftp_get_list_file_names = {"AT+QFTPNLST=\"/\",\"COM:\"\r\n", "+QFTPNLST:", NULL, NULL, &parse_list_file, 3000, NULL, reset};

struct command ftp_get_file_size = {"get_size", "QFTPSIZE:", NULL, NULL, &ftp_file_size_cb, 3000, &ftp_get_size };

struct command ftp_get_file_to_ram = {"get_file", "QFTPGET:", NULL, NULL, NULL, 3000, &ftp_get_file };

struct command ftp_close = {"AT+QFTPCLOSE\r\n", "+QFTPCLOSE: 0,0", NULL, NULL, NULL, 1500, NULL };

struct command ftp_deactivate_pdp = {"AT+QIDEACT=1\r\n", "OK\r\n", NULL, NULL, NULL, 4000, NULL };
const struct command* ftp_command_list_table[] = 
{
	&ftp_open,
	&ftp_set_current_dir,
	&ftp_get_list_file_names,
	&ftp_get_file_size,
	&ftp_get_file_to_ram,
	&ftp_close,
	&ftp_deactivate_pdp,	
};

Ve yeni bir güncelleme var ise artık dosyamızı sd karta yazdırabiliriz.

		if (is_any_new_firmware  && get_command_window_status(&command_machine_t, "FTP"))
		{
			// Herhangi bir yarım kalma durumları için dosya var olup olmaması sınanır.
			if ((fr = f_open(&file, "can.bin\0", FA_READ)) == FR_NO_FILE)
			{
					//Nothing.
			}
			
			else
			{
				f_close(&file);
				
				f_unlink("can.bin\0");
			}
			
			temp = file_size / 1000;
			
			temp_remainig = (file_size) - (temp * 1000);
		
			uart_tx_platform_specific((uint8_t *)"AT+QFOPEN=\"RAM:can.bin\",2\r\n", strlen("AT+QFOPEN=\"RAM:can.bin\",2\r\n"));
		
			HAL_Delay(500);
			
			while(temp-- > -1)
			{		
				int data_size = temp > -1 ? 1000 : temp_remainig;
			
				char temp_buf[75] = { 0 };
				
				snprintf(temp_buf, sizeof(temp_buf), "AT+QFREAD=3000,%d\r\n", 1000);
				
				clear_gsm_uart(&gsm_uart);	
				
				uart_tx_platform_specific((uint8_t *)temp_buf, strlen(temp_buf));
				
				HAL_Delay(1500);
			
				if (strlen(gsm_uart.buffer) > 0)
				{						
					char* ret = NULL;
					
					ret = strstr(gsm_uart.buffer, "CONNECT");
					
					if (ret != NULL)
					{
						ret = ret + sizeof("CONNECT");
						
						while (*ret++ != '\n');
						
						if ((fr = f_open(&file,(char *)"can.bin\0", FA_OPEN_APPEND | FA_WRITE)) == FR_OK)
						{												
							UINT bw;
							
							f_write(&file, ret, data_size, &bw);  
							
							fr = f_close(&file);	
						}						
					}
				}
			}
			
			NVIC_SystemReset();
		}

Aynı MCU içerisinde daha önce yazmış olduğum STM32 SD KART ÜZERİNDEN BOOT ETMEK içeriğinde bulunan yazılım her reset sonrasında çalıştırılmakta.

Eğer modeminiz destekliyorsa, ilgili dosyayı modemin RAM bölgesine indirilmesini sağlamak gerçekten güzel bir kolaylık sağlamakta. Buna ek olarak ilgili SSL sertifikalarını kullanarak rahatlıkla FTPS işlemini de yapabiliyorsunuz.

Sözün özü; Duru kütüphanesini kullanarak güncelleme yapmak bu kadar basit 🙂

Herkese iyi çalışmalar dilerim.