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 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 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 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 224 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 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 262 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 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 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 314 bool NIC::is_hostless() 315 { 316 return ch0_filter_.flags & NCSI_OEM_FILTER_FLAGS_HOSTLESS; 317 } 318 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