# 機能設計書 157-TCP\connect_async

## 概要

本ドキュメントは、Hack Standard Library (HSL) における `TCP\connect_async` 関数の機能設計を記載する。この関数は TCP ソケットへの非同期接続機能を提供する。

### 本機能の処理概要

`TCP\connect_async` は、指定されたホストとポートに対して TCP ソケット接続を非同期で確立する関数である。IPv4 と IPv6 の両方をサポートし、接続オプションによりプロトコルバージョンの優先順位やタイムアウトを設定できる。接続成功後は `CloseableSocket` インターフェースを実装したソケットハンドルを返す。

**業務上の目的・背景**：ネットワーク通信はモダンなアプリケーションにおいて不可欠な機能である。TCP は信頼性のあるストリーム型プロトコルであり、HTTP、データベース接続、マイクロサービス間通信など、多くのアプリケーションプロトコルの基盤となる。非同期接続により、接続待機中に他の処理を実行でき、アプリケーションのレスポンス性を向上させることができる。

**機能の利用シーン**：
- HTTP クライアントの実装
- データベースサーバーへの接続
- マイクロサービス間通信
- 外部 API への TCP レベルでの接続
- カスタムプロトコルの実装

**主要な処理内容**：
1. IP プロトコルバージョンの決定（IPv6 優先、IPv6 強制、IPv4 強制）
2. ホスト名の解決（DNS ルックアップ）
3. ソケットの作成とアドレス構造体の構築
4. 非同期接続の実行
5. 接続成功時にソケットハンドルを返却

**関連システム・外部連携**：オペレーティングシステムの TCP/IP スタックを使用。DNS 解決には `gethostbyname` (IPv4) または `dns_get_record` (IPv6) を使用する。

**権限による制御**：ネットワークアクセスはオペレーティングシステムのセキュリティポリシーに従う。特定のポートへのアクセスにはファイアウォールルールの影響を受ける場合がある。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 該当なし | - | 本プロジェクトはUIを持たないライブラリのため画面なし |

## 機能種別

データ連携（ネットワーク接続）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| $host | string | Yes | 接続先のホスト名または IP アドレス | - |
| $port | int | Yes | 接続先のポート番号 | 有効なポート番号（通常 1-65535） |
| $opts | ConnectOptions | No | 接続オプション。デフォルトは空の shape | - |

### ConnectOptions の構造

| フィールド名 | 型 | 説明 |
|-------------|-----|------|
| timeout_ns | ?int | 接続タイムアウト（ナノ秒単位）。0 または未指定でタイムアウトなし |
| ip_version | IPProtocolBehavior | IP プロトコルバージョンの動作指定 |

### IPProtocolBehavior の値

| 値 | 説明 |
|---|------|
| PREFER_IPV6 | IPv6 を優先し、失敗した場合は IPv4 にフォールバック（デフォルト） |
| FORCE_IPV6 | IPv6 のみ使用 |
| FORCE_IPV4 | IPv4 のみ使用 |

### 入力データソース

ネットワーク（TCP/IP スタック）

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 戻り値 | Awaitable<CloseableSocket> | 接続されたTCPソケット。読み書き可能なハンドル |

### CloseableSocket インターフェース

| メソッド | 戻り値 | 説明 |
|---------|--------|------|
| getLocalAddress() | (string, int) | ローカル側のアドレスとポートを取得 |
| getPeerAddress() | (string, int) | リモート側のアドレスとポートを取得 |
| readAllowPartialSuccessAsync() | Awaitable<string> | データを非同期で読み取り |
| writeAllowPartialSuccessAsync(string) | Awaitable<int> | データを非同期で書き込み |
| close() | void | ソケットを閉じる |

### 出力先

関数の戻り値として呼び出し元に返却（Awaitable）

## 処理フロー

### 処理シーケンス

