Search in sources :

Example 1 with ScheduledCommandData

use of me.retrodaredevil.solarthing.type.alter.packets.ScheduledCommandData in project solarthing by wildmountainfarms.

the class AlterManagerAction method onUpdate.

@Override
protected void onUpdate() {
    super.onUpdate();
    // Supply queried solarthing_open packets to the security packet receiver, which may execute code to put commands requested to be scheduled in solarthing_alter
    List<StoredPacketGroup> packets = openDatabaseCache.getAllCachedPackets();
    securityPacketReceiver.receivePacketGroups(packets);
    // Check packets in solarthing_alter and see if we need to send a command because of a scheduled command packet
    Instant now = Instant.now();
    List<VersionedPacket<StoredAlterPacket>> alterPackets = alterPacketsProvider.getPackets();
    if (alterPackets == null) {
        LOGGER.info("alterPackets is null. Maybe query failed? Maybe additional info in previous logs?");
    } else {
        for (VersionedPacket<StoredAlterPacket> versionedPacket : alterPackets) {
            AlterPacket packet = versionedPacket.getPacket().getPacket();
            if (packet instanceof ScheduledCommandPacket) {
                ScheduledCommandPacket scheduledCommandPacket = (ScheduledCommandPacket) packet;
                ScheduledCommandData data = scheduledCommandPacket.getData();
                if (data.getScheduledTimeMillis() <= now.toEpochMilli()) {
                    if (now.toEpochMilli() - data.getScheduledTimeMillis() > Duration.ofMinutes(5).toMillis()) {
                        LOGGER.warn("Not going to send a command scheduled for more than 5 minutes ago! data: " + data);
                    } else {
                        doSendCommand(versionedPacket, scheduledCommandPacket);
                    }
                }
            } else if (packet instanceof FlagPacket) {
                FlagPacket flagPacket = (FlagPacket) packet;
                FlagData data = flagPacket.getFlagData();
                ActivePeriod activePeriod = data.getActivePeriod();
                if (activePeriod instanceof TimeRangeActivePeriod) {
                    // We only try to "manage" flags that use this type of ActivePeriod
                    TimeRangeActivePeriod period = (TimeRangeActivePeriod) activePeriod;
                    TimeRange timeRange = period.getTimeRange();
                    Instant endTime = timeRange.getEndTime();
                    if (endTime != null && endTime.compareTo(now) < 0) {
                        // If there is an end time, and it is in the past, then we should remove the flag
                        executorService.execute(() -> {
                            try {
                                database.getAlterDatabase().delete(versionedPacket);
                            } catch (SolarThingDatabaseException e) {
                                LOGGER.error("Could not delete a FlagPacket with an expired time", e);
                            // If we cannot delete it, no need to try again, it'll still be here next time around
                            }
                        });
                    }
                }
            }
        }
    }
}
Also used : FlagPacket(me.retrodaredevil.solarthing.type.alter.packets.FlagPacket) RequestFlagPacket(me.retrodaredevil.solarthing.commands.packets.open.RequestFlagPacket) ImmutableStoredAlterPacket(me.retrodaredevil.solarthing.type.alter.ImmutableStoredAlterPacket) StoredAlterPacket(me.retrodaredevil.solarthing.type.alter.StoredAlterPacket) Instant(java.time.Instant) VersionedPacket(me.retrodaredevil.solarthing.database.VersionedPacket) ActivePeriod(me.retrodaredevil.solarthing.type.alter.flag.ActivePeriod) TimeRangeActivePeriod(me.retrodaredevil.solarthing.type.alter.flag.TimeRangeActivePeriod) ImmutableStoredAlterPacket(me.retrodaredevil.solarthing.type.alter.ImmutableStoredAlterPacket) DeleteAlterPacket(me.retrodaredevil.solarthing.commands.packets.open.DeleteAlterPacket) StoredAlterPacket(me.retrodaredevil.solarthing.type.alter.StoredAlterPacket) AlterPacket(me.retrodaredevil.solarthing.type.alter.AlterPacket) TimeRangeActivePeriod(me.retrodaredevil.solarthing.type.alter.flag.TimeRangeActivePeriod) UpdateConflictSolarThingDatabaseException(me.retrodaredevil.solarthing.database.exception.UpdateConflictSolarThingDatabaseException) SolarThingDatabaseException(me.retrodaredevil.solarthing.database.exception.SolarThingDatabaseException) TimeRange(me.retrodaredevil.solarthing.util.TimeRange) ScheduledCommandPacket(me.retrodaredevil.solarthing.type.alter.packets.ScheduledCommandPacket) FlagData(me.retrodaredevil.solarthing.type.alter.flag.FlagData) StoredPacketGroup(me.retrodaredevil.solarthing.packets.collection.StoredPacketGroup) ScheduledCommandData(me.retrodaredevil.solarthing.type.alter.packets.ScheduledCommandData)

