/* Copyright (C) 2023-2024 José Rebelo This file is part of Gadgetbridge. Gadgetbridge is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gadgetbridge is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.services; import android.widget.Toast; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.ZeppOsSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.huami.zeppos.AbstractZeppOsService; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; public class ZeppOsFtpServerService extends AbstractZeppOsService { private static final Logger LOG = LoggerFactory.getLogger(ZeppOsFtpServerService.class); private static final short ENDPOINT = 0x0006; public static final byte FTP_CMD_START = 0x01; public static final byte FTP_CMD_STOP = 0x02; public static final byte FTP_CMD_INFO = 0x03; public static final byte FTP_INFO_STOP = 0x00; public static final byte FTP_INFO_STARTED = 0x02; private Callback mCallback = null; public ZeppOsFtpServerService(final ZeppOsSupport support) { super(support, true); } @Override public short getEndpoint() { return ENDPOINT; } @Override public void handlePayload(final byte[] payload) { switch (payload[0]) { case FTP_CMD_INFO: switch (payload[1]) { case FTP_INFO_STOP: LOG.info("FTP Server stopped"); if (mCallback != null) { mCallback.onFtpServerStop(); } else { // If there's no callback, show a toast (eg. used from developer options) GB.toast("FTP Server stopped", Toast.LENGTH_SHORT, GB.INFO); } // TODO: The toast + preference update should not be here final GBDeviceEventUpdatePreferences wifiStoppedEvent = new GBDeviceEventUpdatePreferences() .withPreference(DeviceSettingsPreferenceConst.FTP_SERVER_STATUS, "Stopped"); getSupport().evaluateGBDeviceEvent(wifiStoppedEvent); return; case FTP_INFO_STARTED: final String address = StringUtils.untilNullTerminator(payload, 2); if (address == null) { LOG.error("Unable to parse address from FTP info payload"); return; } final String username = StringUtils.untilNullTerminator(payload, 2 + address.length() + 1); if (username == null) { LOG.error("Unable to parse username from FTP info payload"); return; } LOG.info("FTP Server started, address = {}, username = {}", address, username); if (mCallback != null) { mCallback.onFtpServerStart(address, username); } else { // If there's no callback, show a toast (eg. used from developer options) GB.toast("FTP Server started", Toast.LENGTH_SHORT, GB.INFO); } // TODO: The toast + preference update should not be here final GBDeviceEventUpdatePreferences wifiStartedEvent = new GBDeviceEventUpdatePreferences() .withPreference(DeviceSettingsPreferenceConst.FTP_SERVER_ADDRESS, address) .withPreference(DeviceSettingsPreferenceConst.FTP_SERVER_USERNAME, username) .withPreference(DeviceSettingsPreferenceConst.FTP_SERVER_STATUS, "Started"); getSupport().evaluateGBDeviceEvent(wifiStartedEvent); return; default: LOG.warn("Unexpected FTP info byte {}", String.format("0x%02x", payload[1])); break; } return; default: LOG.warn("Unexpected FTP byte {}", String.format("0x%02x", payload[0])); } } @Override public boolean onSendConfiguration(final String config, final Prefs prefs) { switch (config) { case DeviceSettingsPreferenceConst.FTP_SERVER_START: startFtpServer(prefs.getString(DeviceSettingsPreferenceConst.FTP_SERVER_ROOT_DIR, "")); return true; case DeviceSettingsPreferenceConst.FTP_SERVER_STOP: stopFtpServer(); return true; case DeviceSettingsPreferenceConst.FTP_SERVER_ROOT_DIR: case DeviceSettingsPreferenceConst.FTP_SERVER_ADDRESS: case DeviceSettingsPreferenceConst.FTP_SERVER_USERNAME: case DeviceSettingsPreferenceConst.FTP_SERVER_STATUS: // Ignore preferences that are not reloadable return true; } return false; } @Override public void initialize(final TransactionBuilder builder) { } public void setCallback(final Callback callback) { mCallback = callback; } public void removeCallback() { mCallback = null; } public void startFtpServer(final String rootDir) { LOG.info("Starting FTP Server, rootDir={}", rootDir); final ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { baos.write(FTP_CMD_START); if (!StringUtils.isNullOrEmpty(rootDir)) { baos.write(rootDir.getBytes(StandardCharsets.UTF_8)); baos.write(0); } } catch (final Exception e) { LOG.error("Failed to create command", e); return; } write("start ftp server", baos.toByteArray()); } public void stopFtpServer() { LOG.info("Stopping FTP Server"); write("stop ftp server", FTP_CMD_STOP); } public interface Callback { void onFtpServerStart(String address, String username); void onFtpServerStop(); } }