```
1. IP プロトコルバージョンの決定
   └─ オプションから ip_version を取得（デフォルト: PREFER_IPV6）
2. ソケットドメインのリスト作成
   └─ PREFER_IPV6: [PF_INET6, PF_INET]
   └─ FORCE_IPV6: [PF_INET6]
   └─ FORCE_IPV4: [PF_INET]
3. 各ドメインに対してループ処理
   a. ソケット作成 (OS\socket)
   b. ホスト名解決 (resolve_hostname)
   c. sockaddr 構造体の構築
   d. 非同期接続の試行 (socket_connect_async)
   e. 成功時は CloseableTCPSocket を返却
   f. 失敗時は次のドメインを試行
4. 全ドメインで失敗した場合は例外をスロー
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B[IP バージョン決定]
    B --> C[ドメインリスト作成]
    C --> D{ドメインが残っている?}
    D -->|Yes| E[ソケット作成]
    E --> F[ホスト名解決]
    F --> G{解決成功?}
    G -->|No| D
    G -->|Yes| H[sockaddr 構築]
    H --> I[非同期接続試行]
    I --> J{接続成功?}
    J -->|Yes| K[CloseableTCPSocket 返却]
    J -->|No| L[例外を保存]
    L --> D
    D -->|No| M{例外あり?}
    M -->|Yes| N[例外をスロー]
    M -->|No| O[EINVAL エラー]
    K --> P[終了]
    N --> Q[異常終了]
    O --> Q
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-157-01 | IPv6 優先 | デフォルトでは IPv6 を優先し、失敗時に IPv4 にフォールバック | ip_version 未指定時 |
| BR-157-02 | タイムアウト分離 | IPv4 と IPv6 のフォールバック時、タイムアウトは各接続試行に個別に適用される | timeout_ns 指定時 |
| BR-157-03 | 接続失敗時の例外 | 全てのプロトコルで接続に失敗した場合、最後の ErrnoException をスロー | 接続失敗時 |
| BR-157-04 | ノンブロッキング | 接続処理は非同期で実行され、呼び出し元をブロックしない | 常時 |

### 計算ロジック

タイムアウトの適用：
- `timeout_ns` が指定された場合、各 IP バージョンへの接続試行ごとに独立してタイムアウトが適用される
- PREFER_IPV6 の場合、IPv6 と IPv4 それぞれに `timeout_ns` が適用される（合計で最大 2 * timeout_ns の待機時間）

## データベース操作仕様

### 操作別データベース影響一覧

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| - | - | - | 本機能はデータベース操作を行わない |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| ECONNREFUSED | OS\ErrnoException | 接続先がリッスンしていない | ホスト・ポートを確認、サーバーが起動しているか確認 |
| ETIMEDOUT | OS\ErrnoException | 接続がタイムアウト | ネットワーク状態を確認、タイムアウト値を調整 |
| ENETUNREACH | OS\ErrnoException | ネットワークに到達不能 | ネットワーク設定を確認 |
| ECONNRESET | OS\ErrnoException | 接続がリセットされた | リトライを検討 |
| EINVAL | OS\ErrnoException | 有効なアドレスを構築できなかった | ホスト名を確認 |
| EADDRNOTAVAIL | OS\ErrnoException | アドレスが利用不可（IPv6 非対応など） | IP バージョンを変更 |

### リトライ仕様

本関数自体にはリトライ機構はない。PREFER_IPV6 モードでは IPv6 失敗時に IPv4 へのフォールバックを試行する。アプリケーションレベルでのリトライは呼び出し側で実装する。

## トランザクション仕様

本機能はトランザクションを使用しない。接続はアトミックな操作として扱われる。

## パフォーマンス要件

- 接続確立のレイテンシ：ネットワーク状況に依存（通常ミリ秒オーダー）
- 非同期実行により、接続待機中に他の処理が可能
- ソケットは O_NONBLOCK モードで操作される

## セキュリティ考慮事項

- TLS/SSL 暗号化は本関数のスコープ外（上位レイヤーで実装）
- ホスト名解決は DNS スプーフィングの影響を受ける可能性がある
- ネットワークアクセスはOSのファイアウォールポリシーに従う
- 信頼できないネットワークでの通信には TLS の使用を推奨

## 備考

- 非推奨の `connect_nd_async` 関数が存在するが、`connect_async` を使用すること
- 戻り値の `CloseableSocket` は使用後に必ず `close()` を呼び出すこと
- Unix ソケットへの接続には `Unix\connect_async` を使用すること

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

接続オプションとプロトコル関連の型定義を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ConnectOptions.php | `src/tcp/ConnectOptions.php` | ConnectOptions 型エイリアスの定義 |
| 1-2 | IPProtocolBehavior.php | `src/network/IPProtocolBehavior.php` | IP プロトコル動作の enum 定義 |
| 1-3 | Socket.php | `src/tcp/Socket.php` | TCP ソケットインターフェース定義。TAddress の型 |
| 1-4 | CloseableSocket.php | `src/tcp/CloseableSocket.php` | 閉じることができるソケットのインターフェース |

**読解のコツ**: `ConnectOptions` は shape 型で、オプショナルなフィールドを持つ。`IPProtocolBehavior` は enum で 3 つの値を持つ。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | connect.php | `src/tcp/connect.php` | connect_async 関数の実装（21-94行目） |

**主要処理フロー**:
1. **26-27行目**: オプションから ip_version と timeout_ns を取得
2. **28-38行目**: ip_version に基づいてソケットドメインのリストを構築
3. **41-86行目**: 各ドメインに対してループ処理
4. **42行目**: OS\socket でソケット作成
5. **44-74行目**: ドメインに応じてホスト名解決と sockaddr 構築
6. **80-82行目**: 非同期接続試行と CloseableTCPSocket 返却
7. **83-85行目**: 例外発生時は保存して次のドメインへ
8. **87-93行目**: 全失敗時に例外スロー

```hack
async function connect_async(
  string $host,
  int $port,
  ConnectOptions $opts = shape(),
): Awaitable<CloseableSocket> {
  $ipver = $opts['ip_version'] ?? Network\IPProtocolBehavior::PREFER_IPV6;
  $timeout_ns = $opts['timeout_ns'] ?? 0;
  // ...
}
```

#### Step 3: 内部実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | socket_connect_async.php | `src/network/_Private/socket_connect_async.php` | 非同期接続の低レベル実装 |
| 3-2 | resolve_hostname.php | `src/network/_Private/resolve_hostname.php` | ホスト名解決の実装 |
| 3-3 | CloseableTCPSocket.php | `src/tcp/_Private/CloseableTCPSocket.php` | TCP ソケットの具象クラス |

**socket_connect_async.php の主要処理**:
- **28-29行目**: ソケットを O_NONBLOCK モードに設定
- **30-32行目**: connect を試行し、BlockingIOException をキャッチ
- **36行目**: poll_async で書き込み可能になるまで待機
- **40-44行目**: タイムアウトやクローズ時のエラー処理
- **47-52行目**: SO_ERROR でエラーチェック

### プログラム呼び出し階層図

```
TCP\connect_async($host, $port, $opts)
    │
    ├─ オプション解析
    │
    ├─ ドメインループ
    │      │
    │      ├─ OS\socket(domain, SOCK_STREAM, 0)
    │      │
    │      ├─ _Network\resolve_hostname(af, $host)
    │      │      └─ gethostbyname / dns_get_record
    │      │
    │      ├─ sockaddr 構築
    │      │      ├─ OS\sockaddr_in (IPv4)
    │      │      └─ OS\sockaddr_in6 (IPv6)
    │      │
    │      ├─ _Network\socket_connect_async($sock, $sa, $timeout)
    │      │      ├─ OS\fcntl (O_NONBLOCK 設定)
    │      │      ├─ OS\connect
    │      │      └─ _OS\poll_async (接続完了待機)
    │      │
    │      └─ _TCP\CloseableTCPSocket($sock)
    │
    └─ 例外処理