Example 2 with ScheduledCommandData

use of me.retrodaredevil.solarthing.type.alter.packets.ScheduledCommandData in project solarthing by wildmountainfarms.

the class ScheduleCommandChatBotHandler method schedule.

private void schedule(MessageSender messageSender, Instant now, Instant targetTime, AvailableCommand availableCommand) {
    if (now.plus(Duration.ofMinutes(1)).isAfter(targetTime)) {
        messageSender.sendMessage("Cannot schedule a command less than one minute from now.");
        return;
    }
    if (now.plus(Duration.ofHours(72)).isBefore(targetTime)) {
        messageSender.sendMessage("Cannot schedule a command more than 72 hours from now.");
        return;
    }
    UUID uniqueId = UUID.randomUUID();
    PacketCollectionCreator creator = commandHelper.getCommandManager().makeCreator(sourceId, zoneId, // We don't have an InstanceTargetPacket because scheduling commands is not handled by a program with a fragment ID // also look at PacketGroups.parseToTargetPacketGroup() for interpretation without a TargetInstancePacket
    null, new ImmutableScheduleCommandPacket(new ScheduledCommandData(targetTime.toEpochMilli(), availableCommand.getCommandInfo().getName(), Collections.singleton(availableCommand.getFragmentId())), uniqueId), PacketCollectionIdGenerator.Defaults.UNIQUE_GENERATOR);
    PacketCollection packetCollection = creator.create(now);
    messageSender.sendMessage("Scheduling " + availableCommand.getCommandInfo().getDisplayName() + " at " + TimeUtil.instantToSlackDateSeconds(targetTime));
    executorService.execute(() -> {
        boolean success = false;
        try {
            database.getOpenDatabase().uploadPacketCollection(packetCollection, null);
            success = true;
        } catch (SolarThingDatabaseException e) {
            LOGGER.error("Could not upload schedule command packet collection", e);
        }
        if (success) {
            messageSender.sendMessage("Successfully requested schedule. ID: " + uniqueId);
        } else {
            messageSender.sendMessage("Could not upload schedule command request.");
        }
    });
}
Also used : PacketCollection(me.retrodaredevil.solarthing.packets.collection.PacketCollection) UUID(java.util.UUID) PacketCollectionCreator(me.retrodaredevil.solarthing.packets.collection.PacketCollectionCreator) ImmutableScheduleCommandPacket(me.retrodaredevil.solarthing.commands.packets.open.ImmutableScheduleCommandPacket) ScheduledCommandData(me.retrodaredevil.solarthing.type.alter.packets.ScheduledCommandData) SolarThingDatabaseException(me.retrodaredevil.solarthing.database.exception.SolarThingDatabaseException)

Example 3 with ScheduledCommandData

use of me.retrodaredevil.solarthing.type.alter.packets.ScheduledCommandData in project solarthing by wildmountainfarms.

the class CommandOpenPacketsTest method test.

