File: fragmentation.go

package info (click to toggle)
golang-github-twstrike-otr3 0.0~git20161015.0.744856d-3
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 1,080 kB
  • sloc: ansic: 127; makefile: 76
file content (134 lines) | stat: -rw-r--r-- 3,879 bytes parent folder | download | duplicates (3)
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
package otr3

import "bytes"

var (
	fragmentSeparator      = []byte{','}
	fragmentItagsSeparator = []byte{'|'}
)

// fragmentationContext store the current fragmentation running. A fragmentationContext is zero-valid and can be immediately used without initialization.
// In order to follow the fragmentation rules, when the context needs to be reset, just create a new one - don't bother resetting variables
type fragmentationContext struct {
	frag                     []byte
	currentIndex, currentLen uint16
}

func min(l, r uint16) uint16 {
	if l < r {
		return l
	}
	return r
}

func fragmentStart(i, fraglen uint16) uint16 {
	return uint16(i * fraglen)
}

func fragmentEnd(i, fraglen, l uint16) uint16 {
	return uint16(min((i+1)*fraglen, l))
}

func fragmentData(data []byte, i int, fraglen, l uint16) []byte {
	return data[fragmentStart(uint16(i), fraglen):fragmentEnd(uint16(i), fraglen, l)]
}

// SetFragmentSize sets the maximum size for a message fragment.
// If specified, all messages produced by Receive and Send
// will be fragmented into messages of, at most, this number of bytes.
func (c *Conversation) SetFragmentSize(size uint16) {
	c.fragmentSize = size
}

func (c *Conversation) fragment(data encodedMessage, fraglen uint16) []ValidMessage {
	l := len(data)

	if l <= int(fraglen) || fraglen == 0 {
		return []ValidMessage{ValidMessage(data)}
	}

	fakeHeader := c.version.fragmentPrefix(1, 1, c.ourInstanceTag, c.theirInstanceTag)
	realFraglen := (fraglen - uint16(len(fakeHeader))) - 1

	if realFraglen <= 0 {
		return []ValidMessage{ValidMessage(data)}
	}

	numFragments := (l / int(realFraglen)) + 1
	ret := make([]ValidMessage, numFragments)
	for i := 0; i < numFragments; i++ {
		prefix := c.version.fragmentPrefix(i, numFragments, c.ourInstanceTag, c.theirInstanceTag)
		ret[i] = append(append(prefix, fragmentData(data, i, realFraglen, uint16(l))...), fragmentSeparator[0])
	}
	return ret
}

func fragmentsFinished(fctx fragmentationContext) bool {
	return fctx.currentIndex > 0 && fctx.currentIndex == fctx.currentLen
}

func parseFragment(data []byte) (resultData []byte, ix uint16, length uint16, ok bool) {
	parts := bytes.Split(data, fragmentSeparator)
	if len(parts) != 4 {
		return nil, 0, 0, false
	}
	var e1, e2 error
	ix, e1 = bytesToUint16(parts[0])
	length, e2 = bytesToUint16(parts[1])
	resultData = parts[2]
	ok = e1 == nil && e2 == nil
	return
}

func fragmentIsInvalid(ix, l uint16) bool {
	return ix == 0 || l == 0 || ix > l
}

func fragmentIsFirstMessage(ix, l uint16) bool {
	return ix == 1
}

func fragmentIsNextMessage(beforeCtx fragmentationContext, ix, l uint16) bool {
	return beforeCtx.currentIndex+1 == ix && beforeCtx.currentLen == l
}

func (ctx fragmentationContext) discardFragment() fragmentationContext {
	return ctx
}

func (ctx fragmentationContext) appendFragment(data []byte, ix, l uint16) fragmentationContext {
	return fragmentationContext{append(ctx.frag, data...), ix, l}
}

func restartFragment(data []byte, ix, l uint16) fragmentationContext {
	return fragmentationContext{makeCopy(data), ix, l}
}

func forgetFragment() fragmentationContext {
	return fragmentationContext{}
}

func (c *Conversation) receiveFragment(beforeCtx fragmentationContext, data ValidMessage) (fragmentationContext, error) {
	fragBody, ignore, ok1 := c.parseFragmentPrefix(data)
	resultData, ix, l, ok2 := parseFragment(fragBody)

	if ignore {
		c.messageEvent(MessageEventReceivedMessageForOtherInstance)
		return beforeCtx, nil
	}

	if !ok1 || !ok2 {
		return beforeCtx, newOtrError("invalid OTR fragment")
	}

	switch {
	case fragmentIsInvalid(ix, l):
		return beforeCtx.discardFragment(), nil
	case fragmentIsFirstMessage(ix, l):
		return restartFragment(resultData, ix, l), nil
	case fragmentIsNextMessage(beforeCtx, ix, l):
		return beforeCtx.appendFragment(resultData, ix, l), nil
	default:
		return forgetFragment(), nil
	}
}