fix: close STEP-078 V4 bridge guard audit
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled

This commit is contained in:
OpenAI Codex
2026-09-13 17:14:50 +08:00
parent 73779b89bc
commit 75a974e775
15 changed files with 178 additions and 9 deletions

View File

@@ -37,6 +37,7 @@
#include <mbgl/style/filter.hpp>
#include <mbgl/renderer/query.hpp>
#include <mbgl/particle/field/field_profile_contract_json.hpp>
#include <mbgl/particle/ingress/encoded_field_transport.hpp>
// Java -> C++ conversion
#include "style/android_conversion.hpp"
@@ -1425,7 +1426,7 @@ jni::jboolean NativeMapView::controlRasterArraySourceTimeline(JNIEnv& env,
jni::jint NativeMapView::submitRasterArraySourceEncodedField(JNIEnv& env, const jni::String& sourceId, const jni::String& fieldKey, const jni::String& fieldId, jni::jlong generation, jni::jlong dataVersion, const jni::String& contractJson, const jni::Array<jbyte>& qbin) {
const auto id=jni::Make<std::string>(env,sourceId); auto* source=dynamic_cast<mbgl::style::RasterArraySource*>(map->getStyle().getSource(id));
if(!source) return 0; // bridge-layer SOURCE_MISSING sentinel, never a Core result
if(generation<=0 || dataVersion<0 || !qbin) return static_cast<jni::jint>(mbgl::style::RasterArrayEncodedFieldSubmitResult::RejectedDecode);
if(generation<=0 || dataVersion<0 || !qbin || !mbgl::particle::ingress::isValidEncodedFieldTransportSize(qbin.Length(env))) return static_cast<jni::jint>(mbgl::style::RasterArrayEncodedFieldSubmitResult::RejectedDecode);
const auto contract=mbgl::particle::parseResolvedFieldProfileContractJson(jni::Make<std::string>(env,contractJson));
if(!contract) return static_cast<jni::jint>(mbgl::style::RasterArrayEncodedFieldSubmitResult::RejectedContract);
std::vector<uint8_t> bytes(qbin.Length(env)); if(!bytes.empty()) jni::GetArrayRegion(env,*qbin,0,bytes.size(),reinterpret_cast<jbyte*>(bytes.data()));

View File

@@ -7,6 +7,8 @@ import android.os.Handler;
import android.os.Looper;
import java.util.List;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Main-thread, low-frequency Host facade for one raster-array weather runtime.
@@ -86,20 +88,28 @@ public final class WeatherRuntime {
/** Isolated V4 ingress: it deliberately does not mutate V3 state/cadence/timeline. */
public EncodedFieldSubmitResult submitEncodedField(@NonNull String fieldKey, @NonNull String fieldId, long generation, long dataVersion, @NonNull String contractJson, @NonNull byte[] qbin) {
requireAttached(); requireEnabled();
if (fieldKey.isEmpty() || fieldId.isEmpty() || generation <= 0 || dataVersion < 0 || contractJson.isEmpty() || qbin.length == 0) throw new IllegalArgumentException("invalid encoded field argument");
if (!WeatherRuntimeV4Guards.hasValidEncodedIdUtf8Length(fieldKey) || !WeatherRuntimeV4Guards.hasValidEncodedIdUtf8Length(fieldId) || !WeatherRuntimeV4Guards.hasValidEncodedContractUtf8Length(contractJson) || !hasValidEncodedContractProfileId(contractJson) || generation <= 0 || dataVersion < 0 || !WeatherRuntimeV4Guards.hasValidEncodedQbin(qbin)) throw new IllegalArgumentException("invalid encoded field argument");
int code = map.submitRasterArraySourceEncodedField(config.sourceId, fieldKey, fieldId, generation, dataVersion, contractJson, qbin);
if (code == 0) throw new IllegalStateException("SOURCE_MISSING");
return EncodedFieldSubmitResult.fromCode(code);
}
public CommandResult clearEncodedField(@NonNull String fieldKey) { requireAttached(); return fieldKey.isEmpty() ? CommandResult.REJECTED : encodedCommand(map.clearRasterArraySourceEncodedField(config.sourceId, fieldKey)); }
private static boolean hasValidEncodedContractProfileId(String contractJson) {
if (contractJson == null) return false;
try {
return WeatherRuntimeV4Guards.hasValidEncodedIdUtf8Length(new JSONObject(contractJson).optString("profileID", ""));
} catch (JSONException exception) {
return false;
}
}
public CommandResult clearEncodedField(@NonNull String fieldKey) { requireAttached(); return !WeatherRuntimeV4Guards.hasValidEncodedIdUtf8Length(fieldKey) ? CommandResult.REJECTED : encodedCommand(map.clearRasterArraySourceEncodedField(config.sourceId, fieldKey)); }
public CommandResult clearAllEncodedFields() { requireAttached(); return encodedCommand(map.clearAllRasterArraySourceEncodedFields(config.sourceId)); }
/** V4 binding intent; unit and all mode selectors reach Core unchanged. */
public CommandResult setEncodedColorBinding(@NonNull String fieldKey, int semanticID, int groupID, int valueClass, int expectedUnitID) {
requireAttached(); return encodedCommand(map.setRasterArraySourceEncodedColorBinding(config.sourceId, fieldKey, semanticID, groupID, valueClass, expectedUnitID));
requireAttached(); return !WeatherRuntimeV4Guards.hasValidEncodedIdUtf8Length(fieldKey) ? CommandResult.REJECTED : encodedCommand(map.setRasterArraySourceEncodedColorBinding(config.sourceId, fieldKey, semanticID, groupID, valueClass, expectedUnitID));
}
/** ABI reserves all selector slots: inactive slots must be exactly (0, 0). */
public CommandResult setEncodedParticleBinding(@NonNull String fieldKey, int mode, int uSemanticID, int uGroupID, int vSemanticID, int vGroupID, int directionSemanticID, int directionGroupID, int expectedUnitID) {
requireAttached(); return encodedCommand(map.setRasterArraySourceEncodedParticleBinding(config.sourceId, fieldKey, mode, uSemanticID, uGroupID, vSemanticID, vGroupID, directionSemanticID, directionGroupID, expectedUnitID));
requireAttached(); return !WeatherRuntimeV4Guards.hasValidEncodedIdUtf8Length(fieldKey) ? CommandResult.REJECTED : encodedCommand(map.setRasterArraySourceEncodedParticleBinding(config.sourceId, fieldKey, mode, uSemanticID, uGroupID, vSemanticID, vGroupID, directionSemanticID, directionGroupID, expectedUnitID));
}
public CommandResult setEncodedForecastTime(long timestamp) { requireAttached(); return timestamp < 0 ? CommandResult.REJECTED : encodedCommand(map.setRasterArraySourceEncodedForecastTime(config.sourceId, timestamp)); }
/** State and speed intentionally remain independent V4 Core commands. */

View File

@@ -0,0 +1,27 @@
package org.maplibre.android.maps;
import java.nio.charset.StandardCharsets;
/** Small transport guards shared by Java facade checks; they do not define QBIN decoding. */
final class WeatherRuntimeV4Guards {
static final int MAX_ENCODED_ID_UTF8_BYTES = 64;
static final int MAX_CONTRACT_UTF8_BYTES = 256 * 1024;
static final int MAX_ENCODED_QBIN_BYTES = 64 * 1024 * 1024;
private WeatherRuntimeV4Guards() {}
static boolean hasValidEncodedIdUtf8Length(String value) {
if (value == null) return false;
final int length = value.getBytes(StandardCharsets.UTF_8).length;
return length >= 1 && length <= MAX_ENCODED_ID_UTF8_BYTES;
}
static boolean hasValidEncodedQbin(byte[] qbin) {
return qbin != null && qbin.length >= 1 && qbin.length <= MAX_ENCODED_QBIN_BYTES;
}
static boolean hasValidEncodedContractUtf8Length(String contractJson) {
return contractJson != null && !contractJson.isEmpty()
&& contractJson.getBytes(StandardCharsets.UTF_8).length <= MAX_CONTRACT_UTF8_BYTES;
}
}

View File

@@ -0,0 +1,17 @@
package org.maplibre.android.maps;
/** JDK-only directed guard test; Android facade compilation remains a later Android validation. */
public final class WeatherRuntimeV4GuardsTest {
public static void main(String[] args) {
require(WeatherRuntimeV4Guards.hasValidEncodedIdUtf8Length(repeat("a", 64)));
require(!WeatherRuntimeV4Guards.hasValidEncodedIdUtf8Length(repeat("风", 33)));
require(WeatherRuntimeV4Guards.hasValidEncodedQbin(new byte[64 * 1024 * 1024]));
require(!WeatherRuntimeV4Guards.hasValidEncodedQbin(new byte[0]));
require(!WeatherRuntimeV4Guards.hasValidEncodedQbin(new byte[64 * 1024 * 1024 + 1]));
require(WeatherRuntimeV4Guards.hasValidEncodedContractUtf8Length("{}"));
require(!WeatherRuntimeV4Guards.hasValidEncodedContractUtf8Length("a".repeat(256 * 1024 + 1)));
}
private static String repeat(String value, int count) { return value.repeat(count); }
private static void require(boolean value) { if (!value) throw new AssertionError("V4 transport guard failed"); }
}