Tag Archives: php

webmastering

Tworzenie pakietów ICMPPHP

Od tygo­dnia na poważ­nie wzią­łem się za rozpra­co­wy­wa­nie proto­ko­łów siecio­wych. Dzisiaj scho­dzimy na poziom najniż­szy jaki się da w progra­mo­wa­niu, czyli zer i jedy­nek. Ja jako osoba za 2 miesiące rozpo­czy­na­jąca zawo­dowo przy­godę z progra­mo­wa­niem (ojej, wydało się ;-) ) muszę mieć dosyć dobre poję­cie jak to się dzieje, że wpisuję adres www w pasku adresu prze­glą­darki, naci­skam enter i poka­zuje się Fejs­buk. Oczy­wi­ście zwykli użyt­kow­nicy inter­netu nie muszą wiedzieć jak to dokład­nie działa w myśl zasady „nie muszę być mecha­ni­kiem samo­cho­do­wym żeby jeździć autem”.

Wobec tego na warsz­tat wzią­łem na począ­tek rzecz dosyć prostą: ICMP, a nawet tylko jego wyci­nek pod nazwą Echo request/Echo reply. Komu­ni­katy ICMP stano­wią podstawę dzia­ła­nia inter­netu. Każda maszyna podłą­czona do sieci potrafi zada­wać pyta­nia i otrzy­my­wać odpo­wie­dzi w postaci ICMP. Tak naprawdę zdubluję funk­cjo­nal­ność wbudo­waną w każdy system opera­cyjny pod powszech­nie znaną nazwą ping.

Ręczne tworze­nie pakie­tów w PHP nie ma oczy­wi­ście więk­szego sensu poza eduka­cyj­nym (no chyba, że tworzymy coś niety­po­wego lub dosto­so­wu­jemy się do już istnie­ją­cego proto­kołu). Jeśli potrze­bu­jemy coś „pingnąć”, wywo­łu­jemy pole­ce­nie ping shella z poziomu skryptu PHP i tyle.

W Gist zamie­ści­łem klasę gotową do użytku. Na Linuk­sie będzie problem z jej odpa­le­niem, gdyż bez roota nie wywo­łamy funk­cji socket_create(). Ten temat jest na tyle ciekawy, że zosta­wię sobie na następny wpis.

Sama struk­tura proto­kołu jest dosyć prosta. Mamy 6 elementów:

  • 8 bitów typ żąda­nia (0x08 w przy­padku echo requ­est, 0x00 w przy­padku echo reply),
  • 8 bitów kod żąda­nia (0x00),
  • 16 bitów suma kontro­lna (tzw. inter­net check­sum, o którym trochę niżej),
  • 16 bitów iden­ty­fi­ka­tor (losowa liczba z zakresu 0x000 - 0xFFFF),
  • 16 bitów nr sekwen­cyjny (0x00),
  • 8– bitów dane (znaki ASCII).

Operu­jąc nisko­po­zio­mowo musimy zaprzy­jaź­nić się z funk­cjami pack()unpack() w celu stwo­rze­nia i odczytu repre­zen­ta­cji bito­wej ciągu. W przy­padku ICMP będzie to wyglą­dało tak:

const PACKET_REQUEST_TEMPLATE = 'CCnnnA*';
const PACKET_RESPOND_TEMPLATE = 'Ctype/Ccode/nchecksum/nuid/nseq/A*message';

Widać, że 1 bajt możemy odzwier­cie­dlić w postaci typu unsi­gned char, a 2 bajty w postaci unsi­gned short.

Teraz może nasu­nąć się pyta­nie jak wypeł­nić pole check­sum, skoro jest gdzieś w środku? Odpo­wiedź jest prosta: w pole wpisu­jemy tymcza­sowo 0x00, obli­czamy sumę kontro­lną dla cało­ści, a następ­nie zastę­pu­jemy wyni­kiem. Jest to dokład­nie 3 i 4 bajt. Jak obli­czyć sumę kontro­lną? Jest do tego RFC z 1988 r. pod nazwą Compu­ting the Inter­net Check­sum. Jednak zanim znie­chę­ci­cie się suchym żargo­nem infor­ma­tycz­nym wytłu­ma­czę po ludzku:

  1. Cały pakiet ćwiar­tu­jemy na kawałki po 16 bitów,
  2. sumu­jemy wszystko,
  3. jeśli w wyniku powstaje nam jakaś nadwyżka w postaci 17 i więcej bitu, odci­namy ją, a następ­nie doda­jemy do pozo­sta­łych 16tu,
  4. ponow­nie spraw­dzamy czy nie powstała nam nadwyżka,
  5. odwra­camy wynik.