@Test
void test() throws JsonProcessingException {
    PacketTestUtil.testJson(new ImmutableRequestCommandPacket("GEN OFF"), CommandOpenPacket.class, true);
    PacketTestUtil.testJson(new ImmutableRequestFlagPacket(new FlagData("disable_automations", new TimeRangeActivePeriod(TimeRange.create(Instant.parse("2021-10-05T04:53:22.877307Z"), Instant.parse("2021-10-05T04:53:44.305146Z"))))), CommandOpenPacket.class, true);
    PacketTestUtil.testJson(new ImmutableScheduleCommandPacket(new ScheduledCommandData(System.currentTimeMillis(), "GEN OFF", Collections.singleton(1)), UUID.randomUUID()), CommandOpenPacket.class, true);
    PacketTestUtil.testJson(new ImmutableDeleteAlterPacket("my_document_id", new RevisionUpdateToken("46-9ab7d71841380a36e1ec9e367deae36e")), CommandOpenPacket.class, true);
    PacketTestUtil.testJson(new ImmutableRequestHeartbeatPacket(new HeartbeatData("Hourly Mate Ping", "heartbeat.ping.mate.hourly", Duration.ofHours(1), Duration.ofMinutes(5)), UUID.randomUUID()), CommandOpenPacket.class, true);
}
Also used : FlagData(me.retrodaredevil.solarthing.type.alter.flag.FlagData) HeartbeatData(me.retrodaredevil.solarthing.type.event.feedback.HeartbeatData) RevisionUpdateToken(me.retrodaredevil.solarthing.database.couchdb.RevisionUpdateToken) TimeRangeActivePeriod(me.retrodaredevil.solarthing.type.alter.flag.TimeRangeActivePeriod) ScheduledCommandData(me.retrodaredevil.solarthing.type.alter.packets.ScheduledCommandData) Test(org.junit.jupiter.api.Test)

Example 4 with ScheduledCommandData

use of me.retrodaredevil.solarthing.type.alter.packets.ScheduledCommandData in project solarthing by wildmountainfarms.

the class AlterManagerAction method isDocumentMadeByUs.

