diff --git a/src/common.hpp b/src/common.hpp index 8a3df86180..f482e903c8 100644 --- a/src/common.hpp +++ b/src/common.hpp @@ -99,53 +99,52 @@ namespace websocketpp { } // TODO functions for application ranges? - } - } + } // namespace status + } // namespace close namespace frame { - // Opcodes are 4 bits - // See spec section 5.2 - namespace opcode { - enum value { - CONTINUATION = 0x0, - TEXT = 0x1, - BINARY = 0x2, - RSV3 = 0x3, - RSV4 = 0x4, - RSV5 = 0x5, - RSV6 = 0x6, - RSV7 = 0x7, - CLOSE = 0x8, - PING = 0x9, - PONG = 0xA, - CONTROL_RSVB = 0xB, - CONTROL_RSVC = 0xC, - CONTROL_RSVD = 0xD, - CONTROL_RSVE = 0xE, - CONTROL_RSVF = 0xF, - }; - - inline bool reserved(value v) { - return (v >= RSV3 && v <= RSV7) || - (v >= CONTROL_RSVB && v <= CONTROL_RSVF); + // Opcodes are 4 bits + // See spec section 5.2 + namespace opcode { + enum value { + CONTINUATION = 0x0, + TEXT = 0x1, + BINARY = 0x2, + RSV3 = 0x3, + RSV4 = 0x4, + RSV5 = 0x5, + RSV6 = 0x6, + RSV7 = 0x7, + CLOSE = 0x8, + PING = 0x9, + PONG = 0xA, + CONTROL_RSVB = 0xB, + CONTROL_RSVC = 0xC, + CONTROL_RSVD = 0xD, + CONTROL_RSVE = 0xE, + CONTROL_RSVF = 0xF, + }; + + inline bool reserved(value v) { + return (v >= RSV3 && v <= RSV7) || + (v >= CONTROL_RSVB && v <= CONTROL_RSVF); + } + + inline bool invalid(value v) { + return (v > 0xF || v < 0); + } + + inline bool is_control(value v) { + return v >= 0x8; + } } - inline bool invalid(value v) { - return (v > 0xF || v < 0); + namespace limits { + static const uint8_t PAYLOAD_SIZE_BASIC = 125; + static const uint16_t PAYLOAD_SIZE_EXTENDED = 0xFFFF; // 2^16, 65535 + static const uint64_t PAYLOAD_SIZE_JUMBO = 0x7FFFFFFFFFFFFFFF;//2^63 } - - inline bool is_control(value v) { - return v >= 0x8; - } - } - - namespace limits { - static const uint8_t PAYLOAD_SIZE_BASIC = 125; - static const uint16_t PAYLOAD_SIZE_EXTENDED = 0xFFFF; // 2^16, 65535 - static const uint64_t PAYLOAD_SIZE_JUMBO = 0x7FFFFFFFFFFFFFFF;//2^63 - } } // namespace frame - } #endif // WEBSOCKET_CONSTANTS_HPP diff --git a/src/messages/data.cpp b/src/messages/data.cpp index 70a7931a97..aed78410c4 100644 --- a/src/messages/data.cpp +++ b/src/messages/data.cpp @@ -140,3 +140,6 @@ void data::process() { } } +void data::set_header(const std::string& header) { + m_header = header; +} diff --git a/src/messages/data.hpp b/src/messages/data.hpp index 2db40afb06..5f8dc4a102 100644 --- a/src/messages/data.hpp +++ b/src/messages/data.hpp @@ -63,6 +63,8 @@ public: // immediately and throws processor::exception if it fails void set_payload(const std::string& payload); + void set_header(const std::string& header); + // Performs masking and header generation if it has not been done already. void process(); diff --git a/src/processors/hybi.hpp b/src/processors/hybi.hpp index 0cc2a366d4..8a0b2be9d8 100644 --- a/src/processors/hybi.hpp +++ b/src/processors/hybi.hpp @@ -104,7 +104,10 @@ private: template class hybi : public processor_base { public: - hybi(connection_type &connection) : m_connection(connection),m_write_frame(connection) { + hybi(connection_type &connection) + : m_connection(connection), + m_write_frame(connection) + { reset(); } @@ -276,7 +279,8 @@ public: m_control_message = m_connection.get_control_message(); if (!m_control_message) { - throw processor::exception("Out of control messages",processor::error::OUT_OF_MESSAGES); + throw processor::exception("Out of control messages", + processor::error::OUT_OF_MESSAGES); } m_control_message->reset(m_header.get_opcode(),m_header.get_masking_key()); @@ -300,7 +304,8 @@ public: m_data_message = m_connection.get_data_message(); if (!m_data_message) { - throw processor::exception("Out of data messages",processor::error::OUT_OF_MESSAGES); + throw processor::exception("Out of data messages", + processor::error::OUT_OF_MESSAGES); } m_data_message->reset(m_header.get_opcode()); @@ -488,6 +493,19 @@ public: return response; } + // new prepare frame stuff + void prepare_frame(message::data_ptr msg, bool masked, int32_t mask) { + m_write_header.reset(); + m_write_header.set_fin(true); + m_write_header.set_opcode(msg->get_opcode()); + m_write_header.set_masked(masked,mask); + m_write_header.set_payload_size(msg->get_payload().size()); + m_write_header.complete(); + + msg->set_header(m_write_header.get_header_bytes()); + } + + private: connection_type& m_connection; int m_state; @@ -495,8 +513,10 @@ private: message::data_ptr m_data_message; message::control_ptr m_control_message; hybi_header m_header; + hybi_header m_write_header; uint64_t m_payload_left; + frame::parser m_write_frame; // TODO: refactor this out }; diff --git a/src/processors/hybi_header.cpp b/src/processors/hybi_header.cpp index 36462091c9..003fcc9cb4 100644 --- a/src/processors/hybi_header.cpp +++ b/src/processors/hybi_header.cpp @@ -29,53 +29,23 @@ #include -using websocketpp::processor::hybi_header_writer; - -hybi_header_writer::hybi_header_writer() { - reset(); -} - -void hybi_header_writer::reset() { - memset(m_header, 0x00, MAX_HEADER_LENGTH); -} - -std::string hybi_header_writer::get_header_bytes() const { - return std::string(m_header); -} - - - - - - - using websocketpp::processor::hybi_header; -hybi_header::hybi_header(bool reading) { - reset(reading); +hybi_header::hybi_header() { + reset(); } - -uint64_t hybi_header::get_bytes_needed() const { - return m_bytes_needed; -} - -void hybi_header::reset(bool reading) { - if (reading) { - m_state = STATE_BASIC_HEADER; - m_bytes_needed = BASIC_HEADER_LENGTH; - } else { - m_state = STATE_WRITE; - } -} - -bool hybi_header::ready() const { - return m_state == STATE_READY; +void hybi_header::reset() { + memset(m_header, 0x00, MAX_HEADER_LENGTH); + m_state = STATE_BASIC_HEADER; + m_bytes_needed = BASIC_HEADER_LENGTH; } +// Writing interface (parse a byte stream) void hybi_header::consume(std::istream& input) { switch (m_state) { case STATE_BASIC_HEADER: - input.read(&m_header[BASIC_HEADER_LENGTH-m_bytes_needed],m_bytes_needed); + input.read(&m_header[BASIC_HEADER_LENGTH-m_bytes_needed], + m_bytes_needed); m_bytes_needed -= input.gcount(); @@ -93,7 +63,8 @@ void hybi_header::consume(std::istream& input) { } break; case STATE_EXTENDED_HEADER: - input.read(&m_header[get_header_len()-m_bytes_needed],m_bytes_needed); + input.read(&m_header[get_header_len()-m_bytes_needed], + m_bytes_needed); m_bytes_needed -= input.gcount(); @@ -106,7 +77,113 @@ void hybi_header::consume(std::istream& input) { break; } } +uint64_t hybi_header::get_bytes_needed() const { + return m_bytes_needed; +} +bool hybi_header::ready() const { + return m_state == STATE_READY; +} +// Writing interface (set fields directly) +void hybi_header::set_fin(bool fin) { + set_header_bit(BPB0_FIN,0,fin); +} +void hybi_header::set_rsv1(bool b) { + set_header_bit(BPB0_RSV1,0,b); +} +void hybi_header::set_rsv2(bool b) { + set_header_bit(BPB0_RSV2,0,b); +} +void hybi_header::set_rsv3(bool b) { + set_header_bit(BPB0_RSV3,0,b); +} +void hybi_header::set_opcode(frame::opcode::value op) { + m_header[0] &= (0xFF ^ BPB0_OPCODE); // clear op bits + m_header[0] |= op; // set op bits +} +void hybi_header::set_masked(bool masked,int32_t key) { + if (masked) { + m_header[1] |= BPB1_MASK; + set_masking_key(key); + } else { + m_header[1] &= (0xFF ^ BPB1_MASK); + clear_masking_key(); + } +} +void hybi_header::set_payload_size(uint64_t size) { + if (size <= frame::limits::PAYLOAD_SIZE_BASIC) { + m_header[1] &= (size & BPB1_PAYLOAD); + m_payload_size = size; + } else if (size <= frame::limits::PAYLOAD_SIZE_EXTENDED) { + if (get_masked()) { + // shift mask bytes to the correct position given the new size + unsigned int mask_offset = get_header_len()-4; + m_header[1] &= (BASIC_PAYLOAD_16BIT_CODE & BPB1_PAYLOAD); + memcpy(&m_header[get_header_len()-4], &m_header[mask_offset], 4); + } else { + m_header[1] &= (BASIC_PAYLOAD_16BIT_CODE & BPB1_PAYLOAD); + } + m_payload_size = size; + *(reinterpret_cast(&m_header[BASIC_HEADER_LENGTH])) = htons(size); + } else if (size <= frame::limits::PAYLOAD_SIZE_EXTENDED) { + if (get_masked()) { + // shift mask bytes to the correct position given the new size + unsigned int mask_offset = get_header_len()-4; + m_header[1] &= (BASIC_PAYLOAD_64BIT_CODE & BPB1_PAYLOAD); + memcpy(&m_header[get_header_len()-4], &m_header[mask_offset], 4); + } else { + m_header[1] &= (BASIC_PAYLOAD_64BIT_CODE & BPB1_PAYLOAD); + } + m_payload_size = size; + *(reinterpret_cast(&m_header[BASIC_HEADER_LENGTH])) = htonll(size); + } else { + throw processor::exception("set_payload_size called with value that was too large (>2^63)",processor::error::MESSAGE_TOO_BIG); + } +} +void hybi_header::complete() { + validate_basic_header(); + m_state = STATE_READY; +} + +// Reading interface (get string of bytes) +std::string hybi_header::get_header_bytes() const { + return std::string(m_header,get_header_len()); +} + +// Reading interface (get fields directly) +bool hybi_header::get_fin() const { + return ((m_header[0] & BPB0_FIN) == BPB0_FIN); +} +bool hybi_header::get_rsv1() const { + return ((m_header[0] & BPB0_RSV1) == BPB0_RSV1); +} +bool hybi_header::get_rsv2() const { + return ((m_header[0] & BPB0_RSV2) == BPB0_RSV2); +} +bool hybi_header::get_rsv3() const { + return ((m_header[0] & BPB0_RSV3) == BPB0_RSV3); +} +websocketpp::frame::opcode::value hybi_header::get_opcode() const { + return frame::opcode::value(m_header[0] & BPB0_OPCODE); +} +bool hybi_header::get_masked() const { + return ((m_header[1] & BPB1_MASK) == BPB1_MASK); +} +int32_t hybi_header::get_masking_key() const { + if (!get_masked()) { + return 0; + } + return *reinterpret_cast(&m_header[get_header_len()-4]); +} +uint64_t hybi_header::get_payload_size() const { + return m_payload_size; +} + +bool hybi_header::is_control() const { + return (frame::opcode::is_control(get_opcode())); +} + +// private unsigned int hybi_header::get_header_len() const { unsigned int temp = 2; @@ -123,175 +200,9 @@ unsigned int hybi_header::get_header_len() const { return temp; } -int32_t hybi_header::get_masking_key() { - if (m_state != STATE_READY) { - throw processor::exception("attempted to get masking_key before reading full header"); - } - if (!get_masked()) { - // masking key of 0 and not masked are equivalent - return 0; - } - - return *reinterpret_cast(&m_header[get_header_len()-4]); -} - -// get and set header bits -bool hybi_header::get_fin() const { - return ((m_header[0] & BPB0_FIN) == BPB0_FIN); -} -void hybi_header::set_fin(bool fin) { - if (fin) { - m_header[0] |= BPB0_FIN; - } else { - m_header[0] &= (0xFF ^ BPB0_FIN); - } -} - -bool hybi_header::get_rsv1() const { - return ((m_header[0] & BPB0_RSV1) == BPB0_RSV1); -} -void hybi_header::set_rsv1(bool b) { - if (b) { - m_header[0] |= BPB0_RSV1; - } else { - m_header[0] &= (0xFF ^ BPB0_RSV1); - } -} - -bool hybi_header::get_rsv2() const { - return ((m_header[0] & BPB0_RSV2) == BPB0_RSV2); -} -void hybi_header::set_rsv2(bool b) { - if (b) { - m_header[0] |= BPB0_RSV2; - } else { - m_header[0] &= (0xFF ^ BPB0_RSV2); - } -} - -bool hybi_header::get_rsv3() const { - return ((m_header[0] & BPB0_RSV3) == BPB0_RSV3); -} -void hybi_header::set_rsv3(bool b) { - if (b) { - m_header[0] |= BPB0_RSV3; - } else { - m_header[0] &= (0xFF ^ BPB0_RSV3); - } -} - -websocketpp::frame::opcode::value hybi_header::get_opcode() const { - return frame::opcode::value(m_header[0] & BPB0_OPCODE); -} -void hybi_header::set_opcode(frame::opcode::value op) { - if (frame::opcode::reserved(op)) { - throw processor::exception("reserved opcode",processor::error::PROTOCOL_VIOLATION); - } - - if (frame::opcode::invalid(op)) { - throw processor::exception("invalid opcode",processor::error::PROTOCOL_VIOLATION); - } - - if (is_control() && get_basic_size() > frame::limits::PAYLOAD_SIZE_BASIC) { - throw processor::exception("control frames can't have large payloads",processor::error::PROTOCOL_VIOLATION); - } - - m_header[0] &= (0xFF ^ BPB0_OPCODE); // clear op bits - m_header[0] |= op; // set op bits -} - -bool hybi_header::get_masked() const { - return ((m_header[1] & BPB1_MASK) == BPB1_MASK); -} -void hybi_header::set_masked(bool masked,int32_t key) { - if (masked) { - m_header[1] |= BPB1_MASK; - set_masking_key(key); - } else { - m_header[1] &= (0xFF ^ BPB1_MASK); - clear_masking_key(); - } -} - uint8_t hybi_header::get_basic_size() const { return m_header[1] & BPB1_PAYLOAD; } -size_t hybi_header::get_payload_size() const { - if (m_state != STATE_READY) { - // TODO: how to handle errors like this? - throw "attempted to get payload size before reading full header"; - } - - return m_payload_size; -} - -void hybi_header::set_payload_size(size_t size) { - if (size <= frame::limits::PAYLOAD_SIZE_BASIC) { - // encode in byte 2 - m_header[1] &= (size & BPB1_PAYLOAD); - } else if (size <= frame::limits::PAYLOAD_SIZE_EXTENDED) { - // encode two byte - m_header[1] &= (BASIC_PAYLOAD_16BIT_CODE & BPB1_PAYLOAD); - - if (get_masked()) { - - } - - *(reinterpret_cast(&m_header[BASIC_HEADER_LENGTH])) = htons(size); - - - - - } else if (size <= frame::limits::PAYLOAD_SIZE_EXTENDED) { - // encode two byte - m_header[1] &= (BASIC_PAYLOAD_64BIT_CODE & BPB1_PAYLOAD); - } else { - throw processor::exception("Client attempted to send a message that was too big",processor::error::MESSAGE_TOO_BIG); - } -} - -bool hybi_header::is_control() const { - return (frame::opcode::is_control(get_opcode())); -} - -void hybi_header::process_basic_header() { - m_bytes_needed = get_header_len() - BASIC_HEADER_LENGTH; -} -void hybi_header::process_extended_header() { - uint8_t s = get_basic_size(); - - if (s <= frame::limits::PAYLOAD_SIZE_BASIC) { - m_payload_size = s; - } else if (s == BASIC_PAYLOAD_16BIT_CODE) { - // reinterpret the second two bytes as a 16 bit integer in network - // byte order. Convert to host byte order and store locally. - m_payload_size = ntohs(*( - reinterpret_cast(&m_header[BASIC_HEADER_LENGTH]) - )); - - if (m_payload_size < s) { - std::stringstream err; - err << "payload length not minimally encoded. Using 16 bit form for payload size: " << m_payload_size; - throw processor::exception(err.str(),processor::error::PROTOCOL_VIOLATION); - } - - } else if (s == BASIC_PAYLOAD_64BIT_CODE) { - // reinterpret the second eight bytes as a 64 bit integer in - // network byte order. Convert to host byte order and store. - m_payload_size = ntohll(*( - reinterpret_cast(&m_header[BASIC_HEADER_LENGTH]) - )); - - if (m_payload_size <= frame::limits::PAYLOAD_SIZE_EXTENDED) { - throw processor::exception("payload length not minimally encoded", - processor::error::PROTOCOL_VIOLATION); - } - - } else { - // TODO: shouldn't be here how to handle? - throw processor::exception("invalid get_basic_size in process_extended_header"); - } -} void hybi_header::validate_basic_header() const { // check for control frame size @@ -308,6 +219,11 @@ void hybi_header::validate_basic_header() const { if (frame::opcode::reserved(get_opcode())) { throw processor::exception("Reserved opcode used",processor::error::PROTOCOL_VIOLATION); } + + // check for invalid opcodes + if (frame::opcode::invalid(get_opcode())) { + throw processor::exception("Invalid opcode used",processor::error::PROTOCOL_VIOLATION); + } // check for fragmented control message if (is_control() && !get_fin()) { @@ -315,6 +231,53 @@ void hybi_header::validate_basic_header() const { } } +void hybi_header::process_basic_header() { + m_bytes_needed = get_header_len() - BASIC_HEADER_LENGTH; +} +void hybi_header::process_extended_header() { + uint8_t s = get_basic_size(); + + if (s <= frame::limits::PAYLOAD_SIZE_BASIC) { + m_payload_size = s; + } else if (s == BASIC_PAYLOAD_16BIT_CODE) { + // reinterpret the second two bytes as a 16 bit integer in network + // byte order. Convert to host byte order and store locally. + m_payload_size = ntohs(*( + reinterpret_cast(&m_header[BASIC_HEADER_LENGTH]) + )); + + if (m_payload_size < s) { + std::stringstream err; + err << "payload length not minimally encoded. Using 16 bit form for payload size: " << m_payload_size; + throw processor::exception(err.str(),processor::error::PROTOCOL_VIOLATION); + } + + } else if (s == BASIC_PAYLOAD_64BIT_CODE) { + // reinterpret the second eight bytes as a 64 bit integer in + // network byte order. Convert to host byte order and store. + m_payload_size = ntohll(*( + reinterpret_cast(&m_header[BASIC_HEADER_LENGTH]) + )); + + if (m_payload_size <= frame::limits::PAYLOAD_SIZE_EXTENDED) { + throw processor::exception("payload length not minimally encoded", + processor::error::PROTOCOL_VIOLATION); + } + + } else { + // TODO: shouldn't be here how to handle? + throw processor::exception("invalid get_basic_size in process_extended_header"); + } +} + +void hybi_header::set_header_bit(uint8_t bit,int byte,bool value) { + if (value) { + m_header[byte] |= bit; + } else { + m_header[byte] &= (0xFF ^ bit); + } +} + void hybi_header::set_masking_key(int32_t key) { *(reinterpret_cast(&m_header[get_header_len()-4])) = key; } diff --git a/src/processors/hybi_header.hpp b/src/processors/hybi_header.hpp index b3d0f7957e..6343a05c62 100644 --- a/src/processors/hybi_header.hpp +++ b/src/processors/hybi_header.hpp @@ -29,108 +29,92 @@ #define WEBSOCKET_PROCESSOR_HYBI_HEADER_HPP #include "processor.hpp" -#include "../websocket_frame.hpp" // TODO: remove this dependency namespace websocketpp { namespace processor { -class hybi_header_writer { +/// Describes a processor for reading and writing WebSocket frame headers +/** + * The hybi_header class provides a processor capable of reading and writing + * WebSocket frame headers. It has two writing modes and two reading modes. + * + * Writing method 1: call consume() until ready() + * Writing method 2: call set_* methods followed by complete() + * + * Writing methods are valid only when ready() returns false. Use reset() to + * reset the header for writing again. Mixing writing methods between calls to + * reset() may behave unpredictably. + * + * Reading method 1: call get_header_bytes() to return a string of bytes + * Reading method 2: call get_* methods to read individual values + * + * Reading methods are valid only when ready() is true. + * + * @par Thread Safety + * @e Distinct @e objects: Safe.@n + * @e Shared @e objects: Unsafe + */ +class hybi_header { public: - hybi_header_writer(); - void reset(); + /// Construct a header processor and initialize for writing + hybi_header(); + /// Reset a header processor for writing + void reset(); + + // Writing interface (parse a byte stream) + // valid only if ready() returns false + // Consume will throw a processor::exception in the case that the bytes it + // read do not form a valid WebSocket frame header. + void consume(std::istream& input); + uint64_t get_bytes_needed() const; + bool ready() const; + // Writing interface (set fields directly) + // valid only if ready() returns false + // set_* may allow invalid values. Call complete() once values are set to + // check for header validity. void set_fin(bool fin); void set_rsv1(bool b); void set_rsv2(bool b); void set_rsv3(bool b); void set_opcode(frame::opcode::value op); void set_masked(bool masked,int32_t key); - void set_payload_size(size_t size); + void set_payload_size(uint64_t size); + // Complete will throw a processor::exception in the case that the + // combination of values set do not form a valid WebSocket frame header. + void complete(); + // Reading interface (get string of bytes) + // valid only if ready() returns true std::string get_header_bytes() const; -private: - // basic payload byte flags - static const uint8_t BPB0_OPCODE = 0x0F; - static const uint8_t BPB0_RSV3 = 0x10; - static const uint8_t BPB0_RSV2 = 0x20; - static const uint8_t BPB0_RSV1 = 0x40; - static const uint8_t BPB0_FIN = 0x80; - static const uint8_t BPB1_PAYLOAD = 0x7F; - static const uint8_t BPB1_MASK = 0x80; - - static const uint8_t BASIC_PAYLOAD_16BIT_CODE = 0x7E; // 126 - static const uint8_t BASIC_PAYLOAD_64BIT_CODE = 0x7F; // 127 - - static const unsigned int BASIC_HEADER_LENGTH = 2; - static const unsigned int MAX_HEADER_LENGTH = 15; - - char m_header[MAX_HEADER_LENGTH]; -}; - - -// hybi header can be used for the following two tasks: -// - parse a sequence of bytes into a complete header and read fields -// - set fields and then generate a sequence of header bytes - -class hybi_header { -public: - // constructs/resets a hybi_header for the purpose of either reading or writing - hybi_header(bool reading = true); - void reset(bool reading = true); - - // Reading interface (Parse a byte stream) - void consume(std::istream& input); - uint64_t get_bytes_needed() const; - bool ready() const; + // Reading interface (get fields directly) + // valid only if ready() returns true bool get_fin() const; bool get_rsv1() const; bool get_rsv2() const; bool get_rsv3() const; frame::opcode::value get_opcode() const; bool get_masked() const; + // will return zero in the case where get_masked() is false. Note: + // a masking key of zero is slightly different than no mask at all. + int32_t get_masking_key() const; + uint64_t get_payload_size() const; - // Writing interface (Set fields directly) - void set_fin(bool fin); - void set_rsv1(bool b); - void set_rsv2(bool b); - void set_rsv3(bool b); - void set_opcode(frame::opcode::value op); - void set_masked(bool masked,int32_t key); - void set_payload_size(size_t size); - - std::string get_header_bytes() const; - - - unsigned int get_header_len() const; - - int32_t get_masking_key(); - - // get and set header bits - - - - - - - - - uint8_t get_basic_size() const; - size_t get_payload_size() const; - - - - bool is_control() const; - - void process_basic_header(); - void process_extended_header(); - - void validate_basic_header() const; - - void set_masking_key(int32_t key); - void clear_masking_key(); - + bool is_control() const; private: + // general helper functions + unsigned int get_header_len() const; + uint8_t get_basic_size() const; + void validate_basic_header() const; + + // helper functions for writing + void process_basic_header(); + void process_extended_header(); + void set_header_bit(uint8_t bit,int byte,bool value); + void set_masking_key(int32_t key); + void clear_masking_key(); + // basic payload byte flags static const uint8_t BPB0_OPCODE = 0x0F; static const uint8_t BPB0_RSV3 = 0x10;