@@ -142,23 +142,33 @@ impl Manifest {
142142 return Err ( "Manifest has no layers" . to_string ( ) ) ;
143143 }
144144
145+ let is_flashable =
146+ |l : & & Descriptor | l. media_type . starts_with ( "application/vnd.automotive.disk" ) ;
147+
145148 if m. layers . len ( ) == 1 {
146- return Ok ( & m. layers [ 0 ] ) ;
149+ let layer = & m. layers [ 0 ] ;
150+ if is_flashable ( & & layer) {
151+ return Ok ( layer) ;
152+ }
153+ return Err ( format ! (
154+ "Single layer has non-flashable media type: {}" ,
155+ layer. media_type
156+ ) ) ;
147157 }
148158
149- // If artifactType is set, find the layer matching it
159+ // If artifactType is set, find the layer matching it (must also be flashable)
150160 if let Some ( ref artifact_type) = m. artifact_type {
151- if let Some ( layer) = m. layers . iter ( ) . find ( |l| l. media_type == * artifact_type) {
161+ if let Some ( layer) = m
162+ . layers
163+ . iter ( )
164+ . find ( |l| l. media_type == * artifact_type && is_flashable ( l) )
165+ {
152166 return Ok ( layer) ;
153167 }
154168 }
155169
156170 // Fall back to the first disk image layer
157- if let Some ( layer) = m
158- . layers
159- . iter ( )
160- . find ( |l| l. media_type . starts_with ( "application/vnd.automotive.disk" ) )
161- {
171+ if let Some ( layer) = m. layers . iter ( ) . find ( |l| is_flashable ( l) ) {
162172 return Ok ( layer) ;
163173 }
164174
@@ -279,6 +289,113 @@ mod tests {
279289 }
280290 }
281291
292+ #[ test]
293+ fn test_single_flashable_layer ( ) {
294+ let json = r#"{
295+ "schemaVersion": 2,
296+ "config": {
297+ "mediaType": "application/vnd.oci.image.config.v1+json",
298+ "digest": "sha256:config123",
299+ "size": 100
300+ },
301+ "layers": [
302+ {
303+ "mediaType": "application/vnd.automotive.disk.raw",
304+ "digest": "sha256:disk123",
305+ "size": 9999
306+ }
307+ ]
308+ }"# ;
309+ let manifest = Manifest :: parse ( json. as_bytes ( ) , None ) . unwrap ( ) ;
310+ let layer = manifest. get_single_layer ( ) . unwrap ( ) ;
311+ assert_eq ! ( layer. digest, "sha256:disk123" ) ;
312+ }
313+
314+ #[ test]
315+ fn test_single_non_disk_layer_rejected ( ) {
316+ let json = r#"{
317+ "schemaVersion": 2,
318+ "config": {
319+ "mediaType": "application/vnd.oci.image.config.v1+json",
320+ "digest": "sha256:config123",
321+ "size": 100
322+ },
323+ "layers": [
324+ {
325+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
326+ "digest": "sha256:layer123",
327+ "size": 5678
328+ }
329+ ]
330+ }"# ;
331+ let manifest = Manifest :: parse ( json. as_bytes ( ) , None ) . unwrap ( ) ;
332+ let err = manifest. get_single_layer ( ) . unwrap_err ( ) ;
333+ assert ! (
334+ err. contains( "non-flashable" ) ,
335+ "Expected non-flashable error, got: {}" ,
336+ err
337+ ) ;
338+ }
339+
340+ #[ test]
341+ fn test_artifact_type_selection ( ) {
342+ let json = r#"{
343+ "schemaVersion": 2,
344+ "artifactType": "application/vnd.automotive.disk.raw",
345+ "config": {
346+ "mediaType": "application/vnd.oci.image.config.v1+json",
347+ "digest": "sha256:config123",
348+ "size": 100
349+ },
350+ "layers": [
351+ {
352+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
353+ "digest": "sha256:layer1",
354+ "size": 1000
355+ },
356+ {
357+ "mediaType": "application/vnd.automotive.disk.raw",
358+ "digest": "sha256:disk1",
359+ "size": 9999
360+ }
361+ ]
362+ }"# ;
363+ let manifest = Manifest :: parse ( json. as_bytes ( ) , None ) . unwrap ( ) ;
364+ let layer = manifest. get_single_layer ( ) . unwrap ( ) ;
365+ assert_eq ! ( layer. digest, "sha256:disk1" ) ;
366+ }
367+
368+ #[ test]
369+ fn test_no_flashable_layer_error ( ) {
370+ let json = r#"{
371+ "schemaVersion": 2,
372+ "config": {
373+ "mediaType": "application/vnd.oci.image.config.v1+json",
374+ "digest": "sha256:config123",
375+ "size": 100
376+ },
377+ "layers": [
378+ {
379+ "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
380+ "digest": "sha256:layer1",
381+ "size": 1000
382+ },
383+ {
384+ "mediaType": "application/vnd.oci.image.layer.v1.tar+zstd",
385+ "digest": "sha256:layer2",
386+ "size": 2000
387+ }
388+ ]
389+ }"# ;
390+ let manifest = Manifest :: parse ( json. as_bytes ( ) , None ) . unwrap ( ) ;
391+ let err = manifest. get_single_layer ( ) . unwrap_err ( ) ;
392+ assert ! (
393+ err. contains( "No disk image layer found" ) ,
394+ "Expected no-match error, got: {}" ,
395+ err
396+ ) ;
397+ }
398+
282399 #[ test]
283400 fn test_parse_index ( ) {
284401 let json = r#"{
0 commit comments