Zapis w PHP:

protected function computeChecksum($packet) {
 
  // treat the whole packet as 16 bits unsigned short integers
  $seqPer16bits = unpack('n*', $packet);
  $sum = array_sum($seqPer16bits);
 
  // if there is a carry above 16 bit, add it at the beginning
  $sum = ($sum >> 16) + ($sum & 0xFFFF);
 
  // double check if there is no new carry after previous addition
  $sum += ($sum >> 16);
 
  // return 16 bits negate
  return pack('n', ~$sum);
}

Jak „wkleić” wynik w dobre miej­sce? Mała sztuczka:

$packet[2] = $checkSum[0]; // 3 bajt pakietu
$packet[3] = $checkSum[1]; // 4 bajt pakietu

Następ­nie możemy sobie stwo­rzyć gniazdo i usta­wić jakiś timeout:

$this->_socket = socket_create(
  AF_INET, SOCK_RAW, getprotobyname('icmp'));
 
socket_set_option(
  $this->_socket, SOL_SOCKET, SO_RCVTIMEO, array(
    'sec' => 1,
    'usec' => 0));

Jeste­śmy gotowi do wysyłki:

public function sendPacket($destination, $message = self::DEFAULT_MSG, $port = 0) {
  $packet = $this->getNewPacket($message);
  socket_sendto(
    $this->_socket, $packet, strlen($packet), 0, $destination, $port);
 
  $respond = 0;
  socket_recvfrom($this->_socket, $respond, 255, 0, $destination, $port);
 
  // strip IP header
  return substr($respond, 20);
}

Jeśli odbie­ramy pakiet, warto poddać go analizie:

public function analyzeRespond($responsePacket) {
  $unpackedRespond = unpack(self::PACKET_RESPOND_TEMPLATE, $responsePacket);
  if ($unpackedRespond['type'] !== self::TYPE_RESPONSE) {
    throw new Exception('Bad response type');
  }
 
  if ($unpackedRespond['uid'] !== $this->_uid) {
    throw new Exception('Bad unique id');
  }
 
  // set 3rd and 4th checksum byte to 0x00
  // in order to calculate correct checksum
  $responsePacket[2] = pack('C', self::INITIAL_CHECKSUM);
  $responsePacket[3] = pack('C', self::INITIAL_CHECKSUM);
  if (pack('n*', $unpackedRespond['checksum']) !== $this->computeChecksum($responsePacket)) {
    throw new Exception('Bad checksum');
  }
  return $unpackedRespond['message'];
}

Zdaję sobie sprawę, że opis jest niepełny. Chcia­łem zwró­cić uwagę na ciekawsze/trudniejsze momenty. Propo­nuję dokład­nie prze­ana­li­zo­wać sobie całą klasę z linka poda­nego powyżej.

Tak jak obie­ca­łem, w następ­nym wpisie opiszę jak to wywo­ły­wać w możli­wie bezpiecz­nym środowisku.

webmastering

O modyfikatorach w Smarty i opisie słownym interwałów czasu

Pomysł na wpis dał mi kilka dni temu Face­book, a dokład­niej ich total­nie niekon­se­kwentne ozna­cze­nia co się kiedy wyda­rzyło na naszej tablicy. Raz jest to 23 minut(y) temu, raz 7 godz. temu, jesz­cze inaczej około godziny temu. Być może różne ekipy robią osobno opisy czasu i stąd różnice. Posta­no­wi­łem dla sportu zmie­rzyć się z proble­mem. Powstało takie coś jak Time­span Smarty Modi­fier.

Opis jak to zain­sta­lo­wać, wyma­ga­nia i sposób użycia może­cie sobie prze­czy­tać w moim kale­czo­nym angiel­skim w GitHu­bie. Tutaj chciał­bym się skupić jak to działa. Najwy­god­niej­szym sposo­bem korzy­sta­nia z biblio­teki jest poprzez mody­fi­ka­tor w szablo­nach Smarty, a więc {$jakaśZmienna|naszModyfikator}.

