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_ = std::vector<uint8_t>(ncsi_buf.data,
78                                        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 = ncsi_build_simple_ack(request_buf.data,
106                                               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 = ncsi_build_oem_echo_ack(request_buf.data,
191                                                     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 = flag_op(ch0_filter_.flags,
291                                     NCSI_OEM_FILTER_FLAGS_HOSTLESS);
292     }
293 
294     if (channel_count_ > 1)
295     {
296         ch1_filter_.flags = flag_op(ch1_filter_.flags,
297                                     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