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_ = 76 std::vector<uint8_t>(ncsi_buf.data, 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 = 104 ncsi_build_simple_ack(request_buf.data, 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 = 189 ncsi_build_oem_echo_ack(request_buf.data, 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 { 278 return lhs | rhs; 279 }; 280 281 auto clear_flag_op = [](uint8_t lhs, uint8_t rhs) -> auto 282 { 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 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