Przede wszyst­kim sama nazwa pliku musi nazy­wać się modifier.nazwa.php, za to funk­cja smarty_modifier_nazwa($argument). Pierw­szy argu­ment auto­ma­tycz­nie dostaje wartość zmien­nej, dla której wywo­łu­jemy mody­fi­ka­tor. Jeśli chcemy, następne poda­jemy po dwukropku. Drobna uwaga: nigdy niczego nie „echu­jemy”, tylko zwra­camy poprzez return. Chodzi o łańcu­chowe łącze­nie modyfikatorów.

Z uwagi na to, że całość rozro­sła się do ponad 400 linii kodu, nie ma sensu wszyst­kiego tutaj przy­ta­czać. Skupię się na najważ­niej­szych rzeczach.

Całość ma formę klasy abstrak­cyj­nej rozsze­rza­nej przez poszcze­gólne języki. Na podsta­wie zade­kla­ro­wa­nego języka skrypt próbuje znaleźć sobie właściwą klasę (fragm. modifier.timespan.php):

$className = 'Spiechu\TimeSpan\TimeSpan' . strtoupper($lang);
   if (class_exists($className)) {
      $timeSpan = new $className();
 
      // sprawdz czy klasa rozszerza AbstractTimeSpan
      if (!($timeSpan instanceof Spiechu\TimeSpan\AbstractTimeSpan)) {
         $timeSpan = new Spiechu\TimeSpan\TimeSpanEN();
      }
   } else {
 
      // jesli nie ma takiego jezyka lub klasa niewlasciwa uzywam angielskiego
      $timeSpan = new Spiechu\TimeSpan\TimeSpanEN();
   }

Potem tylko konfi­gu­ru­jemy klasę i zwra­camy wynik:

$timeSpan->setStartDate($date)->showSuffix($suffix);
return $timeSpan->getTimeSpan();

W klasie Abstract­Ti­me­Span z kolei mamy trochę logiki zwią­za­nej z obli­cza­niem interwału:

$curDate = new \DateTime('now');
$diff = $curDate->diff($this->_startDate); //otrzymujemt obiekt DateInterval

Począw­szy od najwięk­szej jednostki (rok) odpy­tu­jemy $diff która z jego zmien­nych publicz­nych jest więk­sza od 0 oraz za pomocą metod isHalfUnit($actualUnit, $fullUnit), almostFullUnit($actualUnit, $fullUnit) spraw­dzamy czy może prze­kro­czy­li­śmy połowę obec­nej jednostki, więk­szej jednostki oraz czy nie można jej zaokrą­glić do następ­nej całej.

Klasy rozsze­rza­jące imple­men­tują m.in. metodę getUnit($howMany, $unit­Sym­bol, $half). Liczeb­niki angiel­skie są tak trudne, że potrzeba na nie aż 1 linijki:

if ($howMany > 1) $howMany = 2;
return $this->_units[$howMany][$unitSymbol];

Jeśli mowa o $this->_units to wygląda to tak:

private $_units = array(
   -1 => array('s' => 'just now'),
   0 => array('i' => 'half minute',
      'h' => 'half hour',
      'd' => 'half day',
      'm' => 'half month',
      'y' => 'half year'),
   1 => array('s' => 'a second',
      'i' => 'a minute',
      'h' => 'an hour',
      'd' => 'a day',
      'm' => 'a month',
      'y' => 'a year'),
   2 => array('s' => 'seconds',
      'i' => 'minutes',
      'h' => 'hours',
      'd' => 'days',
      'm' => 'months',
      'y' => 'years')
);

Polskie jednostki z kolei prezen­tują się dużo bardziej okazale (3 odmiany):

private $_units = array(
   -1 => array('s' => 'przed chwilą'),
   0 => array('i' => 'pół minuty',
      'h' => 'pół godziny',
      'd' => 'pół dnia',
      'm' => 'pół miesiąca',
      'y' => 'pół roku'),
   1 => array('s' => 'sekundę',
      'i' => 'minutę',
      'h' => 'godzinę',
      'd' => 'dzień',
      'm' => 'miesiąc',
      'y' => 'rok'),
   2 => array('s' => 'sekundy',
      'i' => 'minuty',
      'h' => 'godziny',
      'd' => 'dni',
      'm' => 'miesiące',
      'y' => 'lata'),
   5 => array('s' => 'sekund',
      'i' => 'minut',
      'h' => 'godzin',
      'd' => 'dni',
      'm' => 'miesięcy',
      'y' => 'lat')
);

