Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposed SatnogsTransferService to perform deduplication on Satnogs Telemetry API Result Field #62

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@
<artifactId>commons-codec</artifactId>
<version>1.15</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies>

<build>
Expand Down
62 changes: 58 additions & 4 deletions src/main/java/org/oresat/uniclogs/links/SatnogsTmLink.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
package org.oresat.uniclogs.links;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.yamcs.ConfigurationException;
import org.yamcs.YConfiguration;
import org.yamcs.YamcsServer;
import org.yamcs.tctm.AbstractTmDataLink;
import org.oresat.uniclogs.services.SatnogsTransferService;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;


public class SatnogsTmLink extends AbstractTmDataLink {

SatnogsTransferService tsfrService;

@Override
protected Status connectionStatus() {
// TODO Auto-generated method stub
Expand All @@ -12,14 +26,54 @@ protected Status connectionStatus() {

@Override
protected void doStart() {
// TODO Auto-generated method stub


try {

URL urlArtifacts = new URL("https://db.satnogs.org/api/telemetry/?format=json&satellite=98867");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please pull the parameters, (so far it's just the satellite param in the URI, but we may need more down the line) so that they are configurable in yamcs.oresat*.yml.

See this piece of code on how to do this and the config it corresponds to.

HttpURLConnection conArtifacts = (HttpURLConnection) urlArtifacts.openConnection();
conArtifacts.setRequestProperty("Authorization", "Bearer " + System.getenv("SATNOGSDB_AUTH_TOKEN"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any environment variables should be managed via a higher level and not-baked into the code like this.

We've been going back-and-forth on the best way to do this, but ultimately, we're thinking it may have to be set as an ENV variable name set in yamcs.oresat*.yml and then the code uses that variable for the getenv call.

For now I'd say do this.

conArtifacts.setRequestMethod("GET");
conArtifacts.setRequestProperty("contentType", "application/json; charset=utf-8");

BufferedReader inArtifacts = new BufferedReader(
new InputStreamReader(conArtifacts.getInputStream()));
String inputLineArtifacts;
StringBuilder contentArtifacts = new StringBuilder();
while ((inputLineArtifacts = inArtifacts.readLine()) != null) {
contentArtifacts.append(inputLineArtifacts);
}
inArtifacts.close();

ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(String.valueOf(contentArtifacts));

// Access specific fields from the JSON structure
String contentArtifactsSet = jsonNode.get("results").asText();

tsfrService.getDeduplication(contentArtifactsSet);

} catch (IOException e) {
this.log.error(e.getMessage());
}
}

@Override
protected void doStop() {
// TODO Auto-generated method stub


}

public SatnogsTmLink(String instanceName, YConfiguration config) {
String envName = config.getString("envService");
this.tsfrService = YamcsServer.getServer().getInstance(instanceName).getService(SatnogsTransferService.class, envName);

if (this.tsfrService == null) {
throw new ConfigurationException("Service " + envName + " does not exist or is not of class SatnogsTransferService.");
}
}

public SatnogsTmLink(String instanceName) {
this(instanceName, YConfiguration.emptyConfig());
}

}
122 changes: 122 additions & 0 deletions src/main/java/org/oresat/uniclogs/services/SatnogsTransferService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package org.oresat.uniclogs.services;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file looks like a carbon copy of UniclogsEnvironment.

There may have been a misunderstanding of what the service file is for that needs more in-person explanation.

But the elements that UniclogsEnvironment is not what's needed in a service, if that's what your impression was. Not only that, but we're likely going to axe UniclogsEnvironment anyways as it turns out to be completely unnecessary.

Anyways, the long-short of it is that nearly everything that's in SatnogsTmLink.doStart() is what should be managed by this service instead.

SatnogsTmLink should have the sole job of just checking if it is able to reach db.satnogs.org/api and see if it is alive and reachable, which is what determines the link's state.

SatnogsTransferService will have the responsibility of using SatnogsTmLink to fetch a specific endpoint of the API and then do all of the TLM deduplication.


import org.yamcs.AbstractYamcsService;
import org.yamcs.InitException;
import org.yamcs.YConfiguration;
import org.yamcs.utils.ByteArrayUtils;
import org.yamcs.yarch.Bucket;
import org.yamcs.yarch.YarchDatabase;
import org.yamcs.yarch.YarchDatabaseInstance;
import java.util.HashSet;
import java.util.Set;

import java.io.IOException;

public class SatnogsTransferService extends AbstractYamcsService {
static final String sequenceNumberId = "seqNum";
static final String hmacKeyId = "hmacKey";
String hmacEnvVar;
byte[] hmacKey;
Integer seqNum;
Bucket db;

private byte[] loadHmacFromEnv(String varName) {
return System.getenv(varName).getBytes();
}

private Set<String> processedData;
// processedData set keeps track of unique data

@Override
protected void doStart() {
try {
this.hmacKey = this.loadHmacKey();
this.seqNum = this.loadSeqNum();
this.log.info("Loaded Sequence Number: " + this.seqNum);
processedData = new HashSet<>();
} catch (IOException e) {
this.log.error(e.getMessage());
}
this.notifyStarted();
}

@Override
protected void doStop() {
try {
this.saveSeqNum(this.seqNum);
this.saveHmacKey(this.hmacKey);
} catch (IOException e) {
this.log.error(e.getMessage());
}
this.notifyStopped();
}

public Integer getSeqNum() {
Integer num = this.seqNum;
this.seqNum++;
return num;
}

public Boolean getDeduplication(String contentArtifactsSet) {
// Check if the data is unique (not already processed)
if (!processedData.contains(contentArtifactsSet)){
System.out.println("Processing unique data: " + contentArtifactsSet);
// Add the processed data to the set
this.log.info("DB Satnogs Artifacts: " + processedData.add(contentArtifactsSet));
return true;
} else {
System.out.println("Duplicate data received: " + contentArtifactsSet);
// Handle duplicate data (If the text is present, it won't be added and the method returns false)
return false;
}
}

public byte[] getHmacKey() {
return this.hmacKey;
}

@Override
public void init(String yamcsInstance, String serviceName, YConfiguration config) throws InitException {
super.init(yamcsInstance, serviceName, config);
this.hmacEnvVar = config.getString("hmacEnvVar");
log.info(this.hmacEnvVar);
YarchDatabaseInstance instanceDb = YarchDatabase.getInstance(yamcsInstance);
try {
this.db = instanceDb.getBucket("env");
if (this.db == null) {
this.log.info(String.format("Env not found for %s, creating new env...", yamcsInstance));
this.db = instanceDb.createBucket("env");
}
} catch (IOException e) {
throw new InitException(e.getMessage());
}
}

private Integer loadSeqNum() throws IOException {
byte[] numBytes = db.getObject(sequenceNumberId);
if (numBytes == null) {
this.log.info(String.format("Sequence Number not found for %s, Sequence Number set to 1", yamcsInstance));
this.saveSeqNum(1);
return 1;
}
return ByteArrayUtils.decodeInt(db.getObject(sequenceNumberId), 0);
}

private byte[] loadHmacKey() throws IOException {
byte[] hmac = this.db.getObject(hmacKeyId);
if (hmac == null) {
this.log.info(String.format("Hmac Key not in %s. Loading from env var: %s", yamcsInstance, this.hmacEnvVar));
hmac = this.loadHmacFromEnv(this.hmacEnvVar);
this.saveHmacKey(hmac);
}
return hmac;
}

private void saveSeqNum(Integer seq) throws IOException {
db.putObject(sequenceNumberId, "Integer", null, ByteArrayUtils.encodeInt(seq));
}

private void saveHmacKey(byte[] hmacKey) throws IOException {
db.putObject(hmacKeyId, "bytes", null, hmacKey);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.oresat.uniclogs.tctm;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why this exists.

SatNOGS is a receive-only network, meaning we will never send commands to it and it has no capability to do commanding.

This plus anything else to do with commanding in this PR can be axed.

import org.oresat.uniclogs.services.SatnogsTransferService;
import org.yamcs.ConfigurationException;
import org.yamcs.YConfiguration;
import org.yamcs.YamcsServer;
import org.yamcs.cmdhistory.CommandHistoryPublisher;
import org.yamcs.commanding.PreparedCommand;
import org.yamcs.tctm.CommandPostprocessor;


public class SatnogsCommandPostprocessor implements CommandPostprocessor {
protected CommandHistoryPublisher cmdHistory;
SatnogsTransferService env;

@Override
public byte[] process(PreparedCommand pc) {
Integer seqNum = this.env.getSeqNum();
byte[] hmacKey = this.env.getHmacKey();

EDLPacket cmd = new EDLPacket(pc.getBinary(), seqNum, hmacKey);
byte[] cmdBin = cmd.getBinary();

pc.setBinary(cmdBin);
this.cmdHistory.publish(pc.getCommandId(), "edl-seqnum", seqNum);
this.cmdHistory.publish(pc.getCommandId(), PreparedCommand.CNAME_BINARY, cmdBin);
return cmdBin;
}

@Override
public void setCommandHistoryPublisher(CommandHistoryPublisher commandHistoryListener) {
this.cmdHistory = commandHistoryListener;
}

public SatnogsCommandPostprocessor(String instanceName, YConfiguration config) {
String envName = config.getString("envService");
this.env = YamcsServer.getServer().getInstance(instanceName).getService(SatnogsTransferService.class, envName);

if (this.env == null) {
throw new ConfigurationException("Service " + envName + " does not exist or is not of class SatnogsTransferService.");
}
}

public SatnogsCommandPostprocessor(String instanceName) {
this(instanceName, YConfiguration.emptyConfig());
}

}
53 changes: 53 additions & 0 deletions src/main/java/org/oresat/uniclogs/tctm/SatnogsPacket.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package org.oresat.uniclogs.tctm;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be culled as SatNOGS does not produce a unique TLM frame that we are processing in the Mission Database.


import java.util.Arrays;

import org.apache.commons.codec.digest.HmacAlgorithms;
import org.apache.commons.codec.digest.HmacUtils;
import org.yamcs.TmPacket;
import org.yamcs.xtce.util.HexUtils;


public class SatnogsPacket extends Packet {
private static final Integer SEQ_NUM_OFFSET = 7;

public SatnogsPacket(byte[] packet, Integer seqNum, byte[] hmacSecret) {
//sequence number offset of 7
super(packet, packet.length+36, seqNum, SEQ_NUM_OFFSET);

// set sequence number in packet
this.encodeSeqNum();


// set frame length in packet: C = (Total Number of Octets in the Transfer Frame) − 1
// CRC adds 4, HMAC adds 32 -> (size + (36 - 1))
this.encodeFrameLength(35, 4);
this.addHmac(hmacSecret);

// Add CRC data to packet
this.encodeCrc();

}

private void addHmac(byte[] hmacSecret) {
byte[] hmac = new HmacUtils(HmacAlgorithms.HMAC_SHA_256, hmacSecret).hmac(this.data.array());
log.info(String.format("Hmac bytes: %s", Arrays.toString(hmac)));
this.data.put(hmac);
log.info(String.format("HMAC_SHA_256 (%s) added to packet (seqNum: %d).", HexUtils.hex(hmac), this.sequenceNumber));
}

protected void encodeCrc() {
int crc = this.calcCrc(this.data.array());
log.info(String.format("CRC_32 (%d) added to packet (seqNum: %d).", crc, this.sequenceNumber));
this.data.putInt(crc);
}

public SatnogsPacket(TmPacket tmPacket) {
super(tmPacket.getPacket(), tmPacket.getPacket().length, getSequenceNumber(tmPacket.getPacket(), SEQ_NUM_OFFSET), SEQ_NUM_OFFSET);
}

@Override
boolean validCrc() {
return crc32(0, this.data.array().length);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.oresat.uniclogs.tctm;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be culled as SatNOGS does not produce a unique TLM frame that we are processing in the Mission Database.


import java.util.Date;

import org.yamcs.TmPacket;
import org.yamcs.YConfiguration;
import org.yamcs.tctm.AbstractPacketPreprocessor;

public class SatnogsPacketPreprocessor extends AbstractPacketPreprocessor {

public SatnogsPacketPreprocessor(String yamcsInstance, YConfiguration config) {
super(yamcsInstance, config);
}

public SatnogsPacketPreprocessor(String yamcsInstance) {
super(yamcsInstance, YConfiguration.emptyConfig());
}

@Override
public TmPacket process(TmPacket tmPacket) {
SatnogsPacket packet = new SatnogsPacket(tmPacket);
tmPacket.setSequenceCount(packet.getSeqNum());
if (!packet.validCrc()) {
tmPacket.setInvalid();
this.eventProducer.sendWarning("PACKET_CORRUPT", "Satnogs Response Packet Corrupted");
}
tmPacket.setGenerationTime(new Date().getTime());
tmPacket.setLocalGenTimeFlag();
return tmPacket;
}
}
2 changes: 1 addition & 1 deletion src/main/yamcs/etc/processor.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ realtime:
services:
- class: org.yamcs.StreamTmPacketProvider
args:
streams: ["edl_tm_realtime", "beacon_tm_realtime", "tm_dump"]
streams: ["edl_tm_realtime", "beacon_tm_realtime", "tm_dump", "satnogs_tm_realtime"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Satnogs Telemetry should be on the same caedance of beacon telemetry and thus should use the beacon_tm_realtime processor.

- class: org.yamcs.StreamTcCommandReleaser
- class: org.yamcs.tctm.StreamParameterProvider
- class: org.yamcs.algorithms.AlgorithmManager
Expand Down
Loading