donet_core/lib.rs
1/*
2 This file is part of Donet.
3
4 Copyright © 2024 Max Rodriguez <[email protected]>
5
6 Donet is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Affero General Public License,
8 as published by the Free Software Foundation, either version 3
9 of the License, or (at your option) any later version.
10
11 Donet is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU Affero General Public License for more details.
15
16 You should have received a copy of the GNU Affero General Public
17 License along with Donet. If not, see <https://www.gnu.org/licenses/>.
18*/
19
20//! # donet-core
21//! Provides the necessary utilities and definitions for using the Donet networking protocol.
22//!
23//! These utilities include a lexer, parser, and high-level representation of the parsed DC
24//! file, as well as creating datagrams, iterating through datagrams, and the definition of
25//! every message type in the Donet protocol.
26//!
27//! ### Getting Started
28//! The recommended way to get started is to enable all features.
29//! Do this by enabling the `full` feature flag:
30//! ```toml
31//! donet-core = { version = "0.1.0", features = ["full"] }
32//! ```
33//!
34//! ### Feature Flags
35//! The crate provides a set of feature flags to reduce the amount of compiled code.
36//! It is possible to just enable certain features over others.
37//! Below is a list of the available feature flags.
38//!
39//! - **`full`**: Enables all feature flags available for donet-core.
40//! - **`datagram`**: Includes Datagram / Datagram Iterator source for writing network packets.
41//! - **`dcfile`**: Includes the DC file lexer, parser, and DC element structures.
42//!
43//! You can return to the Donet manual at [`docs.donet-server.org`].
44//!
45//! [`docs.donet-server.org`]: https://docs.donet-server.org/
46
47#![doc(
48 html_logo_url = "https://gitlab.com/donet-server/donet/-/raw/master/logo/donet_logo_v3.png?ref_type=heads"
49)]
50#![allow(clippy::module_inception)]
51//#![warn(missing_docs)]
52#![deny(unused_extern_crates)]
53
54pub mod globals;
55pub mod protocol;
56
57// Re-export of protocol module
58pub use protocol::*;
59
60#[macro_use]
61extern crate cfg_if;
62
63#[cfg(feature = "datagram")]
64pub mod datagram;
65
66cfg_if! {
67 if #[cfg(feature = "dcfile")] {
68 mod parser;
69 pub mod dcarray;
70 pub mod dcatomic;
71 pub mod dcfield;
72 pub mod dcfile;
73 pub mod dckeyword;
74 pub mod dclass;
75 pub mod dcmolecular;
76 pub mod dcnumeric;
77 pub mod dconfig;
78 pub mod dcparameter;
79 pub mod dcstruct;
80 pub mod dcswitch;
81 pub mod dctype;
82 mod hashgen;
83
84 use anyhow::Result;
85 use dcfile::DCFile;
86 use parser::error::DCReadError;
87 }
88}
89
90/// Returns false if a [`log`] logger is not initialized.
91///
92/// [`log`]: https://docs.rs/log/latest/log/
93///
94#[cfg(feature = "dcfile")]
95fn logger_initialized() -> bool {
96 use log::Level::*;
97
98 let levels: &[log::Level] = &[Error, Warn, Info, Debug, Trace];
99
100 for level in levels {
101 if log::log_enabled!(*level) {
102 return true;
103 }
104 }
105 false
106}
107
108/// Creates a [`pretty_env_logger`] logger if no [`log`]
109/// logger is found to be initialized in this process.
110///
111/// [`pretty_env_logger`]: https://docs.rs/pretty_env_logger/latest/pretty_env_logger/
112/// [`log`]: https://docs.rs/log/latest/log/
113///
114#[cfg(feature = "dcfile")]
115fn init_logger() {
116 if logger_initialized() {
117 return;
118 }
119 pretty_env_logger::init();
120}
121
122/// Easy to use interface for the DC file parser. Handles reading
123/// the DC files, instantiating the DC parsing pipeline, and either
124/// returns the DCFile object or a Parse/File error.
125#[cfg(feature = "dcfile")]
126pub fn read_dc_files<'a>(
127 config: dconfig::DCFileConfig,
128 file_paths: Vec<String>,
129) -> Result<DCFile<'a>, DCReadError> {
130 use log::{info, warn};
131 use parser::InputFile;
132 use std::fs::File;
133 use std::io::{Error, ErrorKind, Read};
134 use std::path::Path;
135
136 init_logger();
137 info!("DC read of {:?}", file_paths);
138
139 let mut filenames: Vec<String> = vec![];
140 let mut file_results: Vec<Result<File, std::io::Error>> = vec![];
141 let mut pipeline_input: Vec<parser::InputFile> = vec![];
142
143 if file_paths.is_empty() {
144 warn!("No DC files given! Using empty DC file.");
145 return read_dc(config, String::default());
146 }
147
148 for file_path in &file_paths {
149 // Get filename from given path
150 match Path::new(file_path).file_name() {
151 Some(filename_osstr) => {
152 // Convert OsStr to String and store filename
153 filenames.push(filename_osstr.to_string_lossy().into_owned());
154 }
155 None => {
156 // std::path::Path.file_name() **only** returns `None`
157 // if the path terminates in '..'.
158 let filename_err: Error = Error::new(
159 ErrorKind::InvalidInput,
160 "Failed to get filename from path because\
161 path terminates in '..'.",
162 );
163 return Err(DCReadError::IO(filename_err));
164 }
165 }
166
167 // Open file using path and store result
168 file_results.push(File::open(file_path));
169 }
170
171 for (index, io_result) in file_results.into_iter().enumerate() {
172 if let Ok(mut dcf) = io_result {
173 // Prepare `InputFile` tuple for the pipeline function.
174 let filename: String = filenames.get(index).unwrap().to_owned();
175 let mut in_file: InputFile = (filename, String::default());
176
177 let res: std::io::Result<usize> = dcf.read_to_string(&mut in_file.1);
178
179 if let Err(res_err) = res {
180 // DC file content may not be in proper UTF-8 encoding.
181 return Err(DCReadError::IO(res_err));
182 }
183 pipeline_input.push(in_file);
184 } else {
185 // Failed to open one of the DC files. (most likely permission error)
186 return Err(DCReadError::IO(io_result.unwrap_err()));
187 }
188 }
189
190 parser::dcparse_pipeline(config, pipeline_input)
191}
192
193/// Front end to the donet-core DC parser pipeline.
194///
195/// ## Example Usage
196/// The following is an example of parsing a simple DC file string,
197/// printing its DC hash in hexadecimal notation, and accessing
198/// the elements of a defined Distributed Class:
199/// ```rust
200/// use donet_core::dcfile::DCFile;
201/// use donet_core::dclass::DClass;
202/// use donet_core::dconfig::*;
203/// use donet_core::read_dc;
204///
205/// let dc_file = "
206///
207/// from game.ai import AnonymousContact/UD
208/// from game.ai import LoginManager/AI
209/// from game.world import DistributedWorld/AI
210/// from game.avatar import DistributedAvatar/AI/OV
211///
212/// typedef uint32 doId;
213/// typedef uint32 zoneId;
214/// typedef uint64 channel;
215///
216/// dclass AnonymousContact {
217/// login(string username, string password) clsend airecv;
218/// };
219///
220/// dclass LoginManager {
221/// login(channel client, string username, string password) airecv;
222/// };
223///
224/// dclass DistributedWorld {
225/// create_avatar(channel client) airecv;
226/// };
227///
228/// dclass DistributedAvatar {
229/// set_xyzh(int16 x, int16 y, int16 z, int16 h) broadcast required;
230/// indicate_intent(int16 / 10, int16 / 10) ownsend airecv;
231/// };
232///
233/// ";
234///
235/// let dc_conf = DCFileConfig::default();
236/// let dc_read = read_dc(dc_conf, dc_file.into());
237///
238/// if let Ok(dc_file) = dc_read {
239/// // Print the DC File's 32-bit hash in hexadecimal format.
240/// println!("{}", dc_file.get_pretty_hash());
241///
242/// // TODO: Retrieve the `DistributedAvatar` dclass by ID.
243/// //let class: &DClass = dc_file.get_dclass_by_id(3);
244///
245/// // TODO: Print the identifier of the dclass.
246/// //println!("{}", class.get_name());
247/// }
248/// ```
249///
250/// The output of the program would be the following:
251/// ```txt
252/// 0x9c737148
253/// DistributedAvatar
254/// ```
255/// <br><img src="https://c.tenor.com/myQHgyWQQ9sAAAAd/tenor.gif">
256///
257#[cfg(feature = "dcfile")]
258pub fn read_dc<'a>(config: dconfig::DCFileConfig, input: String) -> Result<DCFile<'a>, DCReadError> {
259 let dcparse_input: Vec<parser::InputFile> = vec![("input.dc".to_string(), input)];
260
261 parser::dcparse_pipeline(config, dcparse_input)
262}