-
Notifications
You must be signed in to change notification settings - Fork 9
/
record.go
134 lines (106 loc) · 3.64 KB
/
record.go
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
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package sunrpc
import (
"bytes"
"encoding/binary"
"io"
)
/*
From RFC 5531 (https://tools.ietf.org/html/rfc5531)
A record is composed of one or more record fragments. A record
fragment is a four-byte header followed by 0 to (2**31) - 1 bytes of
fragment data. The bytes encode an unsigned binary number; as with
XDR integers, the byte order is from highest to lowest. The number
encodes two values -- a boolean that indicates whether the fragment
is the last fragment of the record (bit value 1 implies the fragment
is the last fragment) and a 31-bit unsigned binary value that is the
length in bytes of the fragment's data. The boolean value is the
highest-order bit of the header; the length is the 31 low-order bits.
*/
const (
// This is maximum size in bytes for an individual record fragment.
// The entire RPC message (record) has no size restriction imposed
// by RFC 5531. Refer: include/linux/sunrpc/msg_prot.h
maxRecordFragmentSize = (1 << 31) - 1
// Max size of RPC message that a client is allowed to send.
maxRecordSize = 1 * 1024 * 1024
)
func isLastFragment(fragmentHeader uint32) bool {
return (fragmentHeader >> 31) == 1
}
func getFragmentSize(fragmentHeader uint32) uint32 {
return fragmentHeader &^ (1 << 31)
}
func createFragmentHeader(size uint32, lastFragment bool) uint32 {
fragmentHeader := size &^ (1 << 31)
if lastFragment {
fragmentHeader |= (1 << 31)
}
return fragmentHeader
}
func minOf(a, b int64) int64 {
if a < b {
return a
}
return b
}
// WriteFullRecord writes the fully formed RPC message reply to network
// by breaking it into one or more record fragments.
func WriteFullRecord(conn io.Writer, data []byte) (int64, error) {
dataSize := int64(len(data))
var totalBytesWritten int64
var lastFragment bool
fragmentHeaderBytes := make([]byte, 4)
for {
remainingBytes := dataSize - totalBytesWritten
if remainingBytes <= maxRecordFragmentSize {
lastFragment = true
}
fragmentSize := uint32(minOf(maxRecordFragmentSize, remainingBytes))
// Create fragment header
binary.BigEndian.PutUint32(fragmentHeaderBytes, createFragmentHeader(fragmentSize, lastFragment))
// Write fragment header and fragment body to network
bytesWritten, err := conn.Write(append(fragmentHeaderBytes, data[totalBytesWritten:fragmentSize]...))
if err != nil {
return int64(totalBytesWritten), err
}
totalBytesWritten += int64(bytesWritten)
if lastFragment {
break
}
}
return totalBytesWritten, nil
}
// ReadFullRecord reads the entire RPC message from network and returns a
// a []byte sequence which contains the record.
func ReadFullRecord(conn io.Reader) ([]byte, error) {
// In almost all cases, RPC message contain only one fragment which
// is not too big in size.
record := new(bytes.Buffer)
var fragmentHeader uint32
for {
// Read record fragment header
err := binary.Read(conn, binary.BigEndian, &fragmentHeader)
if err != nil {
return nil, err
}
fragmentSize := getFragmentSize(fragmentHeader)
if fragmentSize > maxRecordFragmentSize {
return nil, ErrInvalidFragmentSize
}
if int(fragmentSize) > (maxRecordSize - record.Len()) {
return nil, ErrRPCMessageSizeExceeded
}
// Copy fragment body (data) from network to buffer
bytesCopied, err := io.CopyN(record, conn, int64(fragmentSize))
if err != nil || (bytesCopied != int64(fragmentSize)) {
return nil, err
}
if isLastFragment(fragmentHeader) {
break
}
}
return record.Bytes(), nil
}