tedtheripper
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # PSI - Projekt, raport ostateczny <div style="text-align:right"><b>Data</b>: 18.01.2021, <b>Wersja</b>: 2.0, <b>Wariant</b>: W21</div> Zespół: - Przemysław Rozwałka (lider) - przemyslaw.rozwalka.stud@pw.edu.pl - Jakub Budrewicz - Filip Drygaś - Marcel Jarosz Treść > Napisać program obsługujący prosty protokół P2P (Peer-to-Peer). > Założenia: > • Zasób to plik identyfikowany pewną nazwą, za takie same zasoby uważa się zasoby o takich samych nazwach. > • Początkowo dany zasób znajduje się w jednym weźle sieci, następnie może być propagowany do innych węzłów w > ramach inicjowanego przez użytkownika ręcznie transferu (patrz dalej) – raz pobrany zasób zostaje zachowany > jako kopia. > • Tak więc, po pewnym czasie działania systemu ten sam zasób może znajdować się w kilku węzłach sieci (na kilku > maszynach). > • Program ma informować o posiadanych lokalnie (tj. w danym węźle) zasobach i umożliwiać ich pobranie. > • Program powinien umożliwać współbieżne: > ◦ wprowadzanie przez użytkownika nowych zasobów – z lokalnego systemu plików, > ◦ pobieranie konkretnych nazwanych zasobów ze zdalnego węzła, > ◦ pobieranie zasobów „w tle” (także kilku jednocześnie), > ◦ rozgłaszanie informacji o posiadanych lokalnie zasobach. > • W przypadku pobierania zdalnego zasobu system sam (nie użytkownik) decyduje skąd zostanie on pobrany. > • Zasób pobrany do lokalnego węzła jest kopią oryginału, kopia jest traktowana tak samo jak oryginał (są > nierozróżnialne) – t.j.: istnienie kopii jest rozgłaszane tak samo jak oryginału. > • Należy zwrócić uwagę na różne obsługę różnych sytuacji wyjątkowych – np. przerwanie transmisji spowodowane > błędem sieciowym. > • Lokalizacja zasobów ma następować poprzez rozgłaszanie – wskazówka: użyć prot. UDP, ustawić opcje gniazda > SO_BROADCAST, wykorzystać adresy IP rozgłaszające (same bity “1” w części hosta). > • Można dodatkowo wprowadzić skrót lub podpis, aby zapewnić jednoznaczność identyfikacji zasobów. > • Interfejs użytownika – wystarczy prosty interfejs tekstowy, powinien on jednak obsługiwać współbiezny transfer > zasobów (tj. Nie powinien się blokować w oczekiwaniu na przesłanie danego zasobu) Wariant W21: > Zasymulowac programowo błędy transmisji w zakresie rozgłaszania – tj. gubienie datagramów rozgłaszanych. # 1. Interpretacja zadania Mamy przygotować program do wymiany plików między użytkownikami w ramach pojedynczej podsieci stosując podejście peer-to-peer. Założenia techniczne: - Nazwa pliku o maksymalnej długości określonej w pliku konfiguracyjnym, jest niepowtarzalnym identyfikatorem zasobu w lokalnym repozytorium - Dane o dostępności pliku zawierają również jego długość (w bajtach) oraz skrót SHA256 jego zawartości - Program tworzy oraz dynamicznie aktualizuje listę aktywnych węzłów w sieci - Lokalizacja zasobu odbywa się poprzez rozgłoszenie datagramu zawierającego nazwę szukanego pliku, a wszystkie węzły w sieci zwracają wynik zapytania - W celu pobrania pliku, węzeł źródłowy wybierany jest losowo spośród wszystkich węzłów zawierających dany plik - Po zakończeniu transferu, skrót pliku jest weryfikowany; jeżeli zostanie stwierdzona utrata integralności pliku, cały transfer jest ponawiany (węzeł źródłowy wybierany jest na nowo) - Każdy plik pobrany przez dany węzeł automatycznie staje się udostępniany dla sieci - Wszelkie meta-dane o pliku (skrót, status) przechowywane są w odpowiadającym pliku YAML w ukrytym folderze '.meta' # 2. Opis funkcjonalny Założenia funkcjonalne naszej implementacji: - Pliki rozgłaszane są przez klientów, którzy pobrali je w pełni i zweryfikowali ich poprawność z hashem lub dodali plik ręcznie do listy rozgłaszanych plików - Program działa w trybie interaktywnym - Wyłączenie aplikacji powoduje: - Przerwanie pobierania (po ponownym uruchomieniu programu, pobieranie jest wznawiane) - Zapisanie listy rozgłaszanych plików (wraz z lokalizacjami tych plików) - Interfejs użytkownika zrealizowany jest w konsoli i pozwala na: - Wyświetlenie dokumentacji użytkowej - `help` ``` > help Documented commands (type help <topic>): ======================================== add download exit help info peers remove search status ``` - Dodanie pliku - `add <file_path>` ```` > add /home/starwader/TempleOS.ISO Added file TempleOS.ISO with digest 5d0fc944e5d89c155c0fc17c148646715bc1db6fa5750c0b913772cfec19ba26 ```` - Pobranie pliku - `download <filename>` `````` > download TempleOS.ISO Searching... please wait Files found in the network: +----+--------------+-------------+---------+ | ID | Name | Fingerprint | From | +----+--------------+-------------+---------+ | 0 | TempleOS.ISO | 5d0fc944e5 | 3 peers | +----+--------------+-------------+---------+ Starting download... `````` - Wyświetlenie informacji o pliku w lokalnym repozytorium - `info <file_name>` ``` > info windows +---------+-------------+--------+------------+------------------------------+ | Name | Fingerprint | Status | Size | Path | +---------+-------------+--------+------------+------------------------------+ | windows | 6911e83944 | READY | 5824122880 | /home/starwader/isos/windows | +---------+-------------+--------+------------+------------------------------+ ``` - Wyświetlenie listy znanych partnerów - `peers` ``` > peers +----+------------+----------------------------+ | ID | IP address | Last updated | +----+------------+----------------------------+ | 0 | 10.1.1.10 | 2022-01-16 19:49:58.089793 | +----+------------+----------------------------+ | 1 | 10.1.1.53 | 2022-01-16 19:49:55.022034 | +----+------------+----------------------------+ ``` - Usunięcie pliku z repozytorium (nie z systemu plików) - `remove <file_name>` ``` > remove windows Deleting file 'windows' with status 'ready' ``` - Wyszukanie pliku w sieci - `search <file_name>` ``` > search TempleOS.ISO Searching... please wait Files found in the network: +----+--------------+-------------+---------+ | ID | Name | Fingerprint | From | +----+--------------+-------------+---------+ | 0 | TempleOS.ISO | 5d0fc944e5 | 1 peers | +----+--------------+-------------+---------+ ``` - Sprawdzenie statusu programu - `status`: ``` > status +----+--------------+-------------+------------+-------------+----------+-----------+ | ID | File name | Fingerprint | Size | Status | Progress | Peer(s) | +----+--------------+-------------+------------+-------------+----------+-----------+ | 0 | kali.iso | 4a9bf66238 | 3789008896 | READY | --- | --- | | 1 | windows.iso | 6911e83944 | 5824122880 | UPLOADING | --- | 1 clients | | 2 | poc | dad1c4654c | 225 | READY | --- | --- | | 3 | TempleOS.iso | 5d0fc944e5 | 17350656 | DOWNLOADING | 78.41% | 10.1.1.10 | +----+--------------+-------------+------------+-------------+----------+-----------+ ``` - Wyłączenie programu - `exit` # 3. Opis i analiza stosowanych protokołów ## 3.1 Opis stosowanych protokołów ### Transfer plików (TCP) Nasz protokół aplikacyjny TCP do transferu plików ma następujące cechy: - jest protokołem tekstowym - jest sesyjny, lecz bezstanowy (wszystkie informacje zawarte są w treści pojedynczego zapytania) - zapytanie klienta składa się z linii zapytania oraz dowolnej liczby nagłówków w postaci `klucz: wartość` - odpowiedź serwera składa się z linii statusu, dowolnej liczby nagłówków w postaci `klucz: wartość` oraz opcjonalnie zawiera dodatkową zawartość - w ramach pojedynczego połączenia (sesji) obsługiwane jest jedno zapytanie #### Założenia techniczne Konstrukcja protokołu wzorowana jest na uproszczonych elementach protokołu HTTP. Jako ogranicznik linii przyjmujemy kombinację znaków `\r\n` (`CR LF`), lecz dopuszczamy tolerancję również samego znaku `\n` (`LF`). Łańcuchy znaków kodowane są w UTF-8. Dodatkowe parametry zapytania i odpowiedzi podawane są jako nagłówki w formacie klucz-wartość z separatorem `: `, przy czym wielkość liter klucza nie ma znaczenia. Jeżeli dany nagłówek wystąpi kilka razy, uwzględniana jest jego ostatnia wartość. Po opcjonalnej liście nagłówków wymagana jest jedna pusta linia. #### Składnia zapytania ``` METHOD PATH header1: value header2: value ``` Każde zapytanie zawiera ścieżkę zasobu `PATH` - w naszym przypadku będzie to zawsze nazwa pliku. `METHOD` określa charakter zapytania: * `GET` - zwróć dane zasobu `PATH` wraz z jego zawartością * `HEAD` - zwróć dane zasobu `PATH` bez zawartości Nagłówki klienta o specjalnych znaczeniach: * `If-digest: <alg>=<resource_hash>`: informuje serwer o wymaganym skrócie odpytywanego zasobu; jeżeli wersja zasobu na serwerze różni się, serwer powinien zwrócić błąd * `Range: bytes <from>-<to>`: oczekuje od serwera przesłania tylko wskazanego fragmentu danego zasobu #### Składnia odpowiedzi: ``` STATUS_CODE STATUS_MSG header1: value header2: value RESPONSE_BODY ``` Każda odpowiedź zawiera linię statusu złożoną z trzycyfrowego kodu oraz krótkiej czytelnej wiadomości opisującej powodzenie zapytania. Pierwsza cyfra kodu statusu pozwala skategoryzować status według konwencji: *(pomijamy statusy `1xx`)* * `2xx`: OK, zapytanie obsłużone * `3xx`: póki co OK, ale klient powinien wykonać najpierw akcję wskazaną przez serwer * `4xx`: błąd po stronie zapytania (klienta) * `5xx`: błąd serwera Przykładowe statusy: * `200 OK` *(całe zapytanie poprawnie obsłużone)* * `206 Partial content` *(w przypadku wysłania fragmentu zasobu wskazanego przez klienta w nagłówku `Range`)* * `400 Bad request` *(błąd składniowy w zapytaniu)* * `404 Not found` *(zasób nie istnieje)* * `412 Precondition failed` *(w przypadku niespełnionego warunku `If-digest`)* * `416 Invalid range` *(w przypadku niepoprawnego lub niespełnialnego przedziału w nagłówku `Range`)* * `500 Server error` *(błąd serwera)* Nagłówki serwera o specjalnych znaczeniach: * `Content-type: <mime_type>`: typ zawartości odpowiedzi (`RESPONSE_BODY`); obsługujemy `text/plain` dla tekstu oraz `application/octet-stream` dla danych binarnych * `Content-length: <num_bytes>`: długość zawartości odpowiedzi (`RESPONSE_BODY`) * `Content-range: bytes <from>-<to>/<total length>`: długość fragmentu odpowiedzi w przypadku `206 Partial content` * `Digest: <alg>=<resource_hash>`: skrót odpytywanego zasobu wyznaczony wskazanym algorytmem; obsługujemy tylko `SHA256` `RESPONSE_BODY` jest opcjonalną zawartością odpowiedzi (o długości `Content-length`), w szczególności może zawierać dane binarne. ### Wykrywanie węzłów (UDP) Do wykrywania węzłów oraz do wyszukiwania plików u partnerów wykorzystujemy protokół binarny, w którym wyróżniamy wiadomości: * Do wykrywania węzłów: * `HELLO` - datagram rozgłaszany przez klienta chcącego zaktualizować lub zainicjalizować listę partnerów * `HERE` - datagram rozgłaszany przez każdego członka sieci co 10 sekund lub jako odpowiedź na komunikat `HELLO`, zawiera port lokalnego serwera repozytorium plików * Do wyszukiwania plików: * `FIND` - datagram rozgłaszany służący do wyszukiwania plików w sieci * `FOUND` - bezpośredni komunikat od partnera o znalezieniu danego pliku * `NOTFOUND` - bezpośredni komunikat od partnera informujący o nieznalezieniu danego pliku W celu rozróżnienia datagramów oraz rodzaju zawartej wiadomości pochodzących od węzłów naszego protokołu, wprowadzamy nagłówek zawierający dwa *magiczne* bajty (stałe dla protokołu), jego wersję oraz ID wiadomości. ### Zarys struktur, dla czytelności w języku C ```c #define byte unsigned char #define HEADER_LEN 4 #define PROTO_VERSION 1 #define MAGIC_NUMBER 0xD16D #define MAX_FILENAME_LENGTH 32 struct header { uint16_t magic_number; byte proto_version; byte message_id; }; struct msg_hello {/* empty */}; // HELLO(0x01) struct msg_here { // HERE(0x02) uint16_t unicast_port; uint16_t tcp_port; }; struct msg_file_data {// struct used in FIND(0x11), FOUND(0x12), NOTFOUND(0x13) char file_name[MAX_FILENAME_LENGTH]; char file_hash[64]; uint64_t file_size; }; ``` ## 3.2 Analiza stosowanych protokołów ### Protokół TCP (transfer pliku) Wykorzystanie TCP gwarantuje nam spójność danych otrzymanych od węzła źródłowego. Weryfikacja skrótu pliku po pobraniu pozwala nam stwierdzić, że serwer wysłał odpowiedni plik. Zastosowanie skrótu pomaga też, gdy połączenie TCP ulegnie przerwaniu - jesteśmy w stanie podjąć próbę wznowienia pobierania pliku z gwarancją, że pod identyfikatorem zasobu nie istnieje teraz zmieniony plik. ### Protokół UDP (wykrywanie węzłów/plików) #### Działanie w kontekście gubienia datagramów rozgłaszanych - Węzły rozgłaszają co 10 sekund datagram `HERE`, a odrzucenie z listy partnerów następuje po 30 sekundach nieodebrania datagramu po drugiej stronie. Gdy pierwsze rozgłoszenie węzła nie zostanie odebrane, to pozostanie wystarczająco czasu na dojście kolejnego rozgłoszenia. W ten sposób szansa, że usuniemy żywego partnera znacznie maleje. - Na podobnej zasadzie działa oczekiwanie na odpowiedź partnerów na zapytanie `FIND` - w tym przypadku czekamy 2 sekundy na odpowiedź wszystkich partnerów z listy (`FOUND` lub `NOTFOUND`). W przypadku braku odpowiedzi, zapytanie zostaje powtórzone zwracając uwagę tylko na odpowiedzi od tych partnerów, od których wcześniej nie otrzymaliśmy żadnej odpowiedzi. Po 2 takich próbach usuwamy partnera z listy. #### Wyszukiwanie/aktualizowanie listy partnerów 1. Każdy węzeł rozgłasza co 10 sekund lub po otrzymaniu komunikatu HELLO komunikat UDP: `HERE` 2. Każdy wezeł nasłuchuje na komunikaty `HERE` i zbiera listę partnerów w sieci Po dodaniu nowego węzła do sieci, rozgłasza on komunikat `HELLO` i oczekuje na odpowiedzi `HERE` od hostów przez kilka sekund (inicjalizacja). # 4. Schemat komunikacji węzłów <img src="C:\Users\przem\Desktop\data\diagram.svg" alt="diagram" style="zoom: 67%;" /> # 5. Wykorzystane narzędzia Do implementacji programu SimpleP2P użyliśmy Pythona w wersji 3.9. Program działa docelowo tylko na systemie Linux. Wykorzystane biblioteki z biblioteki standardowej Pythona: - `logging` - logowanie zdarzeń w modułach - `struct` - serializacja struktur binarnych - `hashlib` - obliczanie skrótów plików *(SHA256)* - `socket` - obsługa gniazd - `random` - liczby pseudolosowe - `threading` - wielowątkowość - `cmd`, `argparse` - interaktywny interfejs użytkownika - `asyncio` - asynchroniczna obsługa strumieni (TCP oraz plików) w celu lepszego wykorzystania zasobów Zewnętrzne zależności: - `aiofile` - w pełni asynchroniczna obsługa plików - `netifaces` - pozyskiwanie danych o interfejsach sieciowych - `prettytable` - formatowanie tabel w interfejsie użytkownika - `PyYAML` - obsługa plików yaml Do ręcznego testowania protokołu TCP wykorzystano narzędzie `telnet`. # 6. Implementacja Program został podzielony na pięć logicznych moduł: * `common`: zawiera funkcjonalność wspólną dla wszystkich pozostałych modułów, tj.: funkcje pomocnicze, modele oraz konfigurację * `repository`: zajmuje się zarządzaniem stanami plików oraz ich przechowywaniem * `file_transfer`: implementuje protokół TCP wraz z podstawową obsługą transferu plików * `udp`: odpowiada za wszelką komunikację za pomocą datagramów; implementuje wyszukiwanie partnerów oraz plików * `core`: główny moduł zawierający implementację głównego kontrolera oraz interfejs użytkownika ## 6.1 UDP ### Struktury Plik `structs.py` zawiera klasy mapujące struktury binarne z biblioteki `struct` i metody ułatwiające ich obsługę. ### Datagramy Plik `datagrams.py` zawiera klasy przechowujące wysyłane datagramy. Każdy datagram uzywany w komunikacji UDP przechowuje w sobie nagłówek `struct header` oraz strukturę wiadomości w zależności od typu datagramu - `struct message`. Każdy datagram posiada metodę `to_bytes`, która zwraca ciąg bajtów nadający się do wysłania za pomocą gniazd. ### Gniazda Plik `udp_socket.py` zawiera klasy `UdpSocket` oraz `BroadcastSocker(UdpSocket)`, które implementują kontrolę serwera UDP, obsługę socketów (biblioteka `socket` oraz `asyncio`), oraz pozwalają na równoległe wysyłanie datagramów. Klasa `AsyncioDatagramProtocol(asyncio.DatagramProtocol)` implementuje właściwą funkcjonalność asynchronicznego odbierania datagramów na dane gniazdo oraz logikę odrzucania datagramów rozgłaszanych (wariant W21). Klasa `UdpSocket` posiada tablicę `_receive_callbacks`, w której znajdują się wszystkie metody wywoływane w celu obsługi odebranych datagramów. ### Kontroler Moduł UDP zawiera klasę `UdpController` który implementuje całą logikę komunikacji broadcast oraz unicast UDP. ### Filtrowanie datagramów rozgłaszanych pochodzących od siebie Aby nasz program działał prawidłowo, należy odfiltrować wszystkie datagramy rozgłaszane wychodzące z naszego IP. Listę naszych IP pozyskujemy za pomocą biblioteki `netifaces`. Filtrowanie datagramów rozgłaszanych jest zaimplementowane w klasie `AsyncioDatagramProtocol` - można nim sterować za pomocą stałej `BROADCAST_OMIT_SELF`. ### Gubienie datagramów rozgłaszanych (Wariant W21) Gubienie datagramów rozgłaszanych zaimplementowano w `AsyncioDatagramProtocol`. Wartości zmiennych konfiguracyjnych można przekazać jako opcjonalne argumenty skryptu. Rozpoczęcie gubienia datagramów rozgłaszanych następuje z prawdopodobieństwem `BROADCAST_DROP_CHANCE` procent. Po rozpoczęciu trybu gubienia datagramów rozgłaszanych, program odrzuca kolejne przychodzące do niego datagramy `BROADCAST_DROP_IN_ROW` razy. Przykłady działania zamieściliśmy w paragrafie **8.1** *Wariant W21 - Gubienie datagramów rozgłaszanych*. ## 6.2 Transfer plików - TCP ### Koncepcja Zakładając, że protokół będzie służył w pierwszej kolejności do przesyłania plików z gotowymi metadanymi z repozytorium plików, istnieje duży zysk z wykorzystania podejścia nieblokującego w celu obsługi zapytań - zarówno dla odczytu pliku, jak i samego wysyłania przez sieć. W tym celu wykorzystana została biblioteka `asyncio` oferująca wsparcie do programowania asynchronicznego w przystępnym podejściu `async/await`. ### Modele W plikach `enums.py` umieszczono enumeracje z definicjami wszystkich statusów oraz nagłówków wykorzystywanych w protokole. Zdefiniowano również struktury pomocniczne, np.: `HeadersContainer` do obsługi i walidacji nagłówków. Główne klasy to `Request` oraz `Response`, wspierające modułową serializację i deserializację, co widać np. w rozszerzonej klasie `FileResponse`, zawierającej całą logikę do wysyłania lokalnego zasobu jako zawartość `Response`. ### Parsowanie nagłówków Korzystając z licznych uproszczeń wynikających ze wsparcia protokołów tekstowych w `StreamReader`, zdefiniowanych enumeracji oraz funkcji walidujących, jedyną trudnością okazały się nagłówki o bardziej skomplikowanej formie, do czego wykorzystano proste wyrażenia regularne, np. `Range: (\S+) (\d*)-(\d*)`. Pojedyncze metody pomocniczne do przetwarzania tekstu znajdują się w `parse_utils.py`. ### Logika Zarówno dla serwera i klienta protokołu, `asyncio` opakowuje gniazdo dostarczając wysokopoziomowe klasy `StreamReader`/`StreamWriter` do odczytu i zapisu danych ze strumienia. W plikach odpowiednio `client.py` oraz `server.py` znajdują się klasy, których instancje obsługują cały cykl życia pojedynczego połączenia pomiędzy klientem i serwerem. W obu przypadkach logika ogranicza się do pojedynczej funkcji obsługi zdeserializowanej klasy `Request` bądź `Response`. Zastosowanie własnych klas wyjątków pozwala na łatwą propagację błędów i zwrócenie odpowiedniego kodu błędu lub zamknięcie połączenia. ### Komunikacja z resztą programu W celu obsłużenia zapytania o plik należy pobrać z głównego kontrolera aktualny stan pliku, udostępniając możliwość synchronizacji stanu jednocześnie zachowując kontrolę nad dostępem do pliku. W tym celu, w `context.py` zdefiniowano 'konteksty' `FileProviderContext`/`FileConsumerContext` pośredniczące w 'transakcji' dotyczącej pojedynczego pliku. Są to klasy implementujące wzorzec menedżera kontekstu *(powszechnie stosowane w konstrukcji `with`)*, zwalniane w momencie zaprzestania korzystania z powiązanego zasobu. Kontroler przetrzymuje dla każdego pliku listę konsumentów *(przy udostępnianiu pliku)* oraz dostawcę *(w przypadku pobierania pliku)*. ## 6.3 Repozytorium plików ### Domyślna struktura katalogów ``` /home/user/ +--Downloads | +-- simplep2p | +-- .meta | | +-- added_file1.txt.yaml | | +-- downloaded_file1.yaml | | +-- downloaded_file2.yaml | +-- downloaded_file1 | +-- downloaded_file2 ``` ### Pliki z metadanymi Informacje o każdym pliku dodanym do repozytorium gromadzone są w katalogu `.meta` ukazanej wyżej struktury. Zawierają one w sobie informacje takie jak: nazwa pliku, ścieżka bezwzględna pliku, rozmiar w bajtach w momencie dodawania, status, wyznaczony skrót w momencie dodawania, obecny rozmiar oraz obecny skrót. <small>Przykład pliku z metadanymi</small> ``` current_digest: 4bc89cbe78f14ccecef1571aa46048b770353c33a0000e304c2c2e4610a8b853 current_size: 950 digest: 4bc89cbe78f14ccecef1571aa46048b770353c33a0000e304c2c2e4610a8b853 name: added_file1.txt path: /home/username/Desktop/added_file.txt size: 950 status: ready ``` W przypadku, kiedy użytkownik zmodyfikował plik poza naszą aplikacją, zostanie wyliczony dla niego nowy skrót oraz rozmiar. Następnie zastąpi on poprzednie wartości w polach z nazwą "current_". Możliwe statusy pliku: - `ready` - plik jest w pełni gotowy, obecność pliku jest obecnie rozgłaszana innym klientom w sieci - `downloading` - plik jest obecnie pobierany - `invalid` - plik został zmodyfikowany przez użytkownika poza aplikacją W przypadku przerwania pobierania (np. nagłe zatrzymanie działania aplikacji), pobieranie wznowi się po ponownym uruchomieniu na podstawie bieżącego rozmiaru pliku. Jeśli użytkownik chce kontynuować rozgłaszanie pliku ze statusem `invalid`, musi on zostać dodany ponownie. ## 6.4 Główny kontroler ### Zarządzanie komponentami Kontroler zarządza cyklem życia modułów: repozytorium, UDP oraz serwera plików. Tworzona jest pętla obsługi zdarzeń `asyncio` dla serwera TCP oraz periodycznego obserwowania stanu plików. Wszystkie komponenty komunikują się między sobą za pośrednictwem kontrolera; synchronizacja odbywa się za pomocą semafor z pakietu `threading`. ### Obserwacja stanów plików Co 5 sekund badane są stany plików *(w celu wykrycia nieprawidłowych plików)* oraz podejmowane są próby wznowienia pobierania. ### Przechowywanie stanów Nie wszystkie dane o plikach muszą być na bieżąco utrwalane w repozytorium (np. skrót oraz rozmiar pliku), stąd w kontrolerze przechowywana jest kopia słownika plików synchronizowana ze stanem repozytorium. ## 6.5 Konfiguracja Podstawowa konfiguracja, na którą składają się stałe takie jak rozmiary buforów czy maksymalne rozmiary plików, zawiera się w pliku `common/config.py`. W pliku `log.yml` znajduje się konfiguracja loggerów, dzięki czemu istnieje możliwość ustawienia odpowiedniego poziomu logowania. ```python # fragment pliku common/config.py #... BROADCAST_OMIT_SELF = True PROTO_VERSION = 1 ENCODING = "utf-8" MAGIC_NUMBER = 0xD16D UDP_BUFFER_SIZE = 2048 UDP_PEER_CLEANUP_PERIOD = 30 UDP_ADVERTISE_PERIOD = 10 TCP_FILE_SEND_TIMEOUT = 15 TCP_FILE_RECEIVE_TIMEOUT = 10 FILE_WATCHER_PERIOD = 5 MAX_FILENAME_LENGTH = 32 DIGEST_ALG = "sha256" FINDING_TIME = 2 SEARCH_RETRIES = 2 #... ``` ```yaml # fragment pliku log.yml formatters: request: format: '%(asctime)s | %(name)s | %(id).8s | %(levelname)s | %(method)s %(uri)s: %(message)s' simple: format: '%(asctime)s | %(name)s | %(levelname)s | %(message)s' handlers: request-console: class: logging.StreamHandler formatter: request level: DEBUG stream: ext://sys.stderr loggers: ClientHandler: handlers: - request-console level: DEBUG ``` Wszystkie najważniejsze opcje można przekazać jako argument wywołania programu *(są wczytywane do singletona `Config`)*: ``` Simple P2P client optional arguments: -h, --help show this help message and exit --bind-ip BIND_IP IP to bind for file transfer and discovery (default: 0.0.0.0) --tcp-port TCP_PORT TCP port to use for file transfer (default: 13372) --udp-port UDP_PORT UDP port to use for file discovery (default: 13371) --broadcast-iface BROADCAST_IFACE Interface to broadcast on for peer/file discovery, eg. eth0 (default: default) --broadcast-port BROADCAST_PORT UDP port to broadcast on for peer/file discovery (default: 13370) --broadcast-drop-chance BROADCAST_DROP_CHANCE Percentage chance to drop incoming broadcast packet (default: 0) --broadcast-drop-in-row BROADCAST_DROP_IN_ROW Number of packets to be dropped at once (default: 1) ``` ## 6.6 Interfejs użytkownika Interfejs użytkownika to interaktywny wiersz poleceń wywołujący metody kontrolera, zgodnie z funkcjonalnością zawartą w paragrafie nr **3.** ## 6.7 Logowanie Wykorzystano wielopoziomowe logowanie z podziałem na moduły; ważniejsze komunikaty wyświetlane są w konsoli, ponadto bardziej szczegółowe dane zapisywane są do pliku tekstowego. Logowane są wszystkie nieobsłużone oraz niespodziewane wyjątki, sytuacje nietypowe i informacje o komunikacji przepływającej przez protokoły UDP/TCP oraz kontroler. Dla każdego połączenia TCP generowany i logowany jest jego unikalny identyfikator. # 7. Testy oraz obsługa sytuacji wyjątkowych Nieistotne fragmenty wyjścia z konsoli pominięte w celu zwiększenia czytelności. ## 7.1 Wariant W21 - Gubienie datagramów rozgłaszanych Przykład został przygotowany poprzez jednoczesne uruchomienie aplikacji na dwóch komputerach w ramach tej samej sieci fizycznej. Została włączona programowa symulacja 'gubienia' pakietów za pomocą argumentów `--broadcast-drop-chance 100 --broadcast-drop-in-row 3`. ### Przykłady (nie)działania: Widok konsoli hosta `10.1.1.10`: ``` 23:03:31|UdpController|DEBUG|Hello|Responding with HERE to new peer 10.1.1.7 23:03:31|UdpController|DEBUG|Here|Received HERE message from peer 10.1.1.7:12345 23:03:31|UdpController|DEBUG|Here|Discovered peer 10.1.1.7:12345 23:03:39|UdpController|DEBUG|Broadcasting HERE message 23:03:41|UdpController|DEBUG|Here|Received HERE message from peer 10.1.1.7:12345 > search asd Searching... please wait 23:03:49|UdpController|DEBUG|Broadcasting HERE message 23:03:51|UdpController|DEBUG|Here|Received HERE message from peer 10.1.1.7:12345 23:03:53|UdpController|INFO|Search|1 peers did not respond, retrying search for file asd (1/2) 23:03:55|UdpController|INFO|Search|1 peers did not respond, retrying search for file asd (2/2) 23:03:57|UdpController|INFO|Search|Deleting unresponsive peer 10.1.1.7 23:03:57|UdpController|INFO|Search|Found asd in 0 out of 1 peers No files were found in the network ``` Widok konsoli hosta `10.1.1.7`: ``` Starting... this might take a bit 23:03:31|Controller|INFO|Starting... 23:03:31|UdpController|DEBUG|Broadcasting HERE message 23:03:31|AsyncioDatagramProtocol|DEBUG|Dropping incoming broadcast datagram (1/3) 23:03:31|Controller|INFO|Ready Welcome to SimpleP2P! Type ? to list commands > 23:03:39|AsyncioDatagramProtocol|DEBUG|Dropping incoming broadcast datagram (2/3) 23:03:41|UdpController|DEBUG|Broadcasting HERE message 23:03:49|AsyncioDatagramProtocol|DEBUG|Dropping incoming broadcast datagram (3/3) 23:03:51|UdpController|DEBUG|Find|Received datagram from unknown host 10.1.1.10, skipping 23:03:51|UdpController|DEBUG|Broadcasting HERE message 23:03:53|AsyncioDatagramProtocol|DEBUG|Dropping incoming broadcast datagram (1/3) 23:03:55|AsyncioDatagramProtocol|DEBUG|Dropping incoming broadcast datagram (2/3) 23:03:59|AsyncioDatagramProtocol|DEBUG|Dropping incoming broadcast datagram (3/3) 23:04:01|UdpController|DEBUG|Broadcasting HERE message ``` ### Opis - 23:03:31 - `10.1.1.7` włączył program SimpleP2P i wysłał datagram `HELLO`, na który host `10.1.1.10` odpowiedział datagramem `HERE` - `10.1.1.7` wszedł od razu w tryb gubienia pakietów, dlatego nie odebrał od niego odpowiedzi HERE - Tymczasem, host `10.1.1.10` poprawnie odebrał datagram `HERE` od `10.1.1.7` i dodał go do swojej listy partnerów - 23:03:39 - `10.1.1.7` zgubił datagram `HERE` od `10.1.1.10`, dlatego wciąż nie wie o istnieniu `10.1.1.10` - 23:03:41 - `10.1.1.10` poprawnie odebrał datagram `HERE` od `10.1.1.7` - 23:03:49 - `10.1.1.7` ponownie zgubił datagram `HERE` od `10.1.1.10` - 23:03:51 - `10.1.1.7` otrzymał datagram `FIND` od `10.1.1.10`, ale nie miał go na liście partnerów, dlatego mu nie odpowiedział (nie ma informacji np. na jaki port unicast ma odpowiadać) - 23:03:53 - 2 sekundy później, `10.1.1.10` ponowił szukanie pliku "asd", ale `10.1.1.7` znowu zaczął odrzucać pakiety - 23:03:57 - Po trzech nieudanych próbach, `10.1.1.10` usunął `10.1.1.7` z listy partnerów przez brak odpowiedzi ## 7.2 Istnienie wielu wersji tego samego pliku Nasz program w pełni wspiera sytuację, kiedy istnieje kilka wersji pliku identyfikowanego przez tą samą nazwę. Nazwy plików nadal są unikalne w skali lokalnego repozytorium, lecz użytkownik może wybrać w takiej sytuacji wersję na podstawie skrótu. Nawet w przypadku kontynuowania pobierania, wyszukiwany będzie plik ze zgodnym skrótem. ##### Przykład działania z plikiem o dwóch wersjach ``` Welcome to SimpleP2P. Type ? to list commands > download hello.txt Searching... please wait Files found in the network: +----+-----------+-------------+------+---------+ | ID | Name | Fingerprint | Size | From | +----+-----------+-------------+------+---------+ | 0 | hello.txt | 50660cced7 | 26 | 1 peers | | 1 | hello.txt | 8b9040011c | 4 | 1 peers | +----+-----------+-------------+------+---------+ Found multiple versions. Please choose one. Select provider index: 0 Starting download... 2022-01-18 22:04:24,379 | Controller | INFO | Download of hello.txt completed > status +----+-----------+-------------+------+--------+----------+---------+ | ID | File name | Fingerprint | Size | Status | Progress | Peer(s) | +----+-----------+-------------+------+--------+----------+---------+ | 0 | hello.txt | 50660cced7 | 26 | READY | --- | --- | +----+-----------+-------------+------+--------+----------+---------+ ``` ## 7.3 Błąd sieci podczas pobierania Poniższy test został przeprowadzony pomiędzy hostem `192.168.80.1` oraz systemem zwirtualizowanym `192.168.80.132`. Podczas pobierania pliku od hosta do gościa, interfejs sieciowy odbiorcy został nagle wyłączony i ponownie włączony po ok. 30 sekundach. #### Stan początkowy ##### Nadawca pliku ``` 13:59:05,916 | ServerHandler | c701e2cd | DEBUG | : New connection from 192.168.80.132:46538 13:59:05,917 | ServerHandler | c701e2cd | INFO | GET video.mkv: Response code=200 len=3987561655 ``` ##### Odbiorca pliku ``` 13:59:05,591 | Controller | INFO | Starting download of file video.mkv from 192.168.80.1 13:59:05,592 | ClientHandler | f7802949 | DEBUG | GET video.mkv: New connection to 192.168.80.1:13372 > status +----+-----------+-------------+------------+-------------+----------+--------------+ | ID | File name | Fingerprint | Size | Status | Progress | Peer(s) | +----+-----------+-------------+------------+-------------+----------+--------------+ | 0 | video.mkv | a48dcfffaf | 3987561655 | DOWNLOADING | 1.30% | 192.168.80.1 | +----+-----------+-------------+------------+-------------+----------+--------------+ ``` #### Stan po odłączeniu i ponownym podłączeniu intefejsu sieciowego ##### Nadawca pliku ``` 13:59:45,411 | ServerHandler | c701e2cd | ERROR | GET video.mkv: Connection error [...] 14:00:02,736 | UdpController | DEBUG | Find | Sending positive reply for file video.mkv with digest a48dcfff 14:00:04,736 | ServerHandler | 796a29e4 | DEBUG | : New connection from 192.168.80.132:46540 14:00:04,737 | ServerHandler | 796a29e4 | INFO | GET video.mkv: Response code=206 len=3886426695 ``` ##### Odbiorca pliku ``` 13:59:37,400 | UdpController | ERROR | AliveAgent | Error while broadcasting HERE 13:59:40,124 | Controller | WARNING | Download of video.mkv failed 13:59:52,414 | UdpController | ERROR | Search | Error while broadcasting OSError: [Errno 101] Network is unreachable 13:59:57,418 | Controller | INFO | Retrying file video.mkv 13:59:57,418 | UdpController | DEBUG | AliveAgent | Broadcasting HERE message 13:59:59,421 | Controller | WARNING | Cannot find hosts to resume file video.mkv 14:00:01,105 | UdpController | DEBUG | Here | Discovered peer 192.168.80.1:13370 14:00:02,422 | Controller | INFO | Retrying file video.mkv 14:00:04,425 | UdpController | DEBUG | Found | Found file video.mkv 14:00:04,227 | Controller | INFO | Starting download of file video.mkv from 192.168.80.1 14:00:04,425 | ClientHandler | 89e91323 | DEBUG | GET video.mkv: New connection to 192.168.80.1:13372 > status +----+-----------+-------------+------------+-------------+----------+--------------+ | ID | File name | Fingerprint | Size | Status | Progress | Peer(s) | +----+-----------+-------------+------------+-------------+----------+--------------+ | 0 | video.mkv | a48dcfffaf | 3987561655 | DOWNLOADING | 3.57% | 192.168.80.1 | +----+-----------+-------------+------------+-------------+----------+--------------+ ``` * Interfejs został wyłączony w momencie ok. `13:59:30`. Od tego momentu wszystkie próby rozgłaszania datagramów zwróciły błąd `Network unreachable`. * Transfer pliku zwrócił błąd związany z przekroczeniem czasu oczekiwania, który został określony w konfiguracji jako 10 sekund w przypadku klienta oraz 15 sekund w przypadku serwera. * Odbiorca pliku próbował ponowić pobieranie pliku w momencie `13:59:57`, lecz nie znalazł partnera. * Intefejs sieciowy odbiorcy został ponownie podłączony w momencie ok.`14:00:00`; wkrótce wykryto ponownie partnera `192.168.80.1`. * Odbiorca pliku podjął kolejną, tym razem udaną próbę wznowienia pobierania pliku. * Transfer pliku został wznowiony od momentu przerwania, o czym świadczy kod odpowiedzi `206 Partial content` z krótszą zawartością. ## 7.4 Nadawca przesyłający niepoprawną wersję pliku Nasz uproszczony schemat działania repozytorium weryfikuje skrót pliku jednorazowo przy uruchomieniu programu oraz po zakończeniu pobierania; istnieje więc możliwość, że serwer nada zmodyfikowaną wersję pliku w przypadku np. 'podmiany' pliku przez użytkownika. Optymalnym rozwiązaniem byłoby blokowanie dostępu do pliku oraz badanie dat modyfikacji, lecz uznaliśmy, że implementacja wykracza poza zakres projektu. Poniższy test został przeprowadzony pomiędzy hostem `192.168.80.1` oraz systemem zwirtualizowanym `192.168.80.132`. Po wczytaniu listy plików, zmodyfikowano plik `video.mkv` dopisując kilka dodatkowych bajtów. #### Odbiorca pliku ``` 15:03:30,773 | Controller | INFO | Starting download of file video.mkv from 192.168.80.1 15:03:30,773 | ClientHandler | 28440edb | DEBUG | GET video.mkv: New connection to 192.168.80.1:13372 [...] 15:04:20,728 | Controller | INFO | Download of video.mkv completed 15:04:20,729 | Controller | WARNING | Download of video.mkv failed simple_p2p.common.exceptions.LogicError: Invalid file download 15:04:24,933 | Controller | WARNING | Truncating download video.mkv 15:04:24,934 | Controller | INFO | Retrying file video.mkv 15:04:24,937 | UdpController | WARNING | Found | Received FoundDatagram from unknown peer 192.168.80.1 15:04:24,967 | UdpController | DEBUG | AliveAgent | Broadcasting HERE message 15:04:25,482 | UdpController | DEBUG | Here | Received HERE message from peer 192.168.80.1:13370 15:04:25,482 | UdpController | DEBUG | Here | Discovered peer 192.168.80.1:13370 15:04:26,937 | UdpController | INFO | Search | Found video.mkv in 0 out of 0 peers 15:04:26,937 | Controller | WARNING | Cannot find hosts to resume file video.mkv ``` * Plik został pomyślnie pobrany, po czym nastąpiło porównanie skrótu pliku pobranego z tym spodziewanym i wykryto niepoprawność * Nadawca pliku został tymczasowo usunięty z listy partnerów * Nieoczekiwany plik został usunięty - pobieranie należy kontynuować od zera * Nastąpiła próba ponowienia pobierania, lecz nie udało się znaleźć partnera Niestety, w naszym podejściu serwer nie potrafi wykryć faktu przesłania nieodpowiedniego pliku, więc nadal będzie udostępniał nieprawidłowy plik. Wyjątkiem jest próba przesłania pliku, który nie istnieje bądź ma rozmiar mniejszy niż spodziewany - w takim wypadku kontroler wykrywa błąd i ustawia stan pliku na `invalid`. W celu poprawnego obsłużenia tego scenariusza, należałoby zastosować jedno z rozwiązań zaproponowanych na początku opisu testu. # 8. Instrukcja instalacji Do poprawnego działania programu wymagany jest interpreter Python w wersji co najmniej `3.9` ```bash $ python3.9 -m venv venv && source venv/bin/activate $ pip3 install -r requirements.txt && pip3 install . $ simple-p2p ``` ### Dziennik zmian w raporcie na tle pierwszego etapu: > 1. Interpretacja zadania > Identyfikator zasobu jest teraz nazwą pliku, a nie 64-bajtowym skrótem > 2. Opis funkcjonalny > Po pobraniu, plik nie jest usuwany, można wznowić pobieranie > Interfejs konsolowy nie pozwala na przerwanie uploadowania bez usunięcia pliku z repozytorium (`stop-upload`) > Aktualizacja wyglądu i innych funkcjonalności interfejsu tekstowego > 3.1 Opis stosowanych protokołów > UDP - Zmiana kolejności nazwy/skrótu pliku w `struct msg_file_data` - Dodanie pola file_size w `struct msg_file_data` - Zmiana nazw niektórych pól - Dodanie pola `unicast_port` w `struct msg_here` > TCP - Dodanie statusu `416 Invalid Range` - Dodanie nagłówka odpowiedzi `Range-content` - Połączenie jest zamykane po wysłaniu odpowiedzi >3.2 Analiza stosowanych protokołów > Usunięto `PORT 1234` z datagramu `HERE` > 4. Podział na moduły i komunikacja > Zmieniono tytuł na 'Schemat komunikacji węzłów' > 5. Zarys koncepcji implementacji > Zmieniono tytuł na 'Wykorzystane narzędzia' > Dodanie zewnętrznych bibliotek > Uwzględnienie `asyncio` oraz logowania > 6. Implementacja > Dodanie paragrafu wraz z opisami poszczególnych modułów > 7. Testy > Dodanie paragrafu wraz z opisem realizacji wariantu W21

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully