diff --git a/rustcryptfs-lib/src/filename/filename_encoded.rs b/rustcryptfs-lib/src/filename/filename_encoded.rs
new file mode 100644
index 0000000..2edc65e
--- /dev/null
+++ b/rustcryptfs-lib/src/filename/filename_encoded.rs
@@ -0,0 +1,76 @@
+use std::path::Path;
+
+/// EncodedFilename
+#[derive(Debug, PartialEq)]
+pub enum EncodedFilename {
+ ShortFilename(String),
+ LongFilename(LongFilename),
+}
+
+impl EncodedFilename {
+ fn new
(file: P) -> crate::error::Result
+ where
+ P: AsRef,
+ {
+ let path = file.as_ref();
+
+ let filename = path.file_name().unwrap().to_str().expect("Failed to get filename");
+
+ if filename.starts_with("gocryptfs.longname.") {
+ if !filename.ends_with(".name") {
+ let long = std::fs::read_to_string(
+ path.parent().unwrap().join(format!("{}.name", filename)),
+ ).unwrap();
+ Ok(EncodedFilename::LongFilename(LongFilename {
+ filename: filename.to_string(),
+ filename_content: long,
+ }))
+ } else {
+ panic!()
+ }
+ } else {
+ Ok(EncodedFilename::ShortFilename(filename.to_string()))
+ }
+ }
+}
+
+#[derive(Debug, PartialEq)]
+pub struct LongFilename {
+ pub filename: String,
+ pub filename_content: String,
+}
+
+impl From for EncodedFilename {
+ fn from(filename: String) -> Self {
+ if filename.len() > 255 {
+ unimplemented!()
+ } else {
+ Self::ShortFilename(filename)
+ }
+ }
+}
+
+pub trait IntoDecodable {
+ fn to_decodable<'s>(&'s self) -> &'s str;
+}
+
+impl IntoDecodable for EncodedFilename {
+ fn to_decodable<'s>(&'s self) -> &'s str {
+ match self {
+ Self::ShortFilename(s) => s.as_str(),
+ Self::LongFilename(l) => l.filename_content.as_str(),
+ }
+ }
+}
+
+impl IntoDecodable for String {
+ fn to_decodable<'s>(&'s self) -> &'s str {
+ self
+ }
+}
+
+impl<'a> IntoDecodable for &'a str {
+ fn to_decodable<'s>(&'s self) -> &'s str {
+ self
+ }
+}
diff --git a/rustcryptfs-lib/src/filename/mod.rs b/rustcryptfs-lib/src/filename/mod.rs
new file mode 100644
index 0000000..3492a13
--- /dev/null
+++ b/rustcryptfs-lib/src/filename/mod.rs
@@ -0,0 +1,119 @@
+use aes::Aes256;
+use cipher::{block_padding::Pkcs7, inout::InOutBufReserved, Iv, Key, KeyIvInit};
+use eme_mode::DynamicEme;
+use hkdf::Hkdf;
+
+use crate::error::FilenameDecryptError;
+
+pub(crate) type EmeCipher = DynamicEme;
+
+mod filename_encoded;
+
+pub use filename_encoded::*;
+
+// TODO RENAME
+pub struct FilenameDecoder {
+ filename_key: Key,
+}
+
+impl FilenameDecoder {
+ pub fn new(master_key: &[u8]) -> Result {
+ let mut key = [0u8; 32];
+ let hdkf = Hkdf::::new(None, &master_key);
+ hdkf.expand(b"EME filename encryption", &mut key)?;
+
+ Ok(Self {
+ filename_key: Key::::from(key),
+ })
+ }
+
+ pub fn get_decoder_for_dir<'a, 'b>(&'a self, iv: &'b [u8]) -> DirFilenameDecoder<'a, 'b> {
+ let iv = Iv::::from_slice(iv);
+ DirFilenameDecoder {
+ filename_key: &self.filename_key,
+ iv,
+ }
+ }
+}
+
+// TODO RENAME
+pub struct DirFilenameDecoder<'a, 'b> {
+ filename_key: &'a Key,
+ iv: &'b Iv,
+}
+
+impl<'a, 'b> DirFilenameDecoder<'a, 'b> {
+ pub fn decode_filename(&self, name: S) -> Result
+ where
+ S: IntoDecodable,
+ {
+ let cipher = EmeCipher::new(self.filename_key, self.iv);
+
+ let mut filename = base64::decode_config(name.to_decodable(), base64::URL_SAFE_NO_PAD)?;
+ let filename_decoded = cipher
+ .decrypt_padded_mut::(&mut filename)
+ .map_err(|_| FilenameDecryptError::DecryptError())?;
+
+ Ok(String::from_utf8_lossy(filename_decoded).to_string())
+ }
+
+ pub fn encrypt_filename(
+ &self,
+ plain_text_name: &str,
+ ) -> Result {
+ let mut cipher = EmeCipher::new(self.filename_key, self.iv);
+ let mut res = [0u8; 2048];
+
+ let filename_encrypted = cipher
+ .encrypt_padded_inout_mut::(
+ InOutBufReserved::from_slices(plain_text_name.as_bytes(), &mut res).unwrap(),
+ )
+ .map_err(|_| FilenameDecryptError::DecryptError())?; // TODO RENAME ERROR
+
+ // TODO LONG FILENAME
+
+ let filename = base64::encode_config(filename_encrypted, base64::URL_SAFE_NO_PAD);
+
+ Ok(filename.into())
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use crate::filename::EncodedFilename;
+
+ use super::FilenameDecoder;
+
+ #[test]
+ fn test_encrypt() {
+ let master_key = base64::decode("9gtUW9XiiefEgEXEkbONI6rnUsd2yh5UZZLG0V8Bxgk=").unwrap();
+ let dir_iv = base64::decode("6ysCeWOp2euF1x39gth8KQ==").unwrap();
+
+ let decoder = FilenameDecoder::new(&master_key).expect("Failed to get file decoder");
+ let dir_decoder = decoder.get_decoder_for_dir(&dir_iv);
+
+ let encoded = dir_decoder
+ .encrypt_filename("7.mp4")
+ .expect("Failed to encrypt filename");
+
+ assert_eq!(
+ encoded,
+ EncodedFilename::ShortFilename("vTBajRt-yCpxB7Sly0E7lQ".into())
+ );
+ }
+
+ #[test]
+ fn test_decrypt() {
+ let master_key = base64::decode("9gtUW9XiiefEgEXEkbONI6rnUsd2yh5UZZLG0V8Bxgk=").unwrap();
+ let dir_iv = base64::decode("6ysCeWOp2euF1x39gth8KQ==").unwrap();
+
+ let decoder = FilenameDecoder::new(&master_key).expect("Failed to get file decoder");
+ let dir_decoder = decoder.get_decoder_for_dir(&dir_iv);
+
+ let decrypted = dir_decoder
+ .decode_filename("vTBajRt-yCpxB7Sly0E7lQ")
+ .expect("Failed to decrypt filename");
+
+ assert_eq!(decrypted, "7.mp4");
+ }
+}
diff --git a/rustcryptfs-lib/src/io/mod.rs b/rustcryptfs-lib/src/io/mod.rs
new file mode 100644
index 0000000..5852394
--- /dev/null
+++ b/rustcryptfs-lib/src/io/mod.rs
@@ -0,0 +1,155 @@
+use std::{
+ error::Error,
+ fs::File,
+ io::Read,
+ path::{Path, PathBuf},
+};
+
+use crate::filename::{EncodedFilename, FilenameDecoder};
+
+#[derive(Debug)]
+pub struct DirCache {
+ filename: String,
+ dir_iv: [u8; 16],
+ dir_entries: Vec,
+}
+
+impl DirCache {
+ pub fn load_from_path(path: P) -> Self
+ where
+ P: AsRef,
+ {
+ let path = path.as_ref();
+
+ let mut dir_iv = [0u8; 16];
+ {
+ let dir_iv_path = path.join("gocryptfs.diriv");
+
+ let mut file = File::open(dir_iv_path).unwrap();
+
+ file.read_exact(&mut dir_iv).unwrap();
+ }
+
+ let dir_entries = path
+ .read_dir()
+ .unwrap()
+ .filter_map(|f| {
+ if let Ok(entry) = f {
+ if entry.file_name() != "gocryptfs.conf"
+ && entry.file_name() != "gocryptfs.diriv"
+ {
+ Some(entry)
+ } else {
+ None
+ }
+ } else {
+ None
+ }
+ })
+ .map(|f| DirEntry::try_from(f.path().as_path()))
+ .filter_map(|f| f.ok())
+ .collect();
+
+ Self {
+ filename: path.to_string_lossy().to_string(),
+ dir_iv,
+ dir_entries,
+ }
+ }
+
+ pub fn lookup(
+ &self,
+ filename_decoder: &FilenameDecoder,
+ decrypted_path: P,
+ ) -> Option
+ where
+ P: AsRef,
+ {
+ let decrypted_path = decrypted_path.as_ref();
+
+ let mut components = decrypted_path.components();
+
+ let component = components.next().expect("lol");
+
+ let decoder = filename_decoder.get_decoder_for_dir(&self.dir_iv);
+
+ let segment = decoder
+ .encrypt_filename(component.as_os_str().to_str().unwrap())
+ .expect("lol");
+
+ let segment_path = match segment {
+ EncodedFilename::ShortFilename(filename) => PathBuf::from(filename),
+ EncodedFilename::LongFilename(long_filename) => PathBuf::from(long_filename.filename),
+ };
+
+ if segment_path.is_dir() {
+ let (size, _) = components.size_hint();
+
+ if size > 0 {
+ unimplemented!()
+ } else {
+ unimplemented!()
+ //None
+ }
+ } else {
+ // component.as_path()
+ unimplemented!()
+ };
+
+ }
+
+ fn lookup_internal(
+ &self,
+ filename_decoder: &FilenameDecoder,
+ decrypted_path: P,
+ dir: &DirCache,
+ ) -> Option
+ where
+ P: AsRef,
+ {
+ unimplemented!()
+ }
+}
+
+#[derive(Debug)]
+pub enum DirEntry {
+ Dir(DirCache),
+ File(String),
+}
+
+impl DirEntry {
+ /// Returns `true` if the dir entry is [`Dir`].
+ ///
+ /// [`Dir`]: DirEntry::Dir
+ #[must_use]
+ pub fn is_dir(&self) -> bool {
+ matches!(self, Self::Dir(..))
+ }
+
+ /// Returns `true` if the dir entry is [`File`].
+ ///
+ /// [`File`]: DirEntry::File
+ #[must_use]
+ pub fn is_file(&self) -> bool {
+ matches!(self, Self::File(..))
+ }
+}
+
+impl TryFrom<&Path> for DirEntry {
+ type Error = Box; // TODO
+
+ fn try_from(path: &Path) -> Result {
+ Ok(if path.is_dir() {
+ DirEntry::Dir(DirCache::load_from_path(path))
+ } else {
+ DirEntry::File(
+ path.components()
+ .last()
+ .unwrap()
+ .as_os_str()
+ .to_string_lossy()
+ .to_string(),
+ )
+ })
+ }
+}