LINUX　アセンブリ　FTPサーバから時間取得

## 動作確認
1970年からNTPから時間を取得した時点までの秒数を表示します。
その後date命令を用いて何年何月何日何時何分何秒の形式で表示します。
私は中国に住んでいるのですが世界標準時間で出力された以下の2025-05-24 11:59:31に8時間を足すと丁度一致しました。日本だと9時間足す必要があります。

```
ubuntu@ubuntu:~/kaihatsu/ntp$ nasm -f elf64 ntp.asm -o ntp.o
ubuntu@ubuntu:~/kaihatsu/ntp$ ld ntp.o -o ntp
ubuntu@ubuntu:~/kaihatsu/ntp$ ./ntp
1748087971ubuntu@ubuntu:~/kaihatsu/ntp$ date -ud @1748087971 +"%Y-%m-%d %H:%M:%S UTC"
2025-05-24 11:59:31 UTC
ubuntu@ubuntu:~/kaihatsu/ntp$ 
```

## 予備知識
### NTPタイムスタンプ

NTP（Network Time Protocol）のタイムスタンプは、​​64ビット​​の固定小数点数形式で表現されます。

```
0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           Seconds                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                  Fraction（小数部 1/2^32秒単位）              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```
    ​​上位32ビット（Seconds）​​:
    1900年1月1日00:00:00 UTCからの経過秒数（​​秒単位​​）を表します。

    ​​下位32ビット（Fraction）​​:※今回未使用
    秒未満の時間を1/232秒単位で表します。
    （例: 0x80000000 = 0.5秒）




### 宛先情報の構造体の構造
```
; sockaddr_in構造体（IPv4用）
struc sockaddr_in
    .sin_family: resw 1    ; AF_INET = 2 IPv4用は2固定
    .sin_port:   resw 1    ; ポート番号（リトルエンディアン）
    .sin_addr:  resd 1     ; IPv4アドレス（4バイト）
    .sin_zero:  resb 8     ; 常に0埋め
endstruc
```

### システムコールの引数及び返り値(C言語風)
#### 1. ​​SYS_SOCKET（ソケット作成）​​
```
#include <sys/socket.h>

/**
 * ソケットを作成する（64ビットLinux用）
 * @param domain   通信ドメイン（32ビット整数: AF_INET=2）
 * @param type     ソケットタイプ（32ビット整数: SOCK_DGRAM=2）
 * @param protocol プロトコル（32ビット整数: 0=自動選択）
 * @return         成功: 64ビットファイルディスクリプタ（正の整数）
 *                 失敗: 64ビットエラーコード（-1）
 */
int64_t socket(int32_t domain, int32_t type, int32_t protocol);
```

#### 2. ​​SYS_SENDTO（データ送信）
```
#include <sys/socket.h>

/**
 * UDPでデータを送信する（64ビットLinux用）
 * @param sockfd    64ビットファイルディスクリプタ
 * @param buf       送信データの64ビットポインタ
 * @param len       64ビット符号なし整数（データサイズ）
 * @param flags     32ビット整数（フラグ: 通常0）
 * @param dest_addr 宛先アドレスの64ビットポインタ（sockaddr_in*）
 * @param addrlen   32ビット整数（アドレス構造体のサイズ: 16）
 * @return          成功: 送信したバイト数（64ビット符号付き整数）
 *                  失敗: 64ビットエラーコード（-1）
 */
int64_t sendto(
    int64_t sockfd,
    const void *buf,      // 64ビットアドレス
    uint64_t len,         // 64ビット
    int32_t flags,        // 32ビット（下位32ビット使用）
    const struct sockaddr *dest_addr,  // 64ビットアドレス
    uint32_t addrlen      // 32ビット（下位32ビット使用）
);
```

#### 3. ​​SYS_RECVFROM（データ受信）​​
```
#include <sys/socket.h>

/**
 * UDPでデータを受信する（64ビットLinux用）
 * @param sockfd    64ビットファイルディスクリプタ
 * @param buf       受信バッファの64ビットポインタ
 * @param len       64ビット符号なし整数（バッファサイズ）
 * @param flags     32ビット整数（フラグ: 通常0）
 * @param src_addr  送信元アドレスの64ビットポインタ（NULL可）
 * @param addrlen   送信元アドレスサイズの64ビットポインタ（NULL可）
 * @return          成功: 受信したバイト数（64ビット符号付き整数）
 *                  失敗: 64ビットエラーコード（-1）
 */
int64_t recvfrom(
    int64_t sockfd,
    void *buf,           // 64ビットアドレス
    uint64_t len,        // 64ビット
    int32_t flags,       // 32ビット（下位32ビット使用）
    struct sockaddr *src_addr,  // 64ビットアドレス（NULL可能）
    uint32_t *addrlen    // 64ビットアドレス（NULL可能）
);
```

## 検証コード

