1// Copyright 2017 The Fuchsia Authors
2//
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file or at
5// https://opensource.org/licenses/MIT
6
7#include <object/mbuf.h>
8
9#include <lib/user_copy/user_ptr.h>
10
11#include <fbl/algorithm.h>
12#include <fbl/alloc_checker.h>
13
14#define LOCAL_TRACE 0
15
16constexpr size_t MBufChain::MBuf::kHeaderSize;
17constexpr size_t MBufChain::MBuf::kMallocSize;
18constexpr size_t MBufChain::MBuf::kPayloadSize;
19constexpr size_t MBufChain::kSizeMax;
20
21size_t MBufChain::MBuf::rem() const {
22    return kPayloadSize - (off_ + len_);
23}
24
25MBufChain::~MBufChain() {
26    while (!tail_.is_empty())
27        delete tail_.pop_front();
28    while (!freelist_.is_empty())
29        delete freelist_.pop_front();
30}
31
32bool MBufChain::is_full() const {
33    return size_ >= kSizeMax;
34}
35
36bool MBufChain::is_empty() const {
37    return size_ == 0;
38}
39
40size_t MBufChain::Read(user_out_ptr<void> dst, size_t len, bool datagram) {
41    if (size_ == 0) {
42        return 0;
43    }
44
45    if (datagram && len > tail_.front().pkt_len_)
46        len = tail_.front().pkt_len_;
47
48    size_t pos = 0;
49    while (pos < len && !tail_.is_empty()) {
50        MBuf& cur = tail_.front();
51        char* src = cur.data_ + cur.off_;
52        size_t copy_len = MIN(cur.len_, len - pos);
53        if (dst.byte_offset(pos).copy_array_to_user(src, copy_len) != ZX_OK)
54            return pos;
55        pos += copy_len;
56        cur.off_ += static_cast<uint32_t>(copy_len);
57        cur.len_ -= static_cast<uint32_t>(copy_len);
58        size_ -= copy_len;
59        if (cur.len_ == 0 || datagram) {
60            size_ -= cur.len_;
61            if (head_ == &cur)
62                head_ = nullptr;
63            FreeMBuf(tail_.pop_front());
64        }
65    }
66    if (datagram) {
67        // Drain any leftover mbufs in the datagram packet.
68        while (!tail_.is_empty() && tail_.front().pkt_len_ == 0) {
69            MBuf* cur = tail_.pop_front();
70            size_ -= cur->len_;
71            if (head_ == cur)
72                head_ = nullptr;
73            FreeMBuf(cur);
74        }
75    }
76    return pos;
77}
78
79zx_status_t MBufChain::WriteDatagram(user_in_ptr<const void> src, size_t len,
80                                     size_t* written) {
81    if (len == 0) {
82        return ZX_ERR_INVALID_ARGS;
83    }
84    if (len > kSizeMax)
85        return ZX_ERR_OUT_OF_RANGE;
86    if (len + size_ > kSizeMax)
87        return ZX_ERR_SHOULD_WAIT;
88
89    fbl::SinglyLinkedList<MBuf*> bufs;
90    for (size_t need = 1 + ((len - 1) / MBuf::kPayloadSize); need != 0; need--) {
91        auto buf = AllocMBuf();
92        if (buf == nullptr) {
93            while (!bufs.is_empty())
94                FreeMBuf(bufs.pop_front());
95            return ZX_ERR_SHOULD_WAIT;
96        }
97        bufs.push_front(buf);
98    }
99
100    size_t pos = 0;
101    for (auto& buf : bufs) {
102        size_t copy_len = fbl::min(MBuf::kPayloadSize, len - pos);
103        if (src.byte_offset(pos).copy_array_from_user(buf.data_, copy_len) != ZX_OK) {
104            while (!bufs.is_empty())
105                FreeMBuf(bufs.pop_front());
106            return ZX_ERR_INVALID_ARGS; // Bad user buffer.
107        }
108        pos += copy_len;
109        buf.len_ += static_cast<uint32_t>(copy_len);
110    }
111
112    bufs.front().pkt_len_ = static_cast<uint32_t>(len);
113
114    // Successfully built the packet mbufs. Put it on the socket.
115    while (!bufs.is_empty()) {
116        auto next = bufs.pop_front();
117        if (head_ == nullptr) {
118            tail_.push_front(next);
119        } else {
120            tail_.insert_after(tail_.make_iterator(*head_), next);
121        }
122        head_ = next;
123    }
124
125    *written = len;
126    size_ += len;
127    return ZX_OK;
128}
129
130zx_status_t MBufChain::WriteStream(user_in_ptr<const void> src, size_t len, size_t* written) {
131    if (head_ == nullptr) {
132        head_ = AllocMBuf();
133        if (head_ == nullptr)
134            return ZX_ERR_SHOULD_WAIT;
135        tail_.push_front(head_);
136    }
137
138    size_t pos = 0;
139    while (pos < len) {
140        if (head_->rem() == 0) {
141            auto next = AllocMBuf();
142            if (next == nullptr)
143                break;
144            tail_.insert_after(tail_.make_iterator(*head_), next);
145            head_ = next;
146        }
147        void* dst = head_->data_ + head_->off_ + head_->len_;
148        size_t copy_len = fbl::min(head_->rem(), len - pos);
149        if (size_ + copy_len > kSizeMax) {
150            copy_len = kSizeMax - size_;
151            if (copy_len == 0)
152                break;
153        }
154        if (src.byte_offset(pos).copy_array_from_user(dst, copy_len) != ZX_OK)
155            break;
156        pos += copy_len;
157        head_->len_ += static_cast<uint32_t>(copy_len);
158        size_ += copy_len;
159    }
160
161    if (pos == 0)
162        return ZX_ERR_SHOULD_WAIT;
163
164    *written = pos;
165    return ZX_OK;
166}
167
168MBufChain::MBuf* MBufChain::AllocMBuf() {
169    if (freelist_.is_empty()) {
170        fbl::AllocChecker ac;
171        MBuf* buf = new (&ac) MBuf();
172        return (!ac.check()) ? nullptr : buf;
173    }
174    return freelist_.pop_front();
175}
176
177void MBufChain::FreeMBuf(MBuf* buf) {
178    buf->off_ = 0u;
179    buf->len_ = 0u;
180    freelist_.push_front(buf);
181}
182