private boolean isDocumentMadeByUs(Instant now, ScheduledCommandData scheduledCommandData, StoredPacketGroup existingDocument) {
    LargeIntegrityPacket largeIntegrityPacket = (LargeIntegrityPacket) existingDocument.getPackets().stream().filter(p -> p instanceof LargeIntegrityPacket).findAny().orElse(null);
    if (largeIntegrityPacket == null) {
        LOGGER.warn(SolarThingConstants.SUMMARY_MARKER, "The stored document did not have a LargeIntegrity packet. Someone must be trying to stop a scheduled command!");
        return false;
    }
    String sender = largeIntegrityPacket.getSender();
    if (!commandManager.getSender().equals(sender)) {
        LOGGER.info(SolarThingConstants.SUMMARY_MARKER, "The sender of the large integrity packet we are inspecting is not us (" + commandManager.getSender() + "). It is " + sender + ". Might be a malicious actor, might be a bad setup.");
        return false;
    }
    String encryptedHash = largeIntegrityPacket.getEncryptedHash();
    String data;
    try {
        synchronized (CIPHER) {
            data = Decrypt.decrypt(CIPHER, commandManager.getKeyPair().getPublic(), encryptedHash);
        }
    } catch (InvalidKeyException e) {
        throw new RuntimeException("Should be a valid key!", e);
    } catch (DecryptException e) {
        LOGGER.warn(SolarThingConstants.SUMMARY_MARKER, "The document we are inspecting had a large integrity packet with the sender: " + sender + ", but that's us and we couldn't decrypt their payload. Likely a malicious actor", e);
        return false;
    }
    final String[] split = data.split(",", 2);
    LOGGER.debug("decrypted data: " + data);
    if (split.length != 2) {
        LOGGER.warn(SolarThingConstants.SUMMARY_MARKER, "split.length: " + split.length + " split: " + Arrays.asList(split));
        return false;
    }
    String hexMillis = split[0];
    // String message = split[1]; We don't care what the message is. We don't even care enough to check if it matches the payload's hash
    long dateMillis;
    try {
        dateMillis = Long.parseLong(hexMillis, 16);
    } catch (NumberFormatException e) {
        LOGGER.error(SolarThingConstants.SUMMARY_MARKER, "Error parsing hex date millis", e);
        return false;
    }
    if (dateMillis < scheduledCommandData.getScheduledTimeMillis()) {
        LOGGER.warn(SolarThingConstants.SUMMARY_MARKER, "The dateMillis for this is less than the command's scheduled execution time! This must be a malicious actor!");
        return false;
    }
    if (dateMillis > now.toEpochMilli()) {
        LOGGER.warn(SolarThingConstants.SUMMARY_MARKER, "The dateMillis for this is greater than now! This should never ever happen.");
        return false;
    }
    return true;
}
Also used : CommandManager(me.retrodaredevil.solarthing.commands.util.CommandManager) Packet(me.retrodaredevil.solarthing.packets.Packet) Arrays(java.util.Arrays) AlterPacketsProvider(me.retrodaredevil.solarthing.AlterPacketsProvider) LoggerFactory(org.slf4j.LoggerFactory) InstanceTargetPackets(me.retrodaredevil.solarthing.packets.instance.InstanceTargetPackets) VersionedPacket(me.retrodaredevil.solarthing.database.VersionedPacket) LargeIntegrityPacket(me.retrodaredevil.solarthing.packets.security.LargeIntegrityPacket) ActivePeriod(me.retrodaredevil.solarthing.type.alter.flag.ActivePeriod) ImmutableStoredAlterPacket(me.retrodaredevil.solarthing.type.alter.ImmutableStoredAlterPacket) DeleteAlterPacket(me.retrodaredevil.solarthing.commands.packets.open.DeleteAlterPacket) InvalidKeyException(me.retrodaredevil.solarthing.packets.security.crypto.InvalidKeyException) Duration(java.time.Duration) FlagPacket(me.retrodaredevil.solarthing.type.alter.packets.FlagPacket) RequestCommandPacket(me.retrodaredevil.solarthing.commands.packets.open.RequestCommandPacket) DecryptException(me.retrodaredevil.solarthing.packets.security.crypto.DecryptException) Instant(java.time.Instant) IncompatibleUpdateTokenException(me.retrodaredevil.solarthing.database.exception.IncompatibleUpdateTokenException) ZoneId(java.time.ZoneId) Executors(java.util.concurrent.Executors) ExecutionReason(me.retrodaredevil.solarthing.reason.ExecutionReason) List(java.util.List) SolarThingConstants(me.retrodaredevil.solarthing.SolarThingConstants) TimeRange(me.retrodaredevil.solarthing.util.TimeRange) TimeRangeActivePeriod(me.retrodaredevil.solarthing.type.alter.flag.TimeRangeActivePeriod) NoSuchAlgorithmException(java.security.NoSuchAlgorithmException) ScheduledCommandPacket(me.retrodaredevil.solarthing.type.alter.packets.ScheduledCommandPacket) OpenSource(me.retrodaredevil.solarthing.type.open.OpenSource) TargetPacketGroup(me.retrodaredevil.solarthing.packets.collection.TargetPacketGroup) Cipher(javax.crypto.Cipher) ArrayList(java.util.ArrayList) RequestFlagPacket(me.retrodaredevil.solarthing.commands.packets.open.RequestFlagPacket) FlagData(me.retrodaredevil.solarthing.type.alter.flag.FlagData) NoSuchPaddingException(javax.crypto.NoSuchPaddingException) OpenSourceExecutionReason(me.retrodaredevil.solarthing.reason.OpenSourceExecutionReason) ScheduleCommandPacket(me.retrodaredevil.solarthing.commands.packets.open.ScheduleCommandPacket) ExecutorService(java.util.concurrent.ExecutorService) SimpleAction(me.retrodaredevil.action.SimpleAction) ScheduledCommandData(me.retrodaredevil.solarthing.type.alter.packets.ScheduledCommandData) Logger(org.slf4j.Logger) PacketCollectionCreator(me.retrodaredevil.solarthing.packets.collection.PacketCollectionCreator) KeyUtil(me.retrodaredevil.solarthing.packets.security.crypto.KeyUtil) SolarThingDatabase(me.retrodaredevil.solarthing.database.SolarThingDatabase) Decrypt(me.retrodaredevil.solarthing.packets.security.crypto.Decrypt) DatabaseCache(me.retrodaredevil.solarthing.database.cache.DatabaseCache) StoredAlterPacket(me.retrodaredevil.solarthing.type.alter.StoredAlterPacket) ImmutableRequestCommandPacket(me.retrodaredevil.solarthing.commands.packets.open.ImmutableRequestCommandPacket) StoredPacketGroup(me.retrodaredevil.solarthing.packets.collection.StoredPacketGroup) SecurityPacketReceiver(me.retrodaredevil.solarthing.program.SecurityPacketReceiver) AlterPacket(me.retrodaredevil.solarthing.type.alter.AlterPacket) UpdateConflictSolarThingDatabaseException(me.retrodaredevil.solarthing.database.exception.UpdateConflictSolarThingDatabaseException) PublicKeyLookUp(me.retrodaredevil.solarthing.packets.security.crypto.PublicKeyLookUp) Collections(java.util.Collections) PacketCollection(me.retrodaredevil.solarthing.packets.collection.PacketCollection) CommandOpenPacket(me.retrodaredevil.solarthing.commands.packets.open.CommandOpenPacket) SolarThingDatabaseException(me.retrodaredevil.solarthing.database.exception.SolarThingDatabaseException) LargeIntegrityPacket(me.retrodaredevil.solarthing.packets.security.LargeIntegrityPacket) InvalidKeyException(me.retrodaredevil.solarthing.packets.security.crypto.InvalidKeyException) DecryptException(me.retrodaredevil.solarthing.packets.security.crypto.DecryptException)