```

### データフロー図

```
[入力]                         [処理]                              [出力]

$host (string) ──────▶┐
                      │
$port (int) ─────────▶├──▶ ドメインループ ──▶ socket() ──▶ ソケットFD
                      │              │
$opts (shape) ───────▶┘              ▼
                              resolve_hostname() ──▶ IP アドレス
                                     │
                                     ▼
                              sockaddr 構築
                                     │
                                     ▼
                              socket_connect_async()
                                     │
                                     ▼
                              CloseableTCPSocket ──▶ CloseableSocket
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| connect.php | `src/tcp/connect.php` | ソース | connect_async 関数の実装 |
| ConnectOptions.php | `src/tcp/ConnectOptions.php` | ソース | 接続オプションの型定義 |
| Socket.php | `src/tcp/Socket.php` | ソース | TCP ソケットインターフェース |
| CloseableSocket.php | `src/tcp/CloseableSocket.php` | ソース | 閉じることができるソケットインターフェース |
| CloseableTCPSocket.php | `src/tcp/_Private/CloseableTCPSocket.php` | ソース | TCP ソケットの具象実装 |
| socket_connect_async.php | `src/network/_Private/socket_connect_async.php` | ソース | 非同期接続の低レベル実装 |
| resolve_hostname.php | `src/network/_Private/resolve_hostname.php` | ソース | ホスト名解決の実装 |
| IPProtocolBehavior.php | `src/network/IPProtocolBehavior.php` | ソース | IP プロトコル動作の enum |
| Socket.php | `src/network/Socket.php` | ソース | 汎用ソケットインターフェース |
| CloseableSocket.php | `src/network/CloseableSocket.php` | ソース | 汎用閉じることができるソケットインターフェース |
| HSLTCPTest.php | `tests/tcp/HSLTCPTest.php` | テスト | TCP 接続のユニットテスト |