```
section .data
; NTPサーバー情報設定（time.nist.gov）
ntp_server: db 129,6,15,28   ; 接続先サーバー(NTP)のIPアドレス
port:       dw 0x7B00        ; UDPポート123（リトルエンディアン​​ 0x7B00）

; NTPリクエストパケットの初期化
ntp_packet: db 0x1B           ; [重要] フラグ設定：LI=0(正常), VN=3(バージョン3), Mode=3(クライアントモード)
times 47 db 0                 ; 48バイトのNTPパケット形式を満たすため0埋め

section .text
global _start

; Linuxシステムコール番号
%define SYS_SOCKET   41
%define SYS_SENDTO   44
%define SYS_RECVFROM 45
%define SYS_CLOSE    3
%define SYS_EXIT     60
%define SYS_WRITE    1

_start:
    ; [ソケット作成] NTPサーバーと通信するためのUDPソケットを作成
    mov rax, SYS_SOCKET
    mov rdi, 2              ; AF_INET（IPv4通信を指定）
    mov rsi, 2              ; SOCK_DGRAM（UDP通信を指定）
    mov rdx, 0              ; プロトコル自動選択（通常UDPは0）
    syscall
    cmp rax, 0              ; エラーチェック：負の値は失敗
    jl error
    mov [fd], rax           ; 作成したソケットを保存

    ; [宛先設定] 接続先サーバーのアドレス情報を構造体に設定
    mov word [sockaddr], 2  ; sin_family = AF_INET（IPv4）
    mov ax, [port]          ; ポート番号を設定
    mov word [sockaddr+2], ax ; sin_port フィールド
    mov eax, [ntp_server]   ; IPアドレスを設定（4バイト）
    mov dword [sockaddr+4], eax ; sin_addr フィールド

    ; [リクエスト送信] NTPサーバーに時刻取得要求を送信
    mov rax, SYS_SENDTO
    mov rdi, [fd]           ; 使用するソケットを指定
    lea rsi, [ntp_packet]   ; 送信データ（事前に準備したNTPリクエスト）
    mov rdx, 48             ; NTPパケットの標準サイズ（48バイト）
    mov r10, 0              ; フラグなし（通常は0）
    lea r8, [sockaddr]      ; 宛先アドレス構造体のポインタ
    mov r9, 16              ; アドレス構造体のサイズ（sockaddr_inは16バイト）
    syscall
    cmp rax, 48             ; 正常に48バイト送信されたか確認
    jne error

    ; [レスポンス受信] サーバーからの時刻情報を受信
    mov rax, SYS_RECVFROM
    mov rdi, [fd]           ; 同じソケットを使用
    lea rsi, [recv_buf]     ; 受信データ格納先バッファ
    mov rdx, 48             ; 受信予定データサイズ（NTPレスポンスも48バイト）
    mov r10, 0              ; フラグなし
    mov r8, 0               ; 送信元アドレス情報不要（NULL指定）
    mov r9, 0               ; アドレスサイズ情報不要（NULL指定）
    syscall
    cmp rax, 48             ; 48バイト受信したか確認
    jne error

    ; [時刻データ抽出] 受信データをeaxへ格納
    mov eax, [recv_buf + 32]    ; タイムスタンプ上位32ビット（NTPフォーマット）
    mov ebx, [recv_buf + 36]    ; タイムスタンプ下位32ビット（今回は未使用）
    bswap eax                   ; ビッグエンディアン→リトルエンディアン変換
    sub eax, 2208988800         ; NTP時間（1900年起点）→UNIX時間（1970年起点）に変換
                                ; 差は70年分（2208988800秒）

    ; （注）ここでeaxにUNIX時間が格納される
    

; [時刻データ表示] 数値をASCII文字列に変換して出力
    mov edi, output_buf          ; 出力バッファのアドレス
    call int_to_ascii            ; 数値→ASCII変換関数

    mov rax, SYS_WRITE         ; writeシステムコール番号設定
    mov rdi, 1                 ; 標準出力のファイルディスクリプタ
    mov rsi, output_buf        ; 出力データのポインタ
    mov rdx, 10                 ; 出力サイズ（10バイト）
    syscall

    ; [ソケット解放] 通信が完了したのでソケットを閉じる
    mov rax, SYS_CLOSE
    mov rdi, [fd]
    syscall

    ; プログラム正常終了
    mov rdi, 0
    jmp exit

error:
    ; エラー発生時の処理
    mov rdi, 1              ; 終了1（エラー）

exit:
    ; システム終了処理
    mov rax, SYS_EXIT
    syscall
    
; 数値→ASCII変換関数（32bit符号なし整数用）
int_to_ascii:
    mov ecx, 10              ; 除数
    mov ebx, 10              ; カウンタ（10桁）
.convert_loop:
    dec ebx
    xor edx, edx
    div ecx                  ; EDX:EAX / ECX
    add dl, '0'              ; 余りをASCIIに変換
    mov [edi + ebx], dl
    test ebx, ebx
    jnz .convert_loop
    ret

section .bss
fd:         resq 1          ; ソケットファイルディスクリプタ保存用
sockaddr:   resb 16         ; ソケットアドレス情報格納用（AF_INETの場合16バイト）
recv_buf:   resb 48         ; NTPレスポンス受信用バッファ
output_buf: resb 10          ; 追加: 出力用バッファ
```