1 // Copyright 2021 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "nic_mock.h"
16
17 #include "platforms/nemora/portable/ncsi.h"
18 #include "platforms/nemora/portable/ncsi_server.h"
19
20 #include <algorithm>
21 #include <cstddef>
22 #include <cstring>
23 #include <stdexcept>
24
25 namespace mock
26 {
27
parse_ethernet_frame(const ncsi_buf_t & ncsi_buf)28 bool NCSIFrame::parse_ethernet_frame(const ncsi_buf_t& ncsi_buf)
29 {
30 std::memcpy(&dst_mac_, ncsi_buf.data, sizeof(dst_mac_));
31 std::memcpy(&src_mac_, ncsi_buf.data + sizeof(dst_mac_), sizeof(src_mac_));
32 // The constant defined in a way that assumes big-endian platform, so we are
33 // just going to calculate it here properly.
34 const uint8_t et_hi = *(ncsi_buf.data + 2 * sizeof(mac_addr_t));
35 const uint8_t et_lo = *(ncsi_buf.data + 2 * sizeof(mac_addr_t) + 1);
36 ethertype_ = (et_hi << 8) + et_lo;
37
38 if (ethertype_ != NCSI_ETHERTYPE)
39 {
40 return false;
41 }
42
43 // This code parses the NC-SI command, according to spec and
44 // as defined in platforms/nemora/portable/ncsi.h
45 // It takes some shortcuts to only retrieve the data we are interested in,
46 // such as using offsetof ot get to a particular field.
47 control_packet_type_ =
48 *(ncsi_buf.data + offsetof(ncsi_header_t, control_packet_type));
49 channel_id_ = *(ncsi_buf.data + offsetof(ncsi_header_t, channel_id));
50
51 size_t payload_offset = sizeof(ncsi_header_t);
52 if (control_packet_type_ & NCSI_RESPONSE)
53 {
54 is_response_ = true;
55 control_packet_type_ &= ~NCSI_RESPONSE;
56 std::memcpy(&response_code_, ncsi_buf.data + payload_offset,
57 sizeof(response_code_));
58 response_code_ = ntohs(response_code_);
59 std::memcpy(&reason_code_,
60 ncsi_buf.data + payload_offset + sizeof(reason_code_),
61 sizeof(reason_code_));
62 reason_code_ = ntohs(reason_code_);
63 payload_offset += sizeof(reason_code_) + sizeof(response_code_);
64 }
65
66 if (control_packet_type_ == NCSI_OEM_COMMAND)
67 {
68 std::memcpy(&manufacturer_id_, ncsi_buf.data + payload_offset,
69 sizeof(manufacturer_id_));
70 manufacturer_id_ = ntohl(manufacturer_id_);
71 // Number of reserved bytes after manufacturer_id_ = 3
72 oem_command_ =
73 *(ncsi_buf.data + payload_offset + sizeof(manufacturer_id_) + 3);
74 payload_offset += sizeof(ncsi_oem_extension_header_t);
75 }
76
77 packet_raw_ =
78 std::vector<uint8_t>(ncsi_buf.data, ncsi_buf.data + ncsi_buf.len);
79 // TODO: Verify payload length.
80
81 return true;
82 }
83
handle_request(const ncsi_buf_t & request_buf,ncsi_buf_t * response_buf)84 uint32_t NIC::handle_request(const ncsi_buf_t& request_buf,
85 ncsi_buf_t* response_buf)
86 {
87 const ncsi_header_t* ncsi_header =
88 reinterpret_cast<const ncsi_header_t*>(request_buf.data);
89
90 NCSIFrame request_frame;
91 request_frame.parse_ethernet_frame(request_buf);
92 save_frame_to_log(request_frame);
93
94 uint32_t response_size;
95 if (is_loopback_)
96 {
97 std::memcpy(response_buf, &request_buf, sizeof(request_buf));
98 response_size = request_buf.len;
99 }
100 else if (std::find(simple_commands_.begin(), simple_commands_.end(),
101 ncsi_header->control_packet_type) !=
102 simple_commands_.end())
103 {
104 // Simple Response
105 response_size =
106 ncsi_build_simple_ack(request_buf.data, response_buf->data);
107 }
108 else
109 {
110 // Not-so-Simple Response
111 switch (ncsi_header->control_packet_type)
112 {
113 case NCSI_GET_VERSION_ID:
114 response_size = ncsi_build_version_id_ack(
115 request_buf.data, response_buf->data, &version_);
116 break;
117 case NCSI_GET_CAPABILITIES:
118 response_size = sizeof(ncsi_capabilities_response_t);
119 {
120 ncsi_capabilities_response_t response;
121 ncsi_build_response_header(
122 request_buf.data, reinterpret_cast<uint8_t*>(&response),
123 0, 0, response_size - sizeof(ncsi_header_t));
124 response.channel_count = channel_count_;
125 std::memcpy(response_buf->data, &response,
126 sizeof(response));
127 }
128 break;
129 case NCSI_GET_PASSTHROUGH_STATISTICS:
130 if (is_legacy_)
131 {
132 response_size = ncsi_build_pt_stats_legacy_ack(
133 request_buf.data, response_buf->data, &stats_legacy_);
134 }
135 else
136 {
137 response_size = ncsi_build_pt_stats_ack(
138 request_buf.data, response_buf->data, &stats_);
139 }
140 break;
141 case NCSI_GET_LINK_STATUS:
142 response_size = ncsi_build_link_status_ack(
143 request_buf.data, response_buf->data, &link_status_);
144 break;
145 case NCSI_OEM_COMMAND:
146 response_size = handle_oem_request(request_buf, response_buf);
147 break;
148 default:
149 response_size = ncsi_build_simple_nack(
150 request_buf.data, response_buf->data, 1, 1);
151 break;
152 }
153 }
154
155 response_buf->len = response_size;
156
157 return response_size;
158 }
159
handle_oem_request(const ncsi_buf_t & request_buf,ncsi_buf_t * response_buf)160 uint32_t NIC::handle_oem_request(const ncsi_buf_t& request_buf,
161 ncsi_buf_t* response_buf)
162 {
163 const ncsi_oem_simple_cmd_t* oem_cmd =
164 reinterpret_cast<const ncsi_oem_simple_cmd_t*>(request_buf.data);
165 uint32_t response_size;
166 switch (oem_cmd->oem_header.oem_cmd)
167 {
168 case NCSI_OEM_COMMAND_GET_HOST_MAC:
169 response_size = ncsi_build_oem_get_mac_ack(
170 request_buf.data, response_buf->data, &mac_);
171 break;
172 case NCSI_OEM_COMMAND_SET_FILTER:
173 {
174 const ncsi_oem_set_filter_cmd_t* cmd =
175 reinterpret_cast<const ncsi_oem_set_filter_cmd_t*>(
176 request_buf.data);
177 if (set_filter(cmd->hdr.channel_id, cmd->filter))
178 {
179 response_size = ncsi_build_oem_simple_ack(request_buf.data,
180 response_buf->data);
181 }
182 else
183 {
184 response_size = ncsi_build_simple_nack(
185 request_buf.data, response_buf->data, 3, 4);
186 }
187 }
188 break;
189 case NCSI_OEM_COMMAND_ECHO:
190 response_size =
191 ncsi_build_oem_echo_ack(request_buf.data, response_buf->data);
192 break;
193 case NCSI_OEM_COMMAND_GET_FILTER:
194 {
195 const ncsi_simple_command_t* cmd =
196 reinterpret_cast<const ncsi_simple_command_t*>(
197 request_buf.data);
198 if (cmd->hdr.channel_id == 0)
199 {
200 response_size = ncsi_build_oem_get_filter_ack(
201 request_buf.data, response_buf->data, &ch0_filter_);
202 }
203 else if (cmd->hdr.channel_id == 1)
204 {
205 response_size = ncsi_build_oem_get_filter_ack(
206 request_buf.data, response_buf->data, &ch1_filter_);
207 }
208 else
209 {
210 response_size = ncsi_build_simple_nack(
211 request_buf.data, response_buf->data, 3, 4);
212 }
213 }
214 break;
215 default:
216 response_size = ncsi_build_simple_nack(request_buf.data,
217 response_buf->data, 1, 2);
218 break;
219 }
220
221 return response_size;
222 }
223
is_filter_configured(uint8_t channel) const224 bool NIC::is_filter_configured(uint8_t channel) const
225 {
226 if (channel == 0)
227 {
228 return is_ch0_filter_configured_;
229 }
230 else if (channel == 1)
231 {
232 return is_ch1_filter_configured_;
233 }
234
235 throw std::invalid_argument("Unsupported channel");
236 }
237
set_filter(uint8_t channel,const ncsi_oem_filter_t & filter)238 bool NIC::set_filter(uint8_t channel, const ncsi_oem_filter_t& filter)
239 {
240 ncsi_oem_filter_t* nic_filter;
241 if (channel == 0)
242 {
243 nic_filter = &ch0_filter_;
244 is_ch0_filter_configured_ = true;
245 }
246 else if (channel == 1)
247 {
248 nic_filter = &ch1_filter_;
249 is_ch1_filter_configured_ = true;
250 }
251 else
252 {
253 throw std::invalid_argument("Unsupported channel");
254 }
255
256 std::memcpy(nic_filter->mac, filter.mac, MAC_ADDR_SIZE);
257 nic_filter->ip = 0;
258 nic_filter->port = filter.port;
259 return true;
260 }
261
get_filter(uint8_t channel) const262 const ncsi_oem_filter_t& NIC::get_filter(uint8_t channel) const
263 {
264 if (channel == 0)
265 {
266 return ch0_filter_;
267 }
268 else if (channel == 1)
269 {
270 return ch1_filter_;
271 }
272
273 throw std::invalid_argument("Unsupported channel");
274 }
275
set_hostless(bool is_hostless)276 void NIC::set_hostless(bool is_hostless)
277 {
278 auto set_flag_op = [](uint8_t lhs, uint8_t rhs) -> auto {
279 return lhs | rhs;
280 };
281
282 auto clear_flag_op = [](uint8_t lhs, uint8_t rhs) -> auto {
283 return lhs & ~rhs;
284 };
285
286 auto flag_op = is_hostless ? set_flag_op : clear_flag_op;
287
288 if (channel_count_ > 0)
289 {
290 ch0_filter_.flags =
291 flag_op(ch0_filter_.flags, NCSI_OEM_FILTER_FLAGS_HOSTLESS);
292 }
293
294 if (channel_count_ > 1)
295 {
296 ch1_filter_.flags =
297 flag_op(ch1_filter_.flags, NCSI_OEM_FILTER_FLAGS_HOSTLESS);
298 }
299 }
300
toggle_hostless()301 void NIC::toggle_hostless()
302 {
303 if (channel_count_ > 0)
304 {
305 ch0_filter_.flags ^= NCSI_OEM_FILTER_FLAGS_HOSTLESS;
306 }
307
308 if (channel_count_ > 1)
309 {
310 ch1_filter_.flags ^= NCSI_OEM_FILTER_FLAGS_HOSTLESS;
311 }
312 }
313
is_hostless()314 bool NIC::is_hostless()
315 {
316 return ch0_filter_.flags & NCSI_OEM_FILTER_FLAGS_HOSTLESS;
317 }
318
save_frame_to_log(const NCSIFrame & frame)319 void NIC::save_frame_to_log(const NCSIFrame& frame)
320 {
321 if (cmd_log_.size() >= max_log_size_)
322 {
323 cmd_log_.erase(cmd_log_.begin());
324 }
325
326 cmd_log_.push_back(frame);
327 }
328
329 const std::vector<uint8_t> NIC::simple_commands_ = {
330 NCSI_CLEAR_INITIAL_STATE,
331 NCSI_SELECT_PACKAGE,
332 NCSI_DESELECT_PACKAGE,
333 NCSI_ENABLE_CHANNEL,
334 NCSI_DISABLE_CHANNEL,
335 NCSI_RESET_CHANNEL,
336 NCSI_ENABLE_CHANNEL_NETWORK_TX,
337 NCSI_DISABLE_CHANNEL_NETWORK_TX,
338 NCSI_AEN_ENABLE,
339 NCSI_SET_LINK,
340 NCSI_SET_VLAN_FILTER,
341 NCSI_ENABLE_VLAN,
342 NCSI_DISABLE_VLAN,
343 NCSI_SET_MAC_ADDRESS,
344 NCSI_ENABLE_BROADCAST_FILTER,
345 NCSI_DISABLE_BROADCAST_FILTER,
346 NCSI_ENABLE_GLOBAL_MULTICAST_FILTER,
347 NCSI_DISABLE_GLOBAL_MULTICAST_FILTER,
348 NCSI_SET_NCSI_FLOW_CONTROL,
349 };
350
351 } // namespace mock
352