Support TLS. (#49)

diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 4ddd2a3..bf88d86 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -30,7 +30,7 @@
   RUSTFLAGS: "-D warnings"
   LLVM_CONFIG_PATH: llvm-config-10
   RUST_STABLE_TOOLCHAIN: "1.65"
-  RUST_NIGHTLY_TOOLCHAIN: "nightly-2023-01-11"
+  RUST_NIGHTLY_TOOLCHAIN: "nightly-2023-01-28"
 
 jobs:
   required:
diff --git a/.rustfmt.toml b/.rustfmt.toml
index 40ab383..7a6424a 100644
--- a/.rustfmt.toml
+++ b/.rustfmt.toml
@@ -13,7 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-fn_args_layout = "Compressed"
+fn_params_layout = "Compressed"
 format_code_in_doc_comments = true
 format_macro_bodies = true
 format_macro_matchers = true
diff --git a/Cargo.lock b/Cargo.lock
index b5345e2..266bf66 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -154,6 +154,12 @@
 checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
 [[package]]
+name = "base64"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
+
+[[package]]
 name = "bincode"
 version = "1.3.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1711,7 +1717,7 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
 dependencies = [
- "base64",
+ "base64 0.13.1",
  "bytes",
  "encoding_rs",
  "futures-core",
@@ -1755,6 +1761,21 @@
 ]
 
 [[package]]
+name = "ring"
+version = "0.16.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
+dependencies = [
+ "cc",
+ "libc",
+ "once_cell",
+ "spin",
+ "untrusted",
+ "web-sys",
+ "winapi",
+]
+
+[[package]]
 name = "rustc-demangle"
 version = "0.1.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1790,6 +1811,27 @@
 ]
 
 [[package]]
+name = "rustls"
+version = "0.20.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
+dependencies = [
+ "log",
+ "ring",
+ "sct",
+ "webpki",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
+dependencies = [
+ "base64 0.21.0",
+]
+
+[[package]]
 name = "rustversion"
 version = "1.0.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1845,6 +1887,16 @@
 ]
 
 [[package]]