Mało tego, w polskiej klasie mamy jesz­cze jednostki specjalne :-) :

private $_specialUnits = array(
   'poltora' => array('s' => 'półtorej sekundy', // nigdy nie uzywane
      'i' => 'półtorej minuty',
      'h' => 'półtorej godziny',
      'd' => 'półtora dnia',
      'm' => 'półtora miesiąca',
      'y' => 'półtora roku')
);

Metodę getU­nit() przy­ta­czam w całości:

protected function getUnit($howMany, $unitSymbol, $half) {
 
   // kluczowa liczba, do ktorej wszystkie maja odmiane jak '5', potem tylko z jedynka na koncu
   if ($howMany > 21) {
 
      // badamy ostatnia cyfre 
      $howMany = substr($howMany, -1);
 
         // jesli to 1 to odmienia sie jak '5'
         if ($howMany <= 1) {
            $howMany = 5;
 
         // jesli nie to rekurencyjnie dla samej ostatniej cyfry
         } else {
            return $this->getUnit($howMany, $unitSymbol);
         }
   } elseif ($howMany >= 5) {
      $howMany = 5;
   } elseif ($howMany >= 2) {
      $howMany = 2;
 
   // jesli mamy '1 i pol' to odpytujemy specjalne jednostki
   } elseif ($howMany == 1 && $half == true) {
      return $this->_specialUnits['poltora'][$unitSymbol];
   }
   return $this->_units[$howMany][$unitSymbol];
}

Mamy już jednostki, teraz tylko skleić to w jeden ciąg znaków. Zajmuje się tym metoda getTi­me­Span() z klasy Abstract­Ti­me­Span:

public function getTimeSpan() {
   $interval = $this->getInterval();
   $timeUnit = $this->getUnit($interval['counter'], $interval['unit'], $interval['half']);
 
   // jesli gdziekolwiek dane byly szacowane to dorzucamy 'okolo'
   $prefix = ($interval['approx']) ? $this->getPrefix() . ' ' : '';
 
   // jesli mamy co najmniej 1 i pol
   $half = ($interval['half'] && $interval['counter'] > 0) ? $this->getHalf() . ' ' : '';
 
   // sprawdzamy czy chcemy 'temu'
   $suffix = ($this->_showSuffix) ? ' ' . $this->getSuffix() : '';
 
   // podajemy razem z liczba jednostkek
   if ($interval['counter'] > 1) {
      return $prefix . $interval['counter'] . ' ' . $half . $timeUnit . $suffix;
 
   // podajemy bez liczby jednostek
   } elseif ($interval['counter'] >= 0) {
      return $prefix . $timeUnit . ' ' . $half . $suffix;
 
   // zostala tylko mozliwosc, ze to 'przed chwila', czyli offset -1
   } else {
      return $timeUnit;
   }
}

Domyślne warto­ści to 10 sekund dla komu­ni­katu przed chwilą, 15% tole­ran­cji dla połowy jednostki (np. około 2 i pół godziny temu lub około pół godziny temu) i 15% tole­ran­cji dla cało­ści jednostki (np. około godzinę temu).

Myślę, że całość da się łatwo prze­nieść do innych syste­mów szablo­nów (od biedy w samym PHP też w końcu można). Póki co brakuje testów jednost­ko­wych, I’m working on it :-D

webmastering

Reprezentacja bitowa znaków Unicode i UTF-8

Jakiś taki naukowy ten tytuł wyszedł. Ale inaczej się chyba nie da. Potrze­bo­wa­łem pretek­stu żeby się trochę poba­wić w mani­pu­lo­wa­nie bitami. Padło na Unicode z racji fajnego sposobu, w jaki wymy­ślono sam zapis znaków. Jeśli jest jesz­cze ktoś kto nie wie co to jest Unicode to zapra­szam do źródła.

Wszystko zostało tak prze­my­ślane, że im bardziej pokrę­cony język, tym więcej miej­sca potrzeba na jego zapi­sa­nie. Wszyst­kie liczby i litery bez ogon­ków damy radę zapi­sać w postaci 1 bajta. Chodziło o kompa­ty­bil­ność z forma­tem ASCII. Znaczki języka polskiego znaj­dują się w dziale Latin Extended-A. Zapis znaków polskich zajmie 2 bajty w forma­cie UTF-8.