Example 5 with ScheduledCommandData

use of me.retrodaredevil.solarthing.type.alter.packets.ScheduledCommandData in project solarthing by wildmountainfarms.

the class AlterManagerAction method doSendCommand.

private void doSendCommand(VersionedPacket<StoredAlterPacket> versionedPacket, ScheduledCommandPacket scheduledCommandPacket) {
    LOGGER.info(SolarThingConstants.SUMMARY_MARKER, "Sending command from data: " + scheduledCommandPacket.getData());
    ScheduledCommandData data = scheduledCommandPacket.getData();
    RequestCommandPacket requestCommandPacket = new ImmutableRequestCommandPacket(data.getCommandName());
    // Having a document ID based off of the StoredAlterPacket's _id helps make sure we don't process it twice in case we are unable to delete it.
    // -- if there's an update conflict while uploading, we know we already processed it
    String documentId = "scheduled-request-" + versionedPacket.getPacket().getDbId();
    PacketCollectionCreator creator = commandManager.makeCreator(sourceId, zoneId, InstanceTargetPackets.create(data.getTargetFragmentIds()), requestCommandPacket, zonedDateTime -> documentId);
    executorService.execute(() -> {
        Instant uploadingNow = Instant.now();
        PacketCollection packetCollection = creator.create(uploadingNow);
        boolean shouldDeleteAlter = false;
        try {
            database.getOpenDatabase().uploadPacketCollection(packetCollection, null);
            LOGGER.info(SolarThingConstants.SUMMARY_MARKER, "Successfully uploaded packet collection that schedules command from data: " + scheduledCommandPacket.getData() + " document ID: " + documentId);
            shouldDeleteAlter = true;
        } catch (UpdateConflictSolarThingDatabaseException e) {
            LOGGER.error("Got update conflict exception while uploading document ID: " + documentId + ". Will inspect existing document and overwrite if it's a malicious actor...", e);
            VersionedPacket<StoredPacketGroup> existingDocument = null;
            try {
                existingDocument = database.getOpenDatabase().getPacketCollection(documentId);
            } catch (SolarThingDatabaseException ex) {
                LOGGER.error("Could not retrieve document with document ID: " + documentId, ex);
            }
            if (existingDocument != null) {
                if (isDocumentMadeByUs(uploadingNow, data, existingDocument.getPacket())) {
                    LOGGER.info(SolarThingConstants.SUMMARY_MARKER, "False alarm everyone. The packet in the database was made by us and its timestamp is reasonable. document ID: " + documentId);
                    shouldDeleteAlter = true;
                } else {
                    LOGGER.warn(SolarThingConstants.SUMMARY_MARKER, "The packet in the database with document ID: " + documentId + " was not made by us. Could be a malicious actor. We will overwrite that packet.");
                    try {
                        database.getOpenDatabase().uploadPacketCollection(packetCollection, existingDocument.getUpdateToken());
                        shouldDeleteAlter = true;
                    } catch (SolarThingDatabaseException ex) {
                        LOGGER.error("Could not overwrite malicious packet. Will likely try again. document ID: " + documentId, ex);
                    }
                }
            }
        } catch (SolarThingDatabaseException e) {
            LOGGER.error("Failed to upload our request command packet. documentId: " + documentId, e);
        }
        if (shouldDeleteAlter) {
            try {
                database.getAlterDatabase().delete(versionedPacket);
            } catch (SolarThingDatabaseException e) {
                LOGGER.error("Error while deleting an alter document. document ID: " + versionedPacket.getPacket().getDbId() + " update token: " + versionedPacket.getUpdateToken(), e);
            }
        }
    });
}
Also used : RequestCommandPacket(me.retrodaredevil.solarthing.commands.packets.open.RequestCommandPacket) ImmutableRequestCommandPacket(me.retrodaredevil.solarthing.commands.packets.open.ImmutableRequestCommandPacket) PacketCollection(me.retrodaredevil.solarthing.packets.collection.PacketCollection) Instant(java.time.Instant) UpdateConflictSolarThingDatabaseException(me.retrodaredevil.solarthing.database.exception.UpdateConflictSolarThingDatabaseException) VersionedPacket(me.retrodaredevil.solarthing.database.VersionedPacket) ImmutableRequestCommandPacket(me.retrodaredevil.solarthing.commands.packets.open.ImmutableRequestCommandPacket) ScheduledCommandData(me.retrodaredevil.solarthing.type.alter.packets.ScheduledCommandData) PacketCollectionCreator(me.retrodaredevil.solarthing.packets.collection.PacketCollectionCreator) UpdateConflictSolarThingDatabaseException(me.retrodaredevil.solarthing.database.exception.UpdateConflictSolarThingDatabaseException) SolarThingDatabaseException(me.retrodaredevil.solarthing.database.exception.SolarThingDatabaseException)

