/* * Copyright (c) Atmosphère-NX * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #pragma once #include #include #include namespace haze { class PtpDataBuilder final { private: AsyncUsbServer *m_server; u32 m_transmitted_size; u32 m_offset; u8 *m_data; bool m_disabled; private: Result Flush() { ON_SCOPE_EXIT { m_transmitted_size += m_offset; m_offset = 0; }; /* If we're disabled, we have nothing to do. */ R_SUCCEED_IF(m_disabled); /* Otherwise, we should write our buffered data. */ R_RETURN(m_server->WritePacket(m_data, m_offset)); } public: constexpr explicit PtpDataBuilder(void *data, AsyncUsbServer *server) : m_server(server), m_transmitted_size(), m_offset(), m_data(static_cast(data)), m_disabled() { /* ... */ } Result Commit() { if (m_offset > 0) { /* If there is remaining data left to write, write it now. */ R_TRY(this->Flush()); } if (util::IsAligned(m_transmitted_size, PtpUsbBulkHighSpeedMaxPacketLength)) { /* If the transmission size was a multiple of wMaxPacketSize, send a zero length packet. */ R_TRY(this->Flush()); } R_SUCCEED(); } Result AddBuffer(const u8 *buffer, u32 count) { while (count > 0) { /* Calculate how many bytes we can write now. */ const u32 write_size = std::min(count, haze::UsbBulkPacketBufferSize - m_offset); /* Write this number of bytes. */ std::memcpy(m_data + m_offset, buffer, write_size); m_offset += write_size; buffer += write_size; count -= write_size; /* If our buffer is full, flush it. */ if (m_offset == haze::UsbBulkPacketBufferSize) { R_TRY(this->Flush()); } } R_SUCCEED(); } template Result Add(T value) { u8 bytes[sizeof(T)]; std::memcpy(bytes, std::addressof(value), sizeof(T)); R_RETURN(this->AddBuffer(bytes, sizeof(T))); } Result AddDataHeader(PtpUsbBulkContainer &request, u32 data_size) { R_TRY(this->Add(PtpUsbBulkHeaderLength + data_size)); R_TRY(this->Add(PtpUsbBulkContainerType_Data)); R_TRY(this->Add(request.code)); R_TRY(this->Add(request.trans_id)); R_SUCCEED(); } Result AddResponseHeader(PtpUsbBulkContainer &request, PtpResponseCode code, u32 params_size) { R_TRY(this->Add(PtpUsbBulkHeaderLength + params_size)); R_TRY(this->Add(PtpUsbBulkContainerType_Response)); R_TRY(this->Add(code)); R_TRY(this->Add(request.trans_id)); R_SUCCEED(); } template Result WriteVariableLengthData(PtpUsbBulkContainer &request, F &&func) { HAZE_ASSERT(m_offset == 0 && m_transmitted_size == 0); /* Declare how many bytes the data will require to write. */ u32 data_size = 0; { /* Temporarily disable writing to calculate the size.*/ m_disabled = true; ON_SCOPE_EXIT { /* Report how many bytes were required. */ data_size = m_transmitted_size; /* On exit, enable writing and reset sizes. */ m_transmitted_size = 0; m_disabled = false; m_offset = 0; }; R_TRY(func()); R_TRY(this->Commit()); } /* Actually copy and write the data. */ R_TRY(this->AddDataHeader(request, data_size)); R_TRY(func()); R_TRY(this->Commit()); /* We succeeded. */ R_SUCCEED(); } template Result AddString(const T *str) { /* Use one less than the maximum string length for maximum length with null terminator. */ const u8 len = static_cast(std::min(util::Strlen(str), PtpStringMaxLength - 1)); if (len > 0) { /* Length is padded by null terminator for non-empty strings. */ R_TRY(this->Add(len + 1)); for (size_t i = 0; i < len; i++) { R_TRY(this->Add(str[i])); } R_TRY(this->Add(0)); } else { R_TRY(this->Add(len)); } R_SUCCEED(); } template Result AddArray(const T *arr, u32 count) { R_TRY(this->Add(count)); for (size_t i = 0; i < count; i++) { R_TRY(this->Add(arr[i])); } R_SUCCEED(); } }; }