Mając numer znaku z tabeli Unicode najła­twiej wyświe­tlić go za pomocą wbudo­wa­nej w PHP funk­cji html_entity_decode():

echo html_entity_decode('&#' . 0xA7 . ';', ENT_NOQUOTES, 'UTF-8');

Wywo­ła­nie powyż­szego wier­sza spowo­duje wyświe­tle­nie znaku o kodzie 0xA7 — para­grafu §. W jaki więc sposób wyko­nuje się czary-mary i znak Unicode staje się znakiem UTF-8? Weźmy na warsz­tat znak ę opisany w tablicy jako latin small letter e with ogonek. Ma numer 0119 (heksa­de­cy­mal­nie!), czyli można zapi­sać tak:
0×11916 = 28110 = 1000110012
Z powyż­szego widać, że liczbę dzie­siętną 281 możemy zapi­sać w postaci 9 bitów. Doku­men­ta­cja funk­cji utf8_encode() zawiera tabelkę ile bitów znaku zmie­ścimy w ilu bajtach UTF-8. Wygląda na to, że w jedno­baj­to­wym zapi­sie zmie­ścimy znaki o nume­rach od 0 do 12710 (7 bitów). Za to dyspo­nu­jąc 2 bajtami zapi­szemy liczby aż do 204710, czyli w zupeł­no­ści nam wystarczy.

Znak ę w UTF-8 zapi­su­jemy w 2 bajtach, czyli do liczby 110000002 musimy dodać prze­su­niętą o 6 bitów w prawo liczbę 281 (ponie­waż pójdą do drugiego bajtu znaku). Z drugiego wyrzu­camy wszystko poza 6 ostat­nimi bitami, a następ­nie doda­jemy do liczby 100000002.

$uniChar = 0x119;
$byte1 = $uniChar >> 6 | 0xC0;
$byte2 = $uniChar & 0x3F | 0x80

Wyszło na to, że ę w zapi­sie UTF-8 to będzie 11000100 10011001. Po ubra­niu tego wszyst­kiego w funkcję:

function hexToUTF8HexArray($hexNum)
{
   // konwertujemy na liczbe w razie czego
   if (is_string($hexNum)) $hexNum = hexdec($hexNum);
 
   $hexArray = array();
   if ($hexNum < 0x80) {
      $hexArray[0] = '0x' . dechex($hexNum);
      return $hexArray;
   } elseif ($hexNum < 0x800) {
      $hexArray[0] = '0x' . dechex($hexNum >> 6 | 0xC0);
      $hexArray[1] = '0x' . dechex($hexNum & 0x3F | 0x80);
      return $hexArray;
   } else {
      throw new Exception('Not supported');
   }
}

Wywo­łu­jąc hexToUTF8HexArray(0x119) da nam tablicę z warto­ściami 0xc4 i 0×99. No dobra, a co jak chcę odwró­cić proces? Teraz będzie przyjemniej:

function utf8ToUnicode(array $utf8Array)
{
   // konwertujemy na liczby
   $utf8ArrayChecked = array();
   foreach ($utf8Array as $utf8) {
      if (is_string($utf8)) $utf8 = hexdec($utf8);
      $utf8ArrayChecked[] = $utf8;
   }
 
   $bytesCount = count($utf8ArrayChecked);
   switch ($bytesCount) {
      case 1:
         return $utf8ArrayChecked[0];
      case 2:
         // wyrzucamy naglowki
         $b1 = $utf8ArrayChecked[0] & 0x1F;
         $b2 = $utf8ArrayChecked[1] & 0x3F;
 
         // tutaj cala magia
         $number = $b1 << 6 | $b2;
         return '0x' . dechex($number);
      default:
         throw new Exception('Not supported'); 
   }
}

Wywo­łu­jąc utf8ToUnicode(array(0xc4, 0x99)) otrzy­mamy 0×119, czyli gra jak trzeba.

Na koniec cieka­wostka: mając jakiś znak możemy łatwo wydo­być jego wartość UTF-8.

function charToUTF8HexArray($char)
{
   $i = 0;
   $hexArray = array();
   while (isset($char[$i])) {
      $hexArray[] = '0x' . dechex(ord($char[$i++]));
   }
   return $hexArray;
}

charToUTF8HexArray('ę') da nam tablicę 0xc4, 0×99.

Ten wpis to taka trochę notatka dla mnie, przez co może trochę chaotycz­nie napisany…