+name = "sct"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
 name = "security-framework"
 version = "2.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1974,7 +2026,7 @@
 checksum = "3af4420143e6b6baef0efad28e7997ea956d25e65e198ac118b18b976ca2cd12"
 dependencies = [
  "async-stream",
- "base64",
+ "base64 0.13.1",
  "bytes",
  "cfg-if",
  "futures-core",
@@ -2059,6 +2111,12 @@
 ]
 
 [[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
 name = "strsim"
 version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2274,6 +2332,17 @@
 ]
 
 [[package]]
+name = "tokio-rustls"
+version = "0.23.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
+dependencies = [
+ "rustls",
+ "tokio",
+ "webpki",
+]
+
+[[package]]
 name = "tokio-stream"
 version = "0.1.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2307,7 +2376,7 @@
  "async-stream",
  "async-trait",
  "axum",
- "base64",
+ "base64 0.13.1",
  "bytes",
  "futures-core",
  "futures-util",
@@ -2320,7 +2389,9 @@
  "pin-project",
  "prost",
  "prost-derive",
+ "rustls-pemfile",
  "tokio",
+ "tokio-rustls",
  "tokio-stream",
  "tokio-util",
  "tower",
@@ -2624,6 +2695,12 @@
 checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
 
 [[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[package]]
 name = "url"
 version = "2.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2772,6 +2849,16 @@
 ]
 
 [[package]]
+name = "webpki"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
+[[package]]
 name = "which"
 version = "4.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 36d9a18..f614978 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -52,7 +52,7 @@
 thiserror = "1.0.38"
 tokio = { version = "1.24.1", features = ["full"] }
 tokio-stream = "0.1.11"
-tonic = "0.8.3"
+tonic = { version = "0.8.3", features = ["tls"] }
 tracing = { version = "0.1.37", features = ["attributes"] }
 tracing-appender = "0.2.2"
 tracing-subscriber = "0.3.16"
diff --git a/docs/en/setup/service-agent/php-agent/README.md b/docs/en/setup/service-agent/php-agent/README.md
index 9393239..896d4c1 100644
--- a/docs/en/setup/service-agent/php-agent/README.md
+++ b/docs/en/setup/service-agent/php-agent/README.md
@@ -68,7 +68,7 @@
 skywalking_agent.log_level = INFO
 
 ; Address of skywalking oap server.
-skywalking_agent.server_addr = http://0.0.0.0:11800
+skywalking_agent.server_addr = 127.0.0.1:11800
 
 ; Application service name.
 skywalking_agent.service_name = hello-skywalking
@@ -84,4 +84,16 @@
 
 ; Skywalking agent runtime directory, default is /tmp/skywalking-agent.
 ; skywalking_agent.runtime_dir = /tmp/skywalking-agent
+
+; Wether to enable tls for gPRC, default is false.
+; skywalking_agent.enable_tls = Off
+
+; The gRPC SSL trusted ca file.
+; skywalking_agent.ssl_trusted_ca_path =
+
+; The private key file. Enable mTLS when ssl_key_path and ssl_cert_chain_path exist.
+; skywalking_agent.ssl_key_path =
+
+; The certificate file. Enable mTLS when ssl_key_path and ssl_cert_chain_path exist.
+; skywalking_agent.ssl_cert_chain_path =
 ```
diff --git a/src/lib.rs b/src/lib.rs
index 0b6cf29..5237d62 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -60,6 +60,20 @@
 /// Skywalking agent authentication token.
 const SKYWALKING_AGENT_AUTHENTICATION: &str = "skywalking_agent.authentication";
 
+/// Wether to enable tls for gPRC.
+const SKYWALKING_AGENT_ENABLE_TLS: &str = "skywalking_agent.enable_tls";
+
+/// The gRPC SSL trusted ca file.
+const SKYWALKING_AGENT_SSL_TRUSTED_CA_PATH: &str = "skywalking_agent.ssl_trusted_ca_path";
+
+/// The private key file. Enable mTLS when ssl_key_path and ssl_cert_chain_path
+/// exist.
+const SKYWALKING_AGENT_SSL_KEY_PATH: &str = "skywalking_agent.ssl_key_path";
+
+/// The certificate file. Enable mTLS when ssl_key_path and ssl_cert_chain_path
+/// exist.
+const SKYWALKING_AGENT_SSL_CERT_CHAIN_PATH: &str = "skywalking_agent.ssl_cert_chain_path";
+
 #[php_get_module]
 pub fn get_module() -> Module {
     let mut module = Module::new(
@@ -73,7 +87,7 @@
     module.add_ini(SKYWALKING_AGENT_SKYWALKING_VERSION, 8i64, Policy::System);
     module.add_ini(
         SKYWALKING_AGENT_SERVER_ADDR,
-        "http://127.0.0.1:11800".to_string(),
+        "127.0.0.1:11800".to_string(),
         Policy::System,
     );
     module.add_ini(
@@ -102,6 +116,22 @@
         "".to_string(),
         Policy::System,
     );
+    module.add_ini(SKYWALKING_AGENT_ENABLE_TLS, false, Policy::System);
+    module.add_ini(
+        SKYWALKING_AGENT_SSL_TRUSTED_CA_PATH,
+        "".to_string(),
+        Policy::System,
+    );
+    module.add_ini(
+        SKYWALKING_AGENT_SSL_KEY_PATH,
+        "".to_string(),
+        Policy::System,
+    );
+    module.add_ini(
+        SKYWALKING_AGENT_SSL_CERT_CHAIN_PATH,
+        "".to_string(),
+        Policy::System,
+    );
 
     // Hooks.
     module.on_module_init(module::init);
diff --git a/src/module.rs b/src/module.rs
index 6af3fe5..9cbb52c 100644
--- a/src/module.rs
+++ b/src/module.rs
@@ -18,9 +18,11 @@
     execute::register_execute_functions,
     util::{get_sapi_module_name, IPS},
     worker::init_worker,
-    SKYWALKING_AGENT_AUTHENTICATION, SKYWALKING_AGENT_ENABLE, SKYWALKING_AGENT_LOG_FILE,
-    SKYWALKING_AGENT_LOG_LEVEL, SKYWALKING_AGENT_RUNTIME_DIR, SKYWALKING_AGENT_SERVICE_NAME,
-    SKYWALKING_AGENT_SKYWALKING_VERSION,
+    SKYWALKING_AGENT_AUTHENTICATION, SKYWALKING_AGENT_ENABLE, SKYWALKING_AGENT_ENABLE_TLS,
+    SKYWALKING_AGENT_LOG_FILE, SKYWALKING_AGENT_LOG_LEVEL, SKYWALKING_AGENT_RUNTIME_DIR,
+    SKYWALKING_AGENT_SERVICE_NAME, SKYWALKING_AGENT_SKYWALKING_VERSION,
+    SKYWALKING_AGENT_SSL_CERT_CHAIN_PATH, SKYWALKING_AGENT_SSL_KEY_PATH,
+    SKYWALKING_AGENT_SSL_TRUSTED_CA_PATH,
 };
 use once_cell::sync::Lazy;
 use phper::{arrays::ZArr, ini::ini_get, sys};
@@ -37,7 +39,7 @@
     str::FromStr,
     time::SystemTime,
 };
-use tracing::{error, info, metadata::LevelFilter};
+use tracing::{debug, error, info, metadata::LevelFilter};
 use tracing_subscriber::FmtSubscriber;
 
 pub static SERVICE_NAME: Lazy<String> = Lazy::new(|| {
@@ -84,6 +86,29 @@
         .unwrap_or_default()
 });
 
+pub static ENABLE_TLS: Lazy<bool> = Lazy::new(|| ini_get::<bool>(SKYWALKING_AGENT_ENABLE_TLS));
+
+pub static SSL_TRUSTED_CA_PATH: Lazy<String> = Lazy::new(|| {
+    ini_get::<Option<&CStr>>(SKYWALKING_AGENT_SSL_TRUSTED_CA_PATH)
+        .and_then(|s| s.to_str().ok())
+        .map(ToOwned::to_owned)
+        .unwrap_or_default()
+});
+
+pub static SSL_KEY_PATH: Lazy<String> = Lazy::new(|| {
+    ini_get::<Option<&CStr>>(SKYWALKING_AGENT_SSL_KEY_PATH)
+        .and_then(|s| s.to_str().ok())
+        .map(ToOwned::to_owned)
+        .unwrap_or_default()
+});
+
+pub static SSL_CERT_CHAIN_PATH: Lazy<String> = Lazy::new(|| {
+    ini_get::<Option<&CStr>>(SKYWALKING_AGENT_SSL_CERT_CHAIN_PATH)
+        .and_then(|s| s.to_str().ok())
+        .map(ToOwned::to_owned)
+        .unwrap_or_default()
+});
+
 pub fn init() {
     if !is_enable() {
         return;
@@ -91,6 +116,7 @@
 
     init_logger();
 
+    // Skywalking agent info.
     let service_name = Lazy::force(&SERVICE_NAME);
     let service_instance = Lazy::force(&SERVICE_INSTANCE);
     let skywalking_version = Lazy::force(&SKYWALKING_VERSION);
@@ -100,7 +126,7 @@
         service_instance, skywalking_version, authentication, "Starting skywalking agent"
     );
 
-    // Skywalking version check
+    // Skywalking version check.
     if *skywalking_version < 8 {
         error!(
             skywalking_version,
@@ -109,6 +135,17 @@
         return;
     }
 
+    // Initialize TLS if enabled.
+    let enable_tls = Lazy::force(&ENABLE_TLS);
+    let ssl_trusted_ca_path = Lazy::force(&SSL_TRUSTED_CA_PATH);
+    let ssl_key_path = Lazy::force(&SSL_KEY_PATH);
+    let ssl_cert_chain_path = Lazy::force(&SSL_CERT_CHAIN_PATH);
+    debug!(
+        enable_tls,
+        ssl_trusted_ca_path, ssl_key_path, ssl_cert_chain_path, "Skywalking TLS info"
+    );
+
+    // Initialize runtime directory.
     if RUNTIME_DIR.as_os_str().is_empty() {
         error!("The skywalking agent runtime directory must not be empty");
         return;
@@ -118,6 +155,7 @@
         return;
     }
 
+    // Initialize Agent worker.
     Lazy::force(&SOCKET_FILE_PATH);
     init_worker();
 
@@ -127,6 +165,7 @@
         Reporter::new(&*SOCKET_FILE_PATH),
     ));
 
+    // Hook functions.
     register_execute_functions();
 }
 
diff --git a/src/worker.rs b/src/worker.rs
index 1e4fd41..75912af 100644
--- a/src/worker.rs
+++ b/src/worker.rs
@@ -15,7 +15,10 @@
 
 use crate::{
     channel::{self, TxReporter},
-    module::{AUTHENTICATION, SERVICE_INSTANCE, SERVICE_NAME, SOCKET_FILE_PATH},
+    module::{
+        AUTHENTICATION, ENABLE_TLS, SERVICE_INSTANCE, SERVICE_NAME, SOCKET_FILE_PATH,
+        SSL_CERT_CHAIN_PATH, SSL_KEY_PATH, SSL_TRUSTED_CA_PATH,
+    },
     util::change_permission,
     SKYWALKING_AGENT_SERVER_ADDR, SKYWALKING_AGENT_WORKER_THREADS,
 };
@@ -43,7 +46,7 @@
 };
 use tonic::{
     async_trait,
-    transport::{Channel, Endpoint},
+    transport::{Certificate, Channel, ClientTlsConfig, Endpoint, Identity},
 };
 use tracing::{debug, error, info, warn};
 
@@ -166,7 +169,7 @@
             }
         });
 
-        let endpoint = Endpoint::from_shared(server_addr)?;
+        let endpoint = create_endpoint(&server_addr).await?;
         let channel = connect(endpoint).await;
 
         report_properties_and_keep_alive(TxReporter(tx_));
@@ -208,6 +211,42 @@
     Ok(())
 }
 
+async fn create_endpoint(server_addr: &str) -> anyhow::Result<Endpoint> {
+    let scheme = if *ENABLE_TLS { "https" } else { "http" };
+
+    let url = format!("{}://{}", scheme, server_addr);
+    debug!(url, "Create Endpoint");
+    let mut endpoint = Endpoint::from_shared(url)?;
+
+    if *ENABLE_TLS {
+        let domain_name = server_addr.split(':').next().unwrap_or_default();
+        debug!(domain_name, "Configure TLS domain");
+        let mut tls = ClientTlsConfig::new().domain_name(domain_name);
+
+        let ssl_trusted_ca_path = SSL_TRUSTED_CA_PATH.as_str();
+        if !ssl_trusted_ca_path.is_empty() {
+            debug!(ssl_trusted_ca_path, "Configure TLS CA");
+            let ca_cert = tokio::fs::read(&*SSL_TRUSTED_CA_PATH).await?;
+            let ca_cert = Certificate::from_pem(ca_cert);
+            tls = tls.ca_certificate(ca_cert);
+        }
+
+        let ssl_key_path = SSL_KEY_PATH.as_str();
+        let ssl_cert_chain_path = SSL_CERT_CHAIN_PATH.as_str();
+        if !ssl_key_path.is_empty() && !ssl_cert_chain_path.is_empty() {
+            debug!(ssl_trusted_ca_path, "Configure mTLS");
+            let client_cert = tokio::fs::read(&*SSL_CERT_CHAIN_PATH).await?;
+            let client_key = tokio::fs::read(&*SSL_KEY_PATH).await?;
+            let client_identity = Identity::from_pem(client_cert, client_key);
+            tls = tls.identity(client_identity);
+        }
+
+        endpoint = endpoint.tls_config(tls)?;
+    }
+
+    Ok(endpoint)
+}
+
 #[tracing::instrument(skip_all)]
 async fn connect(endpoint: Endpoint) -> Channel {
     let channel = loop {
diff --git a/tests/common/mod.rs b/tests/common/mod.rs
index 7e9fe85..4c40fd7 100644
--- a/tests/common/mod.rs
+++ b/tests/common/mod.rs
@@ -271,10 +271,7 @@
             index
         ),
         "-d",
-        &format!(
-            "skywalking_agent.server_addr=http://{}",
-            COLLECTOR_GRPC_ADDRESS
-        ),
+        &format!("skywalking_agent.server_addr={}", COLLECTOR_GRPC_ADDRESS),
         "-d",
         &format!("skywalking_agent.log_level={}", PROCESS_LOG_LEVEL),
         "-d",
@@ -313,10 +310,7 @@
             index
         ),
         "-d",
-        &format!(
-            "skywalking_agent.server_addr=http://{}",
-            COLLECTOR_GRPC_ADDRESS
-        ),
+        &format!("skywalking_agent.server_addr={}", COLLECTOR_GRPC_ADDRESS),
         "-d",
         &format!("skywalking_agent.log_level={}", PROCESS_LOG_LEVEL),
         "-d",