Aggregations

ScheduledCommandData (me.retrodaredevil.solarthing.type.alter.packets.ScheduledCommandData)7 VersionedPacket (me.retrodaredevil.solarthing.database.VersionedPacket)5 SolarThingDatabaseException (me.retrodaredevil.solarthing.database.exception.SolarThingDatabaseException)5 UpdateConflictSolarThingDatabaseException (me.retrodaredevil.solarthing.database.exception.UpdateConflictSolarThingDatabaseException)4 AlterPacket (me.retrodaredevil.solarthing.type.alter.AlterPacket)4 StoredAlterPacket (me.retrodaredevil.solarthing.type.alter.StoredAlterPacket)4 FlagData (me.retrodaredevil.solarthing.type.alter.flag.FlagData)4 ScheduledCommandPacket (me.retrodaredevil.solarthing.type.alter.packets.ScheduledCommandPacket)4 Instant (java.time.Instant)3 ArrayList (java.util.ArrayList)3 DeleteAlterPacket (me.retrodaredevil.solarthing.commands.packets.open.DeleteAlterPacket)3 ImmutableRequestCommandPacket (me.retrodaredevil.solarthing.commands.packets.open.ImmutableRequestCommandPacket)3 RequestCommandPacket (me.retrodaredevil.solarthing.commands.packets.open.RequestCommandPacket)3 RequestFlagPacket (me.retrodaredevil.solarthing.commands.packets.open.RequestFlagPacket)3 Packet (me.retrodaredevil.solarthing.packets.Packet)3 PacketCollection (me.retrodaredevil.solarthing.packets.collection.PacketCollection)3 PacketCollectionCreator (me.retrodaredevil.solarthing.packets.collection.PacketCollectionCreator)3 ImmutableStoredAlterPacket (me.retrodaredevil.solarthing.type.alter.ImmutableStoredAlterPacket)3 FlagPacket (me.retrodaredevil.solarthing.type.alter.packets.FlagPacket)3 List (java.util.List)2