List<ChunkInfo> getChunksToStitch(BlobProperties stitchedBlobProperties, JSONObject stitchRequestJson)
        throws RestServiceException {
      String reservedMetadataBlobId = null;
      List<String> signedChunkIds = StitchRequestSerDe.fromJson(stitchRequestJson);
      if (signedChunkIds.isEmpty()) {
        throw new RestServiceException("Must provide at least one ID in stitch request",
            RestServiceErrorCode.MissingArgs);
      }
      List<ChunkInfo> chunksToStitch = new ArrayList<>(signedChunkIds.size());
      String expectedSession = null;
      long totalStitchedBlobSize = 0;
      for (String signedChunkId : signedChunkIds) {
        signedChunkId =
            RequestPath.parse(signedChunkId, Collections.emptyMap(), frontendConfig.pathPrefixesToRemove, clusterName)
                .getOperationOrBlobId(false);
        if (!idSigningService.isIdSigned(signedChunkId)) {
          throw new RestServiceException("All chunks IDs must be signed: " + signedChunkId,
              RestServiceErrorCode.BadRequest);
        }
        Pair<String, Map<String, String>> idAndMetadata = idSigningService.parseSignedId(signedChunkId);
        String blobId = idAndMetadata.getFirst();
        Map<String, String> metadata = idAndMetadata.getSecond();

        expectedSession = RestUtils.verifyChunkUploadSession(metadata, expectedSession);
        @SuppressWarnings("ConstantConditions")
        long chunkSizeBytes = RestUtils.getLongHeader(metadata, RestUtils.Headers.BLOB_SIZE, true);

        totalStitchedBlobSize += chunkSizeBytes;
        // Expiration time is sent to the router, but not verified in this handler. The router is responsible for making
        // checks related to internal ambry requirements, like making sure that the chunks do not expire before the
        // metadata blob.
        @SuppressWarnings("ConstantConditions")
        long expirationTimeMs = RestUtils.getLongHeader(metadata, EXPIRATION_TIME_MS_KEY, true);
        verifyChunkAccountAndContainer(blobId, stitchedBlobProperties);

        reservedMetadataBlobId = getAndVerifyReservedMetadataBlobId(metadata, reservedMetadataBlobId, blobId);
        chunksToStitch.add(new ChunkInfo(blobId, chunkSizeBytes, expirationTimeMs, reservedMetadataBlobId));
      }
      //the actual blob size for stitched blob is the sum of all the chunk sizes
      restResponseChannel.setHeader(RestUtils.Headers.BLOB_SIZE, totalStitchedBlobSize);
      return chunksToStitch;
    }
Parse and verify the signed chunk IDs in the body of a stitch request.
@param stitchedBlobProperties the {@link BlobProperties} for the final stitched blob.
@param stitchRequestJson the {@link JSONObject} from the stitch request body.
@return a list of chunks to stitch that can be provided to the router.
@throws RestServiceException
ambry-frontend/src/main/java/com/github/ambry/frontend/PostBlobHandler.java