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