xtea: validate submitted keys and make them not unique per revision
This commit is contained in:
@@ -24,25 +24,18 @@
|
|||||||
*/
|
*/
|
||||||
package net.runelite.http.service.xtea;
|
package net.runelite.http.service.xtea;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
public class XteaEntry
|
public class XteaEntry
|
||||||
{
|
{
|
||||||
private int rev;
|
|
||||||
private int region;
|
private int region;
|
||||||
|
private Instant time;
|
||||||
|
private int rev;
|
||||||
private int key1;
|
private int key1;
|
||||||
private int key2;
|
private int key2;
|
||||||
private int key3;
|
private int key3;
|
||||||
private int key4;
|
private int key4;
|
||||||
|
|
||||||
public int getRev()
|
|
||||||
{
|
|
||||||
return rev;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRev(int rev)
|
|
||||||
{
|
|
||||||
this.rev = rev;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRegion()
|
public int getRegion()
|
||||||
{
|
{
|
||||||
return region;
|
return region;
|
||||||
@@ -53,6 +46,26 @@ public class XteaEntry
|
|||||||
this.region = region;
|
this.region = region;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Instant getTime()
|
||||||
|
{
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTime(Instant time)
|
||||||
|
{
|
||||||
|
this.time = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRev()
|
||||||
|
{
|
||||||
|
return rev;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRev(int rev)
|
||||||
|
{
|
||||||
|
this.rev = rev;
|
||||||
|
}
|
||||||
|
|
||||||
public int getKey1()
|
public int getKey1()
|
||||||
{
|
{
|
||||||
return key1;
|
return key1;
|
||||||
|
|||||||
@@ -24,10 +24,20 @@
|
|||||||
*/
|
*/
|
||||||
package net.runelite.http.service.xtea;
|
package net.runelite.http.service.xtea;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import net.runelite.cache.IndexType;
|
||||||
|
import net.runelite.cache.fs.jagex.DataFile;
|
||||||
|
import net.runelite.cache.util.Djb2;
|
||||||
import net.runelite.http.api.xtea.XteaKey;
|
import net.runelite.http.api.xtea.XteaKey;
|
||||||
import net.runelite.http.api.xtea.XteaRequest;
|
import net.runelite.http.api.xtea.XteaRequest;
|
||||||
|
import net.runelite.http.service.cache.CacheDAO;
|
||||||
|
import net.runelite.http.service.cache.CacheService;
|
||||||
|
import net.runelite.http.service.cache.beans.ArchiveEntry;
|
||||||
|
import net.runelite.http.service.cache.beans.CacheEntry;
|
||||||
|
import net.runelite.http.service.util.exception.InternalServerErrorException;
|
||||||
|
import net.runelite.http.service.util.exception.NotFoundException;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -48,21 +58,32 @@ public class XteaService
|
|||||||
private static final Logger logger = LoggerFactory.getLogger(XteaService.class);
|
private static final Logger logger = LoggerFactory.getLogger(XteaService.class);
|
||||||
|
|
||||||
private static final String CREATE_SQL = "CREATE TABLE IF NOT EXISTS `xtea` (\n"
|
private static final String CREATE_SQL = "CREATE TABLE IF NOT EXISTS `xtea` (\n"
|
||||||
+ " `rev` int(11) NOT NULL,\n"
|
+ " `id` int(11) NOT NULL AUTO_INCREMENT,\n"
|
||||||
+ " `region` int(11) NOT NULL,\n"
|
+ " `region` int(11) NOT NULL,\n"
|
||||||
|
+ " `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n"
|
||||||
|
+ " `rev` int(11) NOT NULL,\n"
|
||||||
+ " `key1` int(11) NOT NULL,\n"
|
+ " `key1` int(11) NOT NULL,\n"
|
||||||
+ " `key2` int(11) NOT NULL,\n"
|
+ " `key2` int(11) NOT NULL,\n"
|
||||||
+ " `key3` int(11) NOT NULL,\n"
|
+ " `key3` int(11) NOT NULL,\n"
|
||||||
+ " `key4` int(11) NOT NULL,\n"
|
+ " `key4` int(11) NOT NULL,\n"
|
||||||
+ " PRIMARY KEY (`rev`,`region`,`key1`,`key2`,`key3`,`key4`)\n"
|
+ " PRIMARY KEY (`id`),\n"
|
||||||
+ ") ENGINE=InnoDB;";
|
+ " KEY `region` (`region`,`time`)\n"
|
||||||
|
+ ") ENGINE=InnoDB";
|
||||||
|
|
||||||
private final Sql2o sql2o;
|
private final Sql2o sql2o;
|
||||||
|
private final Sql2o cacheSql2o;
|
||||||
|
private final CacheService cacheService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public XteaService(@Qualifier("Runelite SQL2O") Sql2o sql2o)
|
public XteaService(
|
||||||
|
@Qualifier("Runelite SQL2O") Sql2o sql2o,
|
||||||
|
@Qualifier("Runelite Cache SQL2O") Sql2o cacheSql2o,
|
||||||
|
CacheService cacheService
|
||||||
|
)
|
||||||
{
|
{
|
||||||
this.sql2o = sql2o;
|
this.sql2o = sql2o;
|
||||||
|
this.cacheSql2o = cacheSql2o;
|
||||||
|
this.cacheService = cacheService;
|
||||||
|
|
||||||
try (Connection con = sql2o.beginTransaction())
|
try (Connection con = sql2o.beginTransaction())
|
||||||
{
|
{
|
||||||
@@ -71,38 +92,83 @@ public class XteaService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(method = POST)
|
private XteaEntry findLatestXtea(Connection con, int region)
|
||||||
public Object submit(@RequestBody XteaRequest xteaRequest)
|
|
||||||
{
|
{
|
||||||
try (Connection con = sql2o.beginTransaction())
|
return con.createQuery("select region, time, key1, key2, key3, key4 from xtea "
|
||||||
|
+ "where region = :region "
|
||||||
|
+ "order by time desc "
|
||||||
|
+ "limit 1")
|
||||||
|
.addParameter("region", region)
|
||||||
|
.executeAndFetchFirst(XteaEntry.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(method = POST)
|
||||||
|
public void submit(@RequestBody XteaRequest xteaRequest)
|
||||||
|
{
|
||||||
|
try (Connection con = sql2o.beginTransaction();
|
||||||
|
Connection cacheCon = cacheSql2o.open())
|
||||||
{
|
{
|
||||||
Query query = con.createQuery("insert ignore into xtea (rev, region, key1, key2, key3, key4) values (:rev, :region, :key1, :key2, :key3, :key4)");
|
CacheDAO cacheDao = new CacheDAO();
|
||||||
|
CacheEntry cache = cacheDao.findMostRecent(cacheCon);
|
||||||
|
|
||||||
|
if (cache == null)
|
||||||
|
{
|
||||||
|
throw new InternalServerErrorException("No most recent cache");
|
||||||
|
}
|
||||||
|
|
||||||
|
Query query = con.createQuery("insert into xtea (region, rev, key1, key2, key3, key4) "
|
||||||
|
+ "values (:region, :rev, :key1, :key2, :key3, :key4)");
|
||||||
|
|
||||||
for (XteaKey key : xteaRequest.getKeys())
|
for (XteaKey key : xteaRequest.getKeys())
|
||||||
{
|
{
|
||||||
query.addParameter("rev", xteaRequest.getRevision())
|
int region = key.getRegion();
|
||||||
.addParameter("region", key.getRegion())
|
int[] keys = key.getKeys();
|
||||||
.addParameter("key1", key.getKeys()[0])
|
|
||||||
.addParameter("key2", key.getKeys()[1])
|
XteaEntry xteaEntry = findLatestXtea(con, region);
|
||||||
.addParameter("key3", key.getKeys()[2])
|
|
||||||
.addParameter("key4", key.getKeys()[3])
|
if (keys.length != 4)
|
||||||
|
{
|
||||||
|
throw new IllegalArgumentException("Key length must be 4");
|
||||||
|
}
|
||||||
|
|
||||||
|
// already have these?
|
||||||
|
if (xteaEntry != null
|
||||||
|
&& xteaEntry.getKey1() == keys[0]
|
||||||
|
&& xteaEntry.getKey2() == keys[1]
|
||||||
|
&& xteaEntry.getKey3() == keys[2]
|
||||||
|
&& xteaEntry.getKey4() == keys[3])
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!checkKeys(cacheCon, cache, region, keys))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
query.addParameter("region", region)
|
||||||
|
.addParameter("rev", xteaRequest.getRevision())
|
||||||
|
.addParameter("key1", keys[0])
|
||||||
|
.addParameter("key2", keys[1])
|
||||||
|
.addParameter("key3", keys[2])
|
||||||
|
.addParameter("key4", keys[3])
|
||||||
.addToBatch();
|
.addToBatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
query.executeBatch();
|
query.executeBatch();
|
||||||
con.commit();
|
con.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping("/{revision}")
|
@RequestMapping
|
||||||
public List<XteaKey> get(@PathVariable int revision)
|
public List<XteaKey> get()
|
||||||
{
|
{
|
||||||
try (Connection con = sql2o.open())
|
try (Connection con = sql2o.open())
|
||||||
{
|
{
|
||||||
List<XteaEntry> entries = con.createQuery("select * from xtea where rev = :rev")
|
List<XteaEntry> entries = con.createQuery(
|
||||||
.addParameter("rev", revision)
|
"select t1.region, t1.time, t1.rev, t1.key1, t1.key2, t1.key3, t1.key4 from xtea t1 "
|
||||||
|
+ "inner join ( select region,max(time) as time from xtea group by region ) t2 "
|
||||||
|
+ "on t1.region = t2.region and t1.time = t2.time")
|
||||||
.executeAndFetch(XteaEntry.class);
|
.executeAndFetch(XteaEntry.class);
|
||||||
|
|
||||||
return entries.stream()
|
return entries.stream()
|
||||||
@@ -111,6 +177,64 @@ public class XteaService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/{region}")
|
||||||
|
public XteaKey getRegion(@PathVariable int region)
|
||||||
|
{
|
||||||
|
XteaEntry entry;
|
||||||
|
|
||||||
|
try (Connection con = sql2o.open())
|
||||||
|
{
|
||||||
|
entry = con.createQuery("select region, time, rev, key1, key2, key3, key4 from xtea "
|
||||||
|
+ "where region = :region order by time desc limit 1")
|
||||||
|
.addParameter("region", region)
|
||||||
|
.executeAndFetchFirst(XteaEntry.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry == null)
|
||||||
|
{
|
||||||
|
throw new NotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return entryToKey(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkKeys(Connection con, CacheEntry cache, int regionId, int[] keys)
|
||||||
|
{
|
||||||
|
int x = regionId >>> 8;
|
||||||
|
int y = regionId & 0xFF;
|
||||||
|
|
||||||
|
String archiveName = new StringBuilder()
|
||||||
|
.append('l')
|
||||||
|
.append(x)
|
||||||
|
.append('_')
|
||||||
|
.append(y)
|
||||||
|
.toString();
|
||||||
|
int archiveNameHash = Djb2.hash(archiveName);
|
||||||
|
|
||||||
|
CacheDAO cacheDao = new CacheDAO();
|
||||||
|
ArchiveEntry archiveEntry = cacheDao.findArchiveByName(con, cache, IndexType.MAPS, archiveNameHash);
|
||||||
|
if (archiveEntry == null)
|
||||||
|
{
|
||||||
|
throw new InternalServerErrorException("Unable to find archive for region");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] data = cacheService.getArchive(archiveEntry);
|
||||||
|
if (data == null)
|
||||||
|
{
|
||||||
|
throw new InternalServerErrorException("Unable to get archive data");
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
DataFile.decompress(data, keys);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static XteaKey entryToKey(XteaEntry xe)
|
private static XteaKey entryToKey(XteaEntry xe)
|
||||||
{
|
{
|
||||||
XteaKey xteaKey = new XteaKey();
|
XteaKey xteaKey = new XteaKey();
|
||||||
|
|||||||
Reference in New Issue
Block a user