blob: f664ca2509849396d611ac66d52a68fb037605c7 [file] [log] [blame]
Victor Hsieh88ac6ca2020-11-13 15:20:24 -08001/*
2 * Copyright (C) 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! This crate implements AuthFS, a FUSE-based, non-generic filesystem where file access is
18//! authenticated. This filesystem assumes the underlying layer is not trusted, e.g. file may be
19//! provided by an untrusted host/VM, so that the content can't be simply trusted. However, with a
Victor Hsieh5deba522022-01-10 17:18:40 -080020//! known file hash from trusted party, this filesystem can still verify a (read-only) file even if
21//! the host/VM as the blob provider is malicious. With the Merkle tree, each read of file block can
22//! be verified individually only when needed.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080023//!
Victor Hsieh5deba522022-01-10 17:18:40 -080024//! AuthFS only serve files that are specifically configured. Each remote file can be configured to
25//! appear as a local file at the mount point. A file configuration may include its remote file
26//! identifier and its verification method (e.g. by known digest).
27//!
28//! AuthFS also support remote directories. A remote directory may be defined by a manifest file,
29//! which contains file paths and their corresponding digests.
30//!
31//! AuthFS can also be configured for write, in which case the remote file server is treated as a
32//! (untrusted) storage. The file/directory integrity is maintained in memory in the VM. Currently,
33//! the state is not persistent, thus only new file/directory are supported.
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080034
Victor Hsieh99782572022-01-05 15:38:33 -080035use anyhow::{anyhow, bail, Result};
Alan Stokese1b6e1c2021-10-01 12:44:49 +010036use log::error;
Victor Hsieh99782572022-01-05 15:38:33 -080037use protobuf::Message;
Victor Hsieh50d75ac2021-09-03 14:46:55 -070038use std::convert::TryInto;
Victor Hsieh99782572022-01-05 15:38:33 -080039use std::fs::File;
Victor Hsiehd18b9752021-11-09 16:03:34 -080040use std::path::{Path, PathBuf};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080041use structopt::StructOpt;
42
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080043mod common;
44mod crypto;
Victor Hsieh09e26262021-03-03 16:00:55 -080045mod file;
Victor Hsiehf7fc3d32021-11-22 10:20:33 -080046mod fsstat;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080047mod fsverity;
48mod fusefs;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080049
Victor Hsiehd18b9752021-11-09 16:03:34 -080050use file::{
Victor Hsieh35dfa1e2022-01-12 17:03:35 -080051 Attr, EagerChunkReader, InMemoryDir, RemoteDirEditor, RemoteFileEditor, RemoteFileReader,
52 RemoteMerkleTreeReader,
Victor Hsiehd18b9752021-11-09 16:03:34 -080053};
Victor Hsiehf7fc3d32021-11-22 10:20:33 -080054use fsstat::RemoteFsStatsReader;
Victor Hsieh35dfa1e2022-01-12 17:03:35 -080055use fsverity::{merkle_tree_size, VerifiedFileEditor, VerifiedFileReader};
Victor Hsieh99782572022-01-05 15:38:33 -080056use fsverity_digests_proto::fsverity_digests::FSVerityDigests;
Victor Hsieh4d6b9d42021-11-08 15:53:49 -080057use fusefs::{AuthFs, AuthFsEntry};
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080058
59#[derive(StructOpt)]
Victor Hsiehf01f3232020-12-11 13:31:31 -080060struct Args {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -080061 /// Mount point of AuthFS.
62 #[structopt(parse(from_os_str))]
63 mount_point: PathBuf,
64
Victor Hsieh2445e332021-06-04 16:44:53 -070065 /// CID of the VM where the service runs.
66 #[structopt(long)]
Victor Hsieh1a8cd042021-09-03 16:29:45 -070067 cid: u32,
Victor Hsieh2445e332021-06-04 16:44:53 -070068
Victor Hsieh4cc3b792021-08-04 12:00:04 -070069 /// Extra options to FUSE
70 #[structopt(short = "o")]
71 extra_options: Option<String>,
72
Victor Hsieh09e26262021-03-03 16:00:55 -080073 /// A read-only remote file with integrity check. Can be multiple.
Victor Hsiehf01f3232020-12-11 13:31:31 -080074 ///
Victor Hsieh5deba522022-01-10 17:18:40 -080075 /// For example, `--remote-ro-file 5:sha256-1234abcd` tells the filesystem to associate the
76 /// file $MOUNTPOINT/5 with a remote FD 5, and has a fs-verity digest with sha256 of the hex
77 /// value 1234abcd.
Victor Hsieh09e26262021-03-03 16:00:55 -080078 #[structopt(long, parse(try_from_str = parse_remote_ro_file_option))]
79 remote_ro_file: Vec<OptionRemoteRoFile>,
Victor Hsiehf01f3232020-12-11 13:31:31 -080080
Victor Hsieh09e26262021-03-03 16:00:55 -080081 /// A read-only remote file without integrity check. Can be multiple.
Victor Hsiehf01f3232020-12-11 13:31:31 -080082 ///
Victor Hsiehb3588ce2021-11-02 15:02:32 -070083 /// For example, `--remote-ro-file-unverified 5` tells the filesystem to associate the file
84 /// $MOUNTPOINT/5 with a remote FD 5.
85 #[structopt(long)]
86 remote_ro_file_unverified: Vec<i32>,
Victor Hsiehf01f3232020-12-11 13:31:31 -080087
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080088 /// A new read-writable remote file with integrity check. Can be multiple.
89 ///
Victor Hsiehb3588ce2021-11-02 15:02:32 -070090 /// For example, `--remote-new-rw-file 5` tells the filesystem to associate the file
91 /// $MOUNTPOINT/5 with a remote FD 5.
92 #[structopt(long)]
93 remote_new_rw_file: Vec<i32>,
Victor Hsieh6a47e7f2021-03-03 15:53:49 -080094
Victor Hsiehd18b9752021-11-09 16:03:34 -080095 /// A read-only directory that represents a remote directory. The directory view is constructed
96 /// and finalized during the filesystem initialization based on the provided mapping file
97 /// (which is a serialized protobuf of android.security.fsverity.FSVerityDigests, which
98 /// essentially provides <file path, fs-verity digest> mappings of exported files). The mapping
99 /// file is supposed to come from a trusted location in order to provide a trusted view as well
100 /// as verified access of included files with their fs-verity digest. Not all files on the
101 /// remote host may be included in the mapping file, so the directory view may be partial. The
102 /// directory structure won't change throughout the filesystem lifetime.
103 ///
Victor Hsieh99782572022-01-05 15:38:33 -0800104 /// For example, `--remote-ro-dir 5:/path/to/mapping:prefix/` tells the filesystem to
Victor Hsiehd18b9752021-11-09 16:03:34 -0800105 /// construct a directory structure defined in the mapping file at $MOUNTPOINT/5, which may
Victor Hsieh99782572022-01-05 15:38:33 -0800106 /// include a file like /5/system/framework/framework.jar. "prefix/" tells the filesystem to
107 /// strip the path (e.g. "system/") from the mount point to match the expected location of the
Victor Hsiehd18b9752021-11-09 16:03:34 -0800108 /// remote FD (e.g. a directory FD of "/system" in the remote).
109 #[structopt(long, parse(try_from_str = parse_remote_new_ro_dir_option))]
110 remote_ro_dir: Vec<OptionRemoteRoDir>,
111
Victor Hsieh45636232021-10-15 17:52:51 -0700112 /// A new directory that is assumed empty in the backing filesystem. New files created in this
113 /// directory are integrity-protected in the same way as --remote-new-verified-file. Can be
114 /// multiple.
115 ///
Victor Hsiehb3588ce2021-11-02 15:02:32 -0700116 /// For example, `--remote-new-rw-dir 5` tells the filesystem to associate $MOUNTPOINT/5
117 /// with a remote dir FD 5.
118 #[structopt(long)]
119 remote_new_rw_dir: Vec<i32>,
Victor Hsieh45636232021-10-15 17:52:51 -0700120
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700121 /// Enable debugging features.
122 #[structopt(long)]
123 debug: bool,
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800124}
125
Victor Hsieh09e26262021-03-03 16:00:55 -0800126struct OptionRemoteRoFile {
Victor Hsiehf01f3232020-12-11 13:31:31 -0800127 /// ID to refer to the remote file.
Victor Hsiehb3588ce2021-11-02 15:02:32 -0700128 remote_fd: i32,
Victor Hsiehf01f3232020-12-11 13:31:31 -0800129
Victor Hsieh5deba522022-01-10 17:18:40 -0800130 /// Expected fs-verity digest (with sha256) for the remote file.
131 digest: String,
Victor Hsiehf01f3232020-12-11 13:31:31 -0800132}
133
Victor Hsiehd18b9752021-11-09 16:03:34 -0800134struct OptionRemoteRoDir {
135 /// ID to refer to the remote dir.
136 remote_dir_fd: i32,
137
138 /// A mapping file that describes the expecting file/directory structure and integrity metadata
139 /// in the remote directory. The file contains serialized protobuf of
140 /// android.security.fsverity.FSVerityDigests.
Victor Hsiehd18b9752021-11-09 16:03:34 -0800141 mapping_file_path: PathBuf,
142
Victor Hsieh99782572022-01-05 15:38:33 -0800143 prefix: String,
Victor Hsiehd18b9752021-11-09 16:03:34 -0800144}
145
Victor Hsieh09e26262021-03-03 16:00:55 -0800146fn parse_remote_ro_file_option(option: &str) -> Result<OptionRemoteRoFile> {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800147 let strs: Vec<&str> = option.split(':').collect();
Victor Hsiehb3588ce2021-11-02 15:02:32 -0700148 if strs.len() != 2 {
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800149 bail!("Invalid option: {}", option);
150 }
Victor Hsieh5deba522022-01-10 17:18:40 -0800151 if let Some(digest) = strs[1].strip_prefix("sha256-") {
152 Ok(OptionRemoteRoFile { remote_fd: strs[0].parse::<i32>()?, digest: String::from(digest) })
153 } else {
154 bail!("Unsupported hash algorithm or invalid format: {}", strs[1]);
155 }
Victor Hsieh45636232021-10-15 17:52:51 -0700156}
157
Victor Hsiehd18b9752021-11-09 16:03:34 -0800158fn parse_remote_new_ro_dir_option(option: &str) -> Result<OptionRemoteRoDir> {
159 let strs: Vec<&str> = option.split(':').collect();
160 if strs.len() != 3 {
161 bail!("Invalid option: {}", option);
162 }
163 Ok(OptionRemoteRoDir {
164 remote_dir_fd: strs[0].parse::<i32>().unwrap(),
165 mapping_file_path: PathBuf::from(strs[1]),
Victor Hsieh99782572022-01-05 15:38:33 -0800166 prefix: String::from(strs[2]),
Victor Hsiehd18b9752021-11-09 16:03:34 -0800167 })
168}
169
Victor Hsieh5deba522022-01-10 17:18:40 -0800170fn from_hex_string(s: &str) -> Result<Vec<u8>> {
171 if s.len() % 2 == 1 {
172 bail!("Incomplete hex string: {}", s);
173 } else {
174 let results = (0..s.len())
175 .step_by(2)
176 .map(|i| {
177 u8::from_str_radix(&s[i..i + 2], 16)
178 .map_err(|e| anyhow!("Cannot parse hex {}: {}", &s[i..i + 2], e))
179 })
180 .collect::<Result<Vec<_>>>();
181 Ok(results?)
182 }
183}
184
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700185fn new_remote_verified_file_entry(
Victor Hsieh2445e332021-06-04 16:44:53 -0700186 service: file::VirtFdService,
Victor Hsiehb3588ce2021-11-02 15:02:32 -0700187 remote_fd: i32,
Victor Hsieh5deba522022-01-10 17:18:40 -0800188 expected_digest: &str,
Victor Hsieh2445e332021-06-04 16:44:53 -0700189 file_size: u64,
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700190) -> Result<AuthFsEntry> {
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700191 Ok(AuthFsEntry::VerifiedReadonly {
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700192 reader: VerifiedFileReader::new(
Victor Hsiehb3588ce2021-11-02 15:02:32 -0700193 RemoteFileReader::new(service.clone(), remote_fd),
Victor Hsiehf01f3232020-12-11 13:31:31 -0800194 file_size,
Victor Hsieh5deba522022-01-10 17:18:40 -0800195 &from_hex_string(expected_digest)?,
Victor Hsieh35dfa1e2022-01-12 17:03:35 -0800196 EagerChunkReader::new(
197 RemoteMerkleTreeReader::new(service.clone(), remote_fd),
198 merkle_tree_size(file_size),
199 )?,
Victor Hsiehf01f3232020-12-11 13:31:31 -0800200 )?,
201 file_size,
Victor Hsieh1bcf4112021-03-19 14:26:57 -0700202 })
Victor Hsiehf01f3232020-12-11 13:31:31 -0800203}
204
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700205fn new_remote_unverified_file_entry(
Victor Hsieh2445e332021-06-04 16:44:53 -0700206 service: file::VirtFdService,
Victor Hsiehb3588ce2021-11-02 15:02:32 -0700207 remote_fd: i32,
Victor Hsieh2445e332021-06-04 16:44:53 -0700208 file_size: u64,
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700209) -> Result<AuthFsEntry> {
Victor Hsiehb3588ce2021-11-02 15:02:32 -0700210 let reader = RemoteFileReader::new(service, remote_fd);
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700211 Ok(AuthFsEntry::UnverifiedReadonly { reader, file_size })
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800212}
213
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700214fn new_remote_new_verified_file_entry(
Victor Hsieh2445e332021-06-04 16:44:53 -0700215 service: file::VirtFdService,
Victor Hsiehb3588ce2021-11-02 15:02:32 -0700216 remote_fd: i32,
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700217) -> Result<AuthFsEntry> {
Victor Hsiehf393a722021-12-08 13:04:27 -0800218 let remote_file = RemoteFileEditor::new(service.clone(), remote_fd);
219 Ok(AuthFsEntry::VerifiedNew {
220 editor: VerifiedFileEditor::new(remote_file),
221 attr: Attr::new_file(service, remote_fd),
222 })
Victor Hsieh6a47e7f2021-03-03 15:53:49 -0800223}
224
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700225fn new_remote_new_verified_dir_entry(
Victor Hsieh45636232021-10-15 17:52:51 -0700226 service: file::VirtFdService,
Victor Hsiehb3588ce2021-11-02 15:02:32 -0700227 remote_fd: i32,
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700228) -> Result<AuthFsEntry> {
Victor Hsiehf393a722021-12-08 13:04:27 -0800229 let dir = RemoteDirEditor::new(service.clone(), remote_fd);
230 let attr = Attr::new_dir(service, remote_fd);
231 Ok(AuthFsEntry::VerifiedNewDirectory { dir, attr })
Victor Hsieh45636232021-10-15 17:52:51 -0700232}
233
Victor Hsiehf7fc3d32021-11-22 10:20:33 -0800234fn prepare_root_dir_entries(
235 service: file::VirtFdService,
236 authfs: &mut AuthFs,
237 args: &Args,
238) -> Result<()> {
Victor Hsieh88e50172021-10-15 13:27:13 -0700239 for config in &args.remote_ro_file {
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800240 authfs.add_entry_at_root_dir(
Victor Hsieh60c2f412021-11-03 13:02:19 -0700241 remote_fd_to_path_buf(config.remote_fd),
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700242 new_remote_verified_file_entry(
Victor Hsieh88e50172021-10-15 13:27:13 -0700243 service.clone(),
Victor Hsiehb3588ce2021-11-02 15:02:32 -0700244 config.remote_fd,
Victor Hsieh5deba522022-01-10 17:18:40 -0800245 &config.digest,
Victor Hsiehb3588ce2021-11-02 15:02:32 -0700246 service.getFileSize(config.remote_fd)?.try_into()?,
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800247 )?,
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800248 )?;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800249 }
250
Victor Hsiehb3588ce2021-11-02 15:02:32 -0700251 for remote_fd in &args.remote_ro_file_unverified {
252 let remote_fd = *remote_fd;
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800253 authfs.add_entry_at_root_dir(
Victor Hsieh60c2f412021-11-03 13:02:19 -0700254 remote_fd_to_path_buf(remote_fd),
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700255 new_remote_unverified_file_entry(
Victor Hsieh88e50172021-10-15 13:27:13 -0700256 service.clone(),
Victor Hsiehb3588ce2021-11-02 15:02:32 -0700257 remote_fd,
258 service.getFileSize(remote_fd)?.try_into()?,
Victor Hsieh88e50172021-10-15 13:27:13 -0700259 )?,
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800260 )?;
Victor Hsieh88e50172021-10-15 13:27:13 -0700261 }
262
Victor Hsiehb3588ce2021-11-02 15:02:32 -0700263 for remote_fd in &args.remote_new_rw_file {
264 let remote_fd = *remote_fd;
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800265 authfs.add_entry_at_root_dir(
Victor Hsieh60c2f412021-11-03 13:02:19 -0700266 remote_fd_to_path_buf(remote_fd),
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700267 new_remote_new_verified_file_entry(service.clone(), remote_fd)?,
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800268 )?;
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800269 }
270
Victor Hsiehb3588ce2021-11-02 15:02:32 -0700271 for remote_fd in &args.remote_new_rw_dir {
272 let remote_fd = *remote_fd;
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800273 authfs.add_entry_at_root_dir(
Victor Hsieh60c2f412021-11-03 13:02:19 -0700274 remote_fd_to_path_buf(remote_fd),
Victor Hsieh26cea2f2021-11-03 10:28:33 -0700275 new_remote_new_verified_dir_entry(service.clone(), remote_fd)?,
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800276 )?;
Victor Hsieh45636232021-10-15 17:52:51 -0700277 }
278
Victor Hsiehd18b9752021-11-09 16:03:34 -0800279 for config in &args.remote_ro_dir {
280 let dir_root_inode = authfs.add_entry_at_root_dir(
281 remote_fd_to_path_buf(config.remote_dir_fd),
282 AuthFsEntry::ReadonlyDirectory { dir: InMemoryDir::new() },
283 )?;
284
Victor Hsieh99782572022-01-05 15:38:33 -0800285 // Build the directory tree based on the mapping file.
286 let mut reader = File::open(&config.mapping_file_path)?;
287 let proto = FSVerityDigests::parse_from_reader(&mut reader)?;
Victor Hsieh5deba522022-01-10 17:18:40 -0800288 for (path_str, digest) in &proto.digests {
289 if digest.hash_alg != "sha256" {
290 bail!("Unsupported hash algorithm: {}", digest.hash_alg);
291 }
292
Victor Hsiehd18b9752021-11-09 16:03:34 -0800293 let file_entry = {
Victor Hsieh99782572022-01-05 15:38:33 -0800294 let remote_path_str = path_str.strip_prefix(&config.prefix).ok_or_else(|| {
295 anyhow!("Expect path {} to match prefix {}", path_str, config.prefix)
296 })?;
Victor Hsiehd18b9752021-11-09 16:03:34 -0800297 // TODO(205883847): Not all files will be used. Open the remote file lazily.
Victor Hsiehd18b9752021-11-09 16:03:34 -0800298 let remote_file = RemoteFileReader::new_by_path(
299 service.clone(),
300 config.remote_dir_fd,
Victor Hsieh99782572022-01-05 15:38:33 -0800301 Path::new(remote_path_str),
Victor Hsiehd18b9752021-11-09 16:03:34 -0800302 )?;
Victor Hsieh5deba522022-01-10 17:18:40 -0800303 let remote_fd = remote_file.get_remote_fd();
304 let file_size = service.getFileSize(remote_fd)?.try_into()?;
305 AuthFsEntry::VerifiedReadonly {
306 reader: VerifiedFileReader::new(
307 remote_file,
308 file_size,
309 &digest.digest,
310 EagerChunkReader::new(
311 RemoteMerkleTreeReader::new(service.clone(), remote_fd),
312 merkle_tree_size(file_size),
313 )?,
314 )?,
315 file_size,
316 }
Victor Hsiehd18b9752021-11-09 16:03:34 -0800317 };
Victor Hsieh99782572022-01-05 15:38:33 -0800318 authfs.add_entry_at_ro_dir_by_path(dir_root_inode, Path::new(path_str), file_entry)?;
Victor Hsiehd18b9752021-11-09 16:03:34 -0800319 }
320 }
321
Victor Hsieh4d6b9d42021-11-08 15:53:49 -0800322 Ok(())
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800323}
324
Victor Hsieh60c2f412021-11-03 13:02:19 -0700325fn remote_fd_to_path_buf(fd: i32) -> PathBuf {
326 PathBuf::from(fd.to_string())
327}
328
Alan Stokese1b6e1c2021-10-01 12:44:49 +0100329fn try_main() -> Result<()> {
Victor Hsieh2442abc2021-11-17 13:25:02 -0800330 let args = Args::from_args_safe()?;
Victor Hsieh9d0ab622021-04-26 17:07:02 -0700331
332 let log_level = if args.debug { log::Level::Debug } else { log::Level::Info };
333 android_logger::init_once(
334 android_logger::Config::default().with_tag("authfs").with_min_level(log_level),
335 );
336
Victor Hsiehf7fc3d32021-11-22 10:20:33 -0800337 let service = file::get_rpc_binder_service(args.cid)?;
338 let mut authfs = AuthFs::new(RemoteFsStatsReader::new(service.clone()));
339 prepare_root_dir_entries(service, &mut authfs, &args)?;
340
Victor Hsieh79f296b2021-12-02 15:38:08 -0800341 fusefs::mount_and_enter_message_loop(authfs, &args.mount_point, &args.extra_options)?;
Victor Hsiehf01f3232020-12-11 13:31:31 -0800342 bail!("Unexpected exit after the handler loop")
Victor Hsieh88ac6ca2020-11-13 15:20:24 -0800343}
Alan Stokese1b6e1c2021-10-01 12:44:49 +0100344
345fn main() {
346 if let Err(e) = try_main() {
347 error!("failed with {:?}", e);
348 std::process::exit(1);
349 }
350}
Victor Hsieh5deba522022-01-10 17:18:40 -0800351
352#[cfg(test)]
353mod tests {
354 use super::*;
355
356 #[test]
357 fn parse_hex_string() {
358 assert_eq!(from_hex_string("deadbeef").unwrap(), vec![0xde, 0xad, 0xbe, 0xef]);
359 assert_eq!(from_hex_string("DEADBEEF").unwrap(), vec![0xde, 0xad, 0xbe, 0xef]);
360 assert_eq!(from_hex_string("").unwrap(), Vec::<u8>::new());
361
362 assert!(from_hex_string("deadbee").is_err());
363 assert!(from_hex_string("X").is_err());
364 }
365}