aboutsummaryrefslogtreecommitdiff
path: root/internal/contentenc/offsets.go
blob: e331d554bc1fa212d6c5b4482f5105368905f8b0 (plain)
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
package contentenc

import (
	"github.com/rfjakob/gocryptfs/internal/tlog"
)

// Contentenc methods that translate offsets between ciphertext and plaintext

// get the block number at plain-text offset
func (be *ContentEnc) PlainOffToBlockNo(plainOffset uint64) uint64 {
	return plainOffset / be.plainBS
}

// get the block number at cipher-text offset
func (be *ContentEnc) CipherOffToBlockNo(cipherOffset uint64) uint64 {
	if cipherOffset < HEADER_LEN {
		panic("BUG: offset is inside the file header")
	}
	return (cipherOffset - HEADER_LEN) / be.cipherBS
}

// get ciphertext offset of block "blockNo"
func (be *ContentEnc) BlockNoToCipherOff(blockNo uint64) uint64 {
	return HEADER_LEN + blockNo*be.cipherBS
}

// get plaintext offset of block "blockNo"
func (be *ContentEnc) BlockNoToPlainOff(blockNo uint64) uint64 {
	return blockNo * be.plainBS
}

// PlainSize - calculate plaintext size from ciphertext size
func (be *ContentEnc) CipherSizeToPlainSize(cipherSize uint64) uint64 {
	// Zero-sized files stay zero-sized
	if cipherSize == 0 {
		return 0
	}

	if cipherSize == HEADER_LEN {
		tlog.Warn.Printf("cipherSize %d == header size: interrupted write?\n", cipherSize)
		return 0
	}

	if cipherSize < HEADER_LEN {
		tlog.Warn.Printf("cipherSize %d < header size %d: corrupt file\n", cipherSize, HEADER_LEN)
		return 0
	}

	// Block number at last byte
	blockNo := be.CipherOffToBlockNo(cipherSize - 1)
	blockCount := blockNo + 1

	overhead := be.BlockOverhead()*blockCount + HEADER_LEN

	return cipherSize - overhead
}

// CipherSize - calculate ciphertext size from plaintext size
func (be *ContentEnc) PlainSizeToCipherSize(plainSize uint64) uint64 {
	// Zero-sized files stay zero-sized
	if plainSize == 0 {
		return 0
	}

	// Block number at last byte
	blockNo := be.PlainOffToBlockNo(plainSize - 1)
	blockCount := blockNo + 1

	overhead := be.BlockOverhead()*blockCount + HEADER_LEN

	return plainSize + overhead
}

// Split a plaintext byte range into (possibly partial) blocks
// Returns an empty slice if length == 0.
func (be *ContentEnc) ExplodePlainRange(offset uint64, length uint64) []intraBlock {
	var blocks []intraBlock
	var nextBlock intraBlock
	nextBlock.fs = be

	for length > 0 {
		nextBlock.BlockNo = be.PlainOffToBlockNo(offset)
		nextBlock.Skip = offset - be.BlockNoToPlainOff(nextBlock.BlockNo)

		// Minimum of remaining plaintext data and remaining space in the block
		nextBlock.Length = MinUint64(length, be.plainBS-nextBlock.Skip)

		blocks = append(blocks, nextBlock)
		offset += nextBlock.Length
		length -= nextBlock.Length
	}
	return blocks
}

// Split a ciphertext byte range into (possibly partial) blocks
// This is used in reverse mode when reading files
func (be *ContentEnc) ExplodeCipherRange(offset uint64, length uint64) []intraBlock {
	var blocks []intraBlock
	var nextBlock intraBlock
	nextBlock.fs = be

	for length > 0 {
		nextBlock.BlockNo = be.CipherOffToBlockNo(offset)
		nextBlock.Skip = offset - be.BlockNoToCipherOff(nextBlock.BlockNo)

		// This block can carry up to "maxLen" payload bytes
		maxLen := be.cipherBS - nextBlock.Skip
		nextBlock.Length = maxLen
		// But if the user requested less, we truncate the block to "length".
		if length < maxLen {
			nextBlock.Length = length
		}

		blocks = append(blocks, nextBlock)
		offset += nextBlock.Length
		length -= nextBlock.Length
	}
	return blocks
}

func (be *ContentEnc) BlockOverhead() uint64 {
	return be.cipherBS - be.plainBS
}

func MinUint64(x uint64, y uint64) uint64 {
	if x < y {
		return x
	}
	return y
}