1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
//! Lump関連のデータ構造群.
//!
//! "lump"とは、`cannyls`におけるデータの格納単位.
//! 各lumpは「128bit幅のID」と「最大30MB程度のデータ(任意のバイト列)」から構成される.
//!
//! `cannyls`のレイヤでは、保存されているlumpの整合性の保証や検証は行わないため、
//! 必要であれば、利用側で冗長化やチェックサム検証等を施す必要がある.
use std::cmp;
use std::fmt;
use std::str::FromStr;
use std::u128;
use trackable::error::ErrorKindExt;

use block::BlockSize;
use storage::DataRegionLumpData;
use {Error, ErrorKind, Result};

/// Lumpの識別子(128bit幅).
#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct LumpId(u128);
impl LumpId {
    /// 識別子のバイト幅.
    pub const SIZE: usize = 16;

    /// 新しい`LumpId`インスタンスを生成する.
    ///
    /// # Examples
    ///
    /// ```
    /// use cannyls::lump::LumpId;
    ///
    /// assert_eq!(LumpId::new(0x12_3456).to_string(), "00000000000000000000000000123456");
    ///
    /// // 16進数文字列からも生成可能
    /// assert_eq!("123456".parse::<LumpId>().unwrap(), LumpId::new(0x12_3456));
    /// ```
    pub fn new(id: u128) -> Self {
        LumpId(id)
    }

    /// 識別子の値(128bit整数)を返す.
    pub fn as_u128(&self) -> u128 {
        self.0
    }
}
impl FromStr for LumpId {
    type Err = Error;

    /// 16進数表記の数値から`LumpId`を生成する.
    ///
    /// 数値の"128bit整数"として扱われ、先頭のゼロは省略可能(`"ab12"`と`"00ab12"`は等価).
    ///
    /// # Errors
    ///
    /// 以下のいずれかの場合には、種類が`ErrorKind::InvalidInput`のエラーが返される:
    ///
    /// - 文字列が16進数表記の整数値を表していない
    /// - 文字列の長さが32文字を超えている
    ///
    /// # Examples
    ///
    /// ```
    /// use std::str::{self, FromStr};
    /// use cannyls::ErrorKind;
    /// use cannyls::lump::LumpId;
    ///
    /// assert_eq!(LumpId::from_str("00ab12").ok(),
    ///            Some(LumpId::new(0xab12)));
    ///
    /// assert_eq!(LumpId::from_str("foo_bar").err().map(|e| *e.kind()),
    ///            Some(ErrorKind::InvalidInput));
    ///
    /// let large_input = str::from_utf8(&[b'a', 33][..]).unwrap();
    /// assert_eq!(LumpId::from_str(large_input).err().map(|e| *e.kind()),
    ///            Some(ErrorKind::InvalidInput));
    /// ```
    fn from_str(s: &str) -> Result<Self> {
        let id = track!(u128::from_str_radix(s, 16).map_err(|e| ErrorKind::InvalidInput.cause(e)))?;
        Ok(LumpId::new(id))
    }
}
impl fmt::Debug for LumpId {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, r#"LumpId("{}")"#, self)
    }
}
impl fmt::Display for LumpId {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for i in (0..Self::SIZE).rev() {
            let b = (self.0 >> (8 * i)) as u8;
            write!(f, "{:02x}", b)?;
        }
        Ok(())
    }
}

/// Lumpのデータ.
///
/// 最大で`MAX_SIZE`までのバイト列を保持可能.
#[derive(Clone)]
pub struct LumpData(LumpDataInner);
impl LumpData {
    /// データの最大長(バイト単位).
    ///
    /// 最小ブロックサイズを用いた場合に表現可能な最大サイズまでのデータが保持可能.
    /// 最後の`-2`は、内部的に付与されるメタ情報のサイズ分.
    ///
    /// # 蛇足
    ///
    /// 現状は簡単のために、最小のブロックサイズに合わせた最大サイズ、となっている。
    ///
    /// ただし、仕組み上は、ストレージが採用したブロックサイズがそれよりも大きい場合には、
    /// 保存可能なデータサイズを比例して大きくすることは可能.
    ///
    /// 一つ一つのlumpのサイズをあまり巨大にしてしまうと、
    /// 一つのlumpの読み書き処理が全体のレイテンシを阻害してしまう可能性もあるので、
    /// 現状くらいの制限でちょうど良いのではないかとも思うが、
    /// もし最大サイズをどうしても上げたい場合には、それも不可能ではない、
    /// ということは記しておく.
    pub const MAX_SIZE: usize = 0xFFFF * (BlockSize::MIN as usize) - 2;

    /// ジャーナル領域に埋め込み可能なデータの最大長(バイト単位).
    pub const MAX_EMBEDDED_SIZE: usize = 0xFFFF;

    /// 引数で指定されたデータを保持する`LumpData`インスタンスを生成する.
    ///
    /// # 性能上の注意
    ///
    /// この関数で生成された`LumpData`インスタンスは、保存先ストレージのブロック境界に合わせた
    /// アライメントが行われていないため、PUTによる保存時に必ず一度、
    /// アライメント用にデータサイズ分のメモリコピーが発生してしまう.
    ///
    /// 保存時のコピーを避けたい場合には[`Storage`]ないし[`DeviceHandle`]が提供している
    /// `allocate_lump_data_with_bytes`メソッドを使用して、
    /// アライメント済みの`LumpData`インスタンスを生成すると良い.
    ///
    /// [`Storage`]: ../storage/struct.Storage.html
    /// [`DeviceHandle`]: ../device/struct.DeviceHandle.html
    ///
    /// # Errors
    ///
    /// データのサイズが`MAX_SIZE`を超えている場合は、`ErrorKind::InvalidInput`エラーが返される.
    pub fn new(data: Vec<u8>) -> Result<Self> {
        track_assert!(
            data.len() <= LumpData::MAX_SIZE,
            ErrorKind::InvalidInput,
            "Too large lump data: {} bytes",
            data.len()
        );
        Ok(LumpData(LumpDataInner::DataRegionUnaligned(data)))
    }

    /// ジャーナル領域埋め込み用の`LumpData`インスタンスを生成する.
    ///
    /// # Errors
    ///
    /// `data`の長さが`MAX_EMBEDDED_SIZE`を超えている場合は、`ErrorKind::InvalidInput`エラーが返される.
    ///
    pub fn new_embedded(data: Vec<u8>) -> Result<Self> {
        track_assert!(
            data.len() <= LumpData::MAX_EMBEDDED_SIZE,
            ErrorKind::InvalidInput,
            "Too large embedded lump data: {} bytes",
            data.len()
        );
        Ok(LumpData(LumpDataInner::JournalRegion(data)))
    }

    /// データを表すバイト列への参照を返す.
    pub fn as_bytes(&self) -> &[u8] {
        self.as_ref()
    }

    /// データを表すバイト列への破壊的な参照を返す.
    pub fn as_bytes_mut(&mut self) -> &mut [u8] {
        self.as_mut()
    }

    /// 所有権を放棄して、内部のバイト列を返す.
    ///
    /// なお、このインスタンスが確保しているのがアライメントされたメモリ領域の場合には、
    /// それを通常の`Vec<u8>`に変換するためのメモリコピーが、本メソッド呼び出し時に発生することになる.
    pub fn into_bytes(self) -> Vec<u8> {
        match self.0 {
            LumpDataInner::JournalRegion(d) => d,
            LumpDataInner::DataRegion(d) => Vec::from(d.as_bytes()),
            LumpDataInner::DataRegionUnaligned(d) => d,
        }
    }

    /// アライメント済みの`LumpData`インスタンス用のメモリ領域を割り当てる.
    ///
    /// この関数によって割当てられたメモリ領域の初期値は未定義であり、
    /// 利用者は事前にゼロ埋めされていることを仮定してはいけない.
    ///
    /// # Errors
    ///
    /// 指定されたサイズが`MAX_SIZE`を超えている場合は、`ErrorKind::InvalidInput`エラーが返される.
    pub(crate) fn aligned_allocate(data_len: usize, block_size: BlockSize) -> Result<Self> {
        track_assert!(
            data_len <= LumpData::MAX_SIZE,
            ErrorKind::InvalidInput,
            "Too large lump data: {} bytes",
            data_len
        );
        Ok(LumpData::from(DataRegionLumpData::new(
            data_len, block_size,
        )))
    }

    pub(crate) fn as_inner(&self) -> &LumpDataInner {
        &self.0
    }
}
impl AsRef<[u8]> for LumpData {
    fn as_ref(&self) -> &[u8] {
        match self.0 {
            LumpDataInner::JournalRegion(ref d) => d,
            LumpDataInner::DataRegion(ref d) => d.as_bytes(),
            LumpDataInner::DataRegionUnaligned(ref d) => d,
        }
    }
}
impl AsMut<[u8]> for LumpData {
    fn as_mut(&mut self) -> &mut [u8] {
        match self.0 {
            LumpDataInner::JournalRegion(ref mut d) => d,
            LumpDataInner::DataRegion(ref mut d) => d.as_bytes_mut(),
            LumpDataInner::DataRegionUnaligned(ref mut d) => d,
        }
    }
}
impl fmt::Debug for LumpData {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let block_size = if let LumpDataInner::DataRegion(ref x) = self.0 {
            Some(x.block_size())
        } else {
            None
        };

        let len = cmp::min(128, self.as_bytes().len());
        let bytes = &self.as_bytes()[0..len];
        let omitted = if len < self.as_ref().len() {
            format!("({} bytes omitted)", self.as_ref().len() - len)
        } else {
            "".to_owned()
        };
        write!(
            f,
            "LumpData {{ block_size: {:?}, bytes: {:?}{} }}",
            block_size, bytes, omitted
        )
    }
}
impl PartialEq for LumpData {
    fn eq(&self, other: &Self) -> bool {
        self.as_ref() == other.as_ref()
    }
}
impl Eq for LumpData {}
impl From<DataRegionLumpData> for LumpData {
    fn from(f: DataRegionLumpData) -> Self {
        LumpData(LumpDataInner::DataRegion(f))
    }
}

#[derive(Clone)]
pub(crate) enum LumpDataInner {
    JournalRegion(Vec<u8>),
    DataRegion(DataRegionLumpData),
    DataRegionUnaligned(Vec<u8>),
}

/// Lumpの概要情報.
///
/// "ヘッダ"という用語は若干不正確だが、
/// `HEAD`リクエストの結果として取得可能なものなので、
/// このような名前にしている.
#[derive(Debug, Clone)]
pub struct LumpHeader {
    /// データサイズの近似値(バイト単位).
    ///
    /// 実際のデータサイズに管理用のメタ情報を追加した上で、
    /// 次のブロックサイズ境界への切り上げたサイズ、となるため、
    /// 最大でストレージのブロックサイズ二つ分だけ、
    /// 実際よりも大きな値となることがある.
    ///
    /// なお、対象lumpのデータがジャーナル領域に埋め込まれている場合には、
    /// 常に正確なサイズが返される.
    pub approximate_data_size: u32,
}