From 92a8c8eb8852d7db73e2d7d27b2ea6cbacc63302 Mon Sep 17 00:00:00 2001 From: Liam Date: Fri, 1 Sep 2023 20:55:22 -0400 Subject: [PATCH] haze: implement android operations --- troposphere/haze/include/haze/ptp.hpp | 5 + .../haze/include/haze/ptp_responder.hpp | 7 + .../haze/include/haze/ptp_responder_types.hpp | 5 + troposphere/haze/include/haze/results.hpp | 1 + troposphere/haze/source/ptp_responder.cpp | 10 +- .../ptp_responder_android_operations.cpp | 203 ++++++++++++++++++ 6 files changed, 230 insertions(+), 1 deletion(-) create mode 100644 troposphere/haze/source/ptp_responder_android_operations.cpp diff --git a/troposphere/haze/include/haze/ptp.hpp b/troposphere/haze/include/haze/ptp.hpp index 771b126ce..beca16842 100644 --- a/troposphere/haze/include/haze/ptp.hpp +++ b/troposphere/haze/include/haze/ptp.hpp @@ -72,6 +72,11 @@ namespace haze { PtpOperationCode_GetFilesystemManifest = 0x1023, PtpOperationCode_GetStreamInfo = 0x1024, PtpOperationCode_GetStream = 0x1025, + PtpOperationCode_AndroidGetPartialObject64 = 0x95c1, + PtpOperationCode_AndroidSendPartialObject = 0x95c2, + PtpOperationCode_AndroidTruncateObject = 0x95c3, + PtpOperationCode_AndroidBeginEditObject = 0x95c4, + PtpOperationCode_AndroidEndEditObject = 0x95c5, PtpOperationCode_MtpGetObjectPropsSupported = 0x9801, PtpOperationCode_MtpGetObjectPropDesc = 0x9802, PtpOperationCode_MtpGetObjectPropValue = 0x9803, diff --git a/troposphere/haze/include/haze/ptp_responder.hpp b/troposphere/haze/include/haze/ptp_responder.hpp index 7f1b177eb..9bd1328e5 100644 --- a/troposphere/haze/include/haze/ptp_responder.hpp +++ b/troposphere/haze/include/haze/ptp_responder.hpp @@ -71,6 +71,13 @@ namespace haze { Result SendObject(PtpDataParser &dp); Result DeleteObject(PtpDataParser &dp); + /* Android operations. */ + Result GetPartialObject64(PtpDataParser &dp); + Result SendPartialObject(PtpDataParser &dp); + Result TruncateObject(PtpDataParser &dp); + Result BeginEditObject(PtpDataParser &dp); + Result EndEditObject(PtpDataParser &dp); + /* MTP operations. */ Result GetObjectPropsSupported(PtpDataParser &dp); Result GetObjectPropDesc(PtpDataParser &dp); diff --git a/troposphere/haze/include/haze/ptp_responder_types.hpp b/troposphere/haze/include/haze/ptp_responder_types.hpp index 3e307f9d3..d11416437 100644 --- a/troposphere/haze/include/haze/ptp_responder_types.hpp +++ b/troposphere/haze/include/haze/ptp_responder_types.hpp @@ -57,6 +57,11 @@ namespace haze { PtpOperationCode_MtpGetObjectPropDesc, PtpOperationCode_MtpGetObjectPropValue, PtpOperationCode_MtpSetObjectPropValue, + PtpOperationCode_AndroidGetPartialObject64, + PtpOperationCode_AndroidSendPartialObject, + PtpOperationCode_AndroidTruncateObject, + PtpOperationCode_AndroidBeginEditObject, + PtpOperationCode_AndroidEndEditObject, }; constexpr const PtpEventCode SupportedEventCodes[] = { /* ... */ }; diff --git a/troposphere/haze/include/haze/results.hpp b/troposphere/haze/include/haze/results.hpp index fec0e84de..e8c501ffc 100644 --- a/troposphere/haze/include/haze/results.hpp +++ b/troposphere/haze/include/haze/results.hpp @@ -37,5 +37,6 @@ namespace haze { R_DEFINE_ERROR_RESULT(UnknownRequestType, 13); R_DEFINE_ERROR_RESULT(UnknownPropertyCode, 14); R_DEFINE_ERROR_RESULT(InvalidPropertyValue, 15); + R_DEFINE_ERROR_RESULT(InvalidArgument, 16); } diff --git a/troposphere/haze/source/ptp_responder.cpp b/troposphere/haze/source/ptp_responder.cpp index ffba26229..ad8d4abb8 100644 --- a/troposphere/haze/source/ptp_responder.cpp +++ b/troposphere/haze/source/ptp_responder.cpp @@ -22,7 +22,7 @@ namespace haze { namespace { - PtpBuffers* GetBuffers() { + PtpBuffers *GetBuffers() { static constinit PtpBuffers buffers = {}; return std::addressof(buffers); } @@ -91,6 +91,9 @@ namespace haze { R_CATCH(haze::ResultInvalidPropertyValue) { R_TRY(this->WriteResponse(PtpResponseCode_MtpInvalidObjectPropValue)); } + R_CATCH(haze::ResultInvalidArgument) { + R_TRY(this->WriteResponse(PtpResponseCode_GeneralError)); + } R_CATCH_MODULE(fs) { /* Errors from fs are typically recoverable. */ R_TRY(this->WriteResponse(PtpResponseCode_GeneralError)); @@ -131,6 +134,11 @@ namespace haze { case PtpOperationCode_MtpGetObjectPropDesc: R_RETURN(this->GetObjectPropDesc(dp)); break; case PtpOperationCode_MtpGetObjectPropValue: R_RETURN(this->GetObjectPropValue(dp)); break; case PtpOperationCode_MtpSetObjectPropValue: R_RETURN(this->SetObjectPropValue(dp)); break; + case PtpOperationCode_AndroidGetPartialObject64: R_RETURN(this->GetPartialObject64(dp)); break; + case PtpOperationCode_AndroidSendPartialObject: R_RETURN(this->SendPartialObject(dp)); break; + case PtpOperationCode_AndroidTruncateObject: R_RETURN(this->TruncateObject(dp)); break; + case PtpOperationCode_AndroidBeginEditObject: R_RETURN(this->BeginEditObject(dp)); break; + case PtpOperationCode_AndroidEndEditObject: R_RETURN(this->EndEditObject(dp)); break; default: R_THROW(haze::ResultOperationNotSupported()); } } diff --git a/troposphere/haze/source/ptp_responder_android_operations.cpp b/troposphere/haze/source/ptp_responder_android_operations.cpp new file mode 100644 index 000000000..aef13011e --- /dev/null +++ b/troposphere/haze/source/ptp_responder_android_operations.cpp @@ -0,0 +1,203 @@ +/* + * 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 . + */ +#include +#include +#include +#include + +namespace haze { + + Result PtpResponder::GetPartialObject64(PtpDataParser &dp) { + PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server)); + + /* Get the object ID, offset, and size for the file we want to read. */ + u32 object_id, size; + u64 offset; + R_TRY(dp.Read(std::addressof(object_id))); + R_TRY(dp.Read(std::addressof(offset))); + R_TRY(dp.Read(std::addressof(size))); + R_TRY(dp.Finalize()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* Lock the object as a file. */ + FsFile file; + R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file))); + + /* Ensure we maintain a clean state on exit. */ + ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); }; + + /* Get the file's size. */ + s64 file_size = 0; + R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(file_size))); + + /* Ensure the requested offset and size are within range. */ + R_UNLESS(offset + size > offset, haze::ResultInvalidArgument()); + R_UNLESS(static_cast(file_size) <= offset + size, haze::ResultInvalidArgument()); + + /* Send the header and data size. */ + R_TRY(db.AddDataHeader(m_request_header, size)); + + /* Begin reading the file, writing data to the builder as we progress. */ + s64 size_remaining = size; + while (true) { + /* Get the next batch. */ + u64 bytes_to_read = std::min(FsBufferSize, size_remaining); + u64 bytes_read; + + R_TRY(m_fs.ReadFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, bytes_to_read, FsReadOption_None, std::addressof(bytes_read))); + + size_remaining -= bytes_read; + offset += bytes_read; + + /* Write to output. */ + R_TRY(db.AddBuffer(m_buffers->file_system_data_buffer, bytes_read)); + + /* If we read fewer bytes than the batch size, or have read enough data, we're done. */ + if (bytes_read < FsBufferSize || size_remaining == 0) { + break; + } + } + + /* Flush the data response. */ + R_TRY(db.Commit()); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::SendPartialObject(PtpDataParser &rdp) { + /* Get the object ID, offset, and size for the file we want to write. */ + u32 object_id, size; + u64 offset; + R_TRY(rdp.Read(std::addressof(object_id))); + R_TRY(rdp.Read(std::addressof(size))); + R_TRY(rdp.Read(std::addressof(offset))); + R_TRY(rdp.Finalize()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(m_send_object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* Lock the object as a file. */ + FsFile file; + R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write | FsOpenMode_Append, std::addressof(file))); + + /* Ensure we maintain a clean state on exit. */ + ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); }; + + /* Get the file's size. */ + s64 file_size = 0; + R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(file_size))); + + /* Ensure the requested offset and size are within range. */ + R_UNLESS(offset + size > offset, haze::ResultInvalidArgument()); + R_UNLESS(static_cast(file_size) <= offset, haze::ResultInvalidArgument()); + + /* Prepare a data parser for the data we are about to receive. */ + PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server)); + + /* Ensure we have a data header. */ + PtpUsbBulkContainer data_header; + R_TRY(dp.Read(std::addressof(data_header))); + R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType()); + R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported()); + R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported()); + + /* Begin writing to the filesystem. */ + s64 size_remaining = size; + while (true) { + /* Read as many bytes as we can. */ + u32 bytes_received; + const Result read_res = dp.ReadBuffer(m_buffers->file_system_data_buffer, FsBufferSize, std::addressof(bytes_received)); + + /* Write to the file. */ + u32 bytes_to_write = std::min(size_remaining, bytes_received); + R_TRY(m_fs.WriteFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, bytes_to_write, 0)); + + size_remaining -= bytes_to_write; + offset += bytes_to_write; + + /* If we received fewer bytes than the batch size, or have written enough data, we're done. */ + if (haze::ResultEndOfTransmission::Includes(read_res) || size_remaining == 0) { + break; + } + + R_TRY(read_res); + } + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::TruncateObject(PtpDataParser &dp) { + /* Get the object ID and size for the file we want to truncate. */ + u32 object_id; + u64 size; + R_TRY(dp.Read(std::addressof(object_id))); + R_TRY(dp.Read(std::addressof(size))); + R_TRY(dp.Finalize()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* Lock the object as a file. */ + FsFile file; + R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write, std::addressof(file))); + + /* Ensure we maintain a clean state on exit. */ + ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); }; + + /* Truncate the file. */ + R_TRY(m_fs.SetFileSize(std::addressof(file), size)); + + /* Write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::BeginEditObject(PtpDataParser &dp) { + /* Get the object ID we are going to begin editing. */ + u32 object_id; + R_TRY(dp.Read(std::addressof(object_id))); + R_TRY(dp.Finalize()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* We don't implement transactions, so write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + Result PtpResponder::EndEditObject(PtpDataParser &dp) { + /* Get the object ID we are going to finish editing. */ + u32 object_id; + R_TRY(dp.Read(std::addressof(object_id))); + R_TRY(dp.Finalize()); + + /* Check if we know about the object. If we don't, it's an error. */ + auto * const obj = m_object_database.GetObjectById(object_id); + R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId()); + + /* We don't implement transactions, so write the success response. */ + R_RETURN(this->WriteResponse(PtpResponseCode_Ok)); + } + + +}