GroupOSCOREGroupCollectionResource.java

 /*******************************************************************************
 * Copyright (c) 2025, RISE AB
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions 
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/
package se.sics.ace.oscore.rs.oscoreGroupManager;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.californium.core.CoapResource;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.CoAP.ResponseCode;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.core.coap.Response;
import org.eclipse.californium.core.server.resources.CoapExchange;
import org.eclipse.californium.core.server.resources.Resource;
import org.eclipse.californium.elements.util.Bytes;

import com.mifmif.common.regex.Generex;
import com.upokecenter.cbor.CBORObject;
import com.upokecenter.cbor.CBORType;

import org.eclipse.californium.cose.AlgorithmID;
import org.eclipse.californium.cose.CoseException;
import org.eclipse.californium.cose.OneKey;
import net.i2p.crypto.eddsa.Utils;
import se.sics.ace.AceException;
import se.sics.ace.Constants;
import se.sics.ace.GroupcommErrors;
import se.sics.ace.GroupcommParameters;
import se.sics.ace.Util;
import se.sics.ace.coap.CoapReq;
import se.sics.ace.oscore.GroupInfo;
import se.sics.ace.oscore.rs.GroupOSCOREValidator;

/**
 * Definition of the Group OSCORE group-collection resource
 */
public class GroupOSCOREGroupCollectionResource extends CoapResource {
	
	private Map<String, GroupOSCOREGroupConfigurationResource> groupConfigurationResources = new HashMap<>();
	
	Resource groupOSCORERootGroupMembership;
	
	private int groupIdPrefixSize;
	
	private Set<CBORObject> usedGroupIdPrefixes = new HashSet<>();
	
	private String prefixMonitorNames;
	
	private String nodeNameSeparator;
	
	private Map<String, GroupInfo> existingGroupInfo = new HashMap<>();
	
	private Map<String, Map<String, Set<Short>>> myScopes;
	
	private GroupOSCOREValidator valid;

    // The map key is the cryptographic curve; the map value is the hex string of the key pair
    private Map<CBORObject, String> gmSigningKeyPairs;
    
    // For the outer map, the map key is the type of authentication credential
    // For the inner map, the map key is the cryptographic curve, while the map value is the hex string of the authentication credential
    private Map<Integer,  Map<CBORObject, String>> gmSigningPublicAuthCred;
    
    // The map key is the cryptographic curve; the map value is the hex string of the key pair
    private Map<CBORObject, String> gmKeyAgreementKeyPairs;
    
    // For the outer map, the map key is the type of authentication credential
    // For the inner map, the map key is the cryptographic curve, while the map value is the hex string of the authentication credential
    private Map<Integer,  Map<CBORObject, String>> gmKeyAgreementPublicAuthCred;
    
    private final static String rootGroupMembershipResourcePath = "ace-group";
    
    private final static String groupCollectionResourcePath = "manage";
    
	private final int maxRandomGroupNameLength = 20;
	private final int maxRandomGroupNameAttempts = 1000;
	private final int maxRandomGroupNameAttemptsPerPattern = 50;

	private String asUri = "";

	/**
	 * Constructor
	 * 
	 * @param resId the resource identifier
	 * @param groupOSCORERootGroupMembership the root group-membership resource
	 * @param groupIdPrefixSize the size in bytes of the Group ID prefixes
	 * @param usedGroupIdPrefixes the set of currently used Group ID prefixes
	 * @param prefixMonitorNames initial part of the node name for monitors
	 * @param nodeNameSeparator for non-monitor members, separator between the
	 *            two components of the node name
	 * @param existingGroupInfo the set of information of the existing OSCORE
	 *            groups
	 * @param gmSigningKeyPairs the signing key pairs of the Group Manager
	 * @param gmSigningPublicAuthCred the signing public authentication
	 *            credentials of the Group Manager
	 * @param gmKeyAgreementKeyPairs the key agreement key pairs of the Group
	 *            Manager
	 * @param gmKeyAgreementPublicAuthCred the key agreement public
	 *            authentication credentials of the Group Manager
	 * @param myScopes the scopes of this OSCORE Group Manager
	 * @param valid the access validator of this OSCORE Group Manager
	 * @param asUri Uri of the AS Token resource
	 */
    public GroupOSCOREGroupCollectionResource(String resId,
    										  Resource groupOSCORERootGroupMembership,
    										  final int groupIdPrefixSize,
    										  Set<CBORObject> usedGroupIdPrefixes,
    										  String prefixMonitorNames,
    										  String nodeNameSeparator,
    										  Map<String, GroupInfo> existingGroupInfo,
    										  Map<CBORObject, String> gmSigningKeyPairs,
    										  Map<Integer,  Map<CBORObject, String>> gmSigningPublicAuthCred,
    										  Map<CBORObject, String> gmKeyAgreementKeyPairs,
    										  Map<Integer,  Map<CBORObject, String>> gmKeyAgreementPublicAuthCred,
    										  Map<String, Map<String, Set<Short>>> myScopes,
			GroupOSCOREValidator valid, String asUri) {
        
        // set resource identifier
        super(resId);
        
        // set display name
        getAttributes().setTitle("Group OSCORE Group Collection Resource " + resId);
     
        this.groupOSCORERootGroupMembership = groupOSCORERootGroupMembership;
        
        this.groupIdPrefixSize = groupIdPrefixSize;
        this.usedGroupIdPrefixes = usedGroupIdPrefixes;
        
        this.prefixMonitorNames = prefixMonitorNames;
        this.nodeNameSeparator = nodeNameSeparator;
        
        this.existingGroupInfo = existingGroupInfo;
        
        this.gmSigningKeyPairs = gmSigningKeyPairs;
        this.gmSigningPublicAuthCred = gmSigningPublicAuthCred;
        this.gmKeyAgreementKeyPairs = gmKeyAgreementKeyPairs;
        this.gmKeyAgreementPublicAuthCred = gmKeyAgreementPublicAuthCred;
        
        this.myScopes = myScopes;
        this.valid = valid;
        
        // TODO: remove
        // ============
        // Force the presence of an already existing group configuration for early testing
        GroupOSCOREGroupConfigurationResource testConf = new GroupOSCOREGroupConfigurationResource(
        													"gp500", CBORObject.NewMap(),
        													this.groupConfigurationResources,
        													this.existingGroupInfo);
        testConf.getConfigurationParameters().Add(GroupcommParameters.GROUP_NAME, CBORObject.FromObject("gp500"));
        this.groupConfigurationResources.put("gp500", testConf);
        // ============
        
    }

    @Override
    public synchronized void handleGET(CoapExchange exchange) {
    	
    	System.out.println("GET request reached the GM at /" + groupCollectionResourcePath);
        
    	// Process the request for retrieving the full list of Group Configurations
    	
    	String subject = null;
    	String errorString = null;
    	
    	Request request = exchange.advanced().getCurrentRequest();
        
        try {
			subject = CoapReq.getInstance(request).getSenderId();
		} catch (AceException e) {
		    System.err.println("Error while retrieving the client identity: " + e.getMessage());
		}
        if (subject == null) {
        	// At this point, this should not really happen, due to the earlier check at the Token Repository
        	errorString = new String("Unauthenticated client tried to get access");
        	System.err.println(errorString);
        	exchange.respond(CoAP.ResponseCode.UNAUTHORIZED, errorString);
        	return;
        }
        
    	// Check that at least one scope entry in the access token allows the "List" admin permission
    	CBORObject[] permissionSetToken = Util.getGroupOSCOREAdminPermissionsFromToken(subject, null);
    	if (permissionSetToken == null) {
        	errorString = new String("Operation not permitted");
    		System.err.println(errorString);
    		exchange.respond(CoAP.ResponseCode.FORBIDDEN, errorString);
    		return;
    	}
    	
    	String auxString = new String("");
    	
    	for (String groupName : this.groupConfigurationResources.keySet()) {
    		boolean selected = false;
    		
    		for (int i = 0; i < permissionSetToken.length; i++) {
    			if (Util.matchingGroupOscoreName(groupName, permissionSetToken[i].get(0))) {
    				try {
        				int permissions = permissionSetToken[i].get(1).AsInt32();
    					if (Util.checkGroupOSCOREAdminPermission(permissions, GroupcommParameters.GROUP_OSCORE_ADMIN_LIST)) {
    						// One match has been found
    						selected = true;
    						break;
    					}
					} catch (AceException e) {
						System.err.println("Error while checking the group name against the group name pattern: " + e.getMessage());
					}
    			}
    		}
    		
    		if (selected == false) {
    			// Move to the next group-configuration resource
    			continue;
    		}
    		
    		// This group configuration has passed the filtering and has been selected
    		if (auxString.equals("") == false) {
    			auxString += ",";
    		}
    		
    		auxString += "<" + request.getURI() + "/" + groupName + ">;rt=\"core.osc.gconf\"";
    	}
    	
    	// Respond to the request for retrieving the full list of Group Configurations

    	Response coapResponse = new Response(CoAP.ResponseCode.CONTENT);

    	if (this.existingGroupInfo.size() != 0) {
        	byte[] responsePayload = auxString.getBytes(Constants.charset);
        	coapResponse.setPayload(responsePayload);
    		coapResponse.getOptions().setContentFormat(MediaTypeRegistry.APPLICATION_LINK_FORMAT);
    	}

    	exchange.respond(coapResponse);

    }
    
    @Override
    public synchronized void handleFETCH(CoapExchange exchange) {

    	System.out.println("FETCH request reached the GM at /" + groupCollectionResourcePath);
        
    	// Process the request for retrieving a list of Group Configurations by filters
    	
    	String subject = null;
    	String errorString = null;
    	
    	Request request = exchange.advanced().getCurrentRequest();
        
        try {
			subject = CoapReq.getInstance(request).getSenderId();
		} catch (AceException e) {
		    System.err.println("Error while retrieving the client identity: " + e.getMessage());
		}
        if (subject == null) {
        	// At this point, this should not really happen, due to the earlier check at the Token Repository
        	errorString = new String("Unauthenticated client tried to get access");
    		System.err.println(errorString);
        	exchange.respond(CoAP.ResponseCode.UNAUTHORIZED, errorString);
        	return;
        }
    	
    	// Check that at least one scope entry in the access token allows the "List" admin permission
    	CBORObject[] permissionSetToken = Util.getGroupOSCOREAdminPermissionsFromToken(subject, null);
    	if (permissionSetToken == null) {
        	errorString = new String("Operation not permitted");
    		System.err.println(errorString);
    		exchange.respond(CoAP.ResponseCode.FORBIDDEN, errorString);
    		return;
    	}
        
    	byte[] requestPayload = exchange.getRequestPayload();
    	
    	if(requestPayload == null || (requestPayload.length == 0)) {
        	errorString = new String("A payload must be present");
    		System.err.println(errorString);
    		exchange.respond(CoAP.ResponseCode.BAD_REQUEST, errorString);
    		return;
    	}
    	
    	if(exchange.getRequestOptions().hasContentFormat() == false ||
    	   exchange.getRequestOptions().getContentFormat() != Constants.APPLICATION_ACE_GROUPCOMM_CBOR) {
        	errorString = new String("The CoAP option Content-Format must be present, with value application/ace-groupcomm+cbor");
    		System.err.println(errorString);
    		exchange.respond(CoAP.ResponseCode.BAD_REQUEST, errorString);
    		return;
    	}

    	CBORObject requestCBOR = CBORObject.DecodeFromBytes(requestPayload);
    	
    	// The payload of the request must be a CBOR Map
    	if (!requestCBOR.getType().equals(CBORType.Map)) {
        	errorString = new String("Invalid payload format");
    		System.err.println(errorString);
			exchange.respond(CoAP.ResponseCode.BAD_REQUEST, errorString);
    		return;
    	}
    	
    	for (CBORObject key : requestCBOR.getKeys()) {
    		boolean error = false;
    		
    		if (!GroupcommParameters.isAdminRequestParameterMeaningful(key, requestCBOR.get(key))) {
    			error = true;
    		}
    		if (!error && key.equals(GroupcommParameters.GROUP_NAME) && requestCBOR.get(key).isTagged()) {
    			 if (!requestCBOR.get(key).HasOneTag() || !requestCBOR.get(key).HasTag(21065)) {
    				 error = true;
    			 }
    		}

    		if (error) {
            	errorString = new String("Invalid format of paramemeter with CBOR abbreviation: " + key.AsInt32());
        		System.err.println(errorString);
    			exchange.respond(CoAP.ResponseCode.BAD_REQUEST, errorString);
    			return;
    		}
    	}
    	
    	String auxString = new String("");
    	
    	for (String groupName : this.groupConfigurationResources.keySet()) {
    		
			boolean selected = false;
    		
		    for (int i = 0; i < permissionSetToken.length; i++) {
		        if (Util.matchingGroupOscoreName(groupName, permissionSetToken[i].get(0))) {
		            try {
			            int permissions = permissionSetToken[i].get(1).AsInt32();
		                if (Util.checkGroupOSCOREAdminPermission(permissions, GroupcommParameters.GROUP_OSCORE_ADMIN_LIST)) {
		                    // One match has been found
		                    selected = true;
		                    break;
		                }
		            } catch (AceException e) {
		                System.err.println("Error while checking the group name against the group name pattern: " + e.getMessage());
		            }
		        }
		    }
		    
		    if (selected == false) {
		        // Move to the next group-configuration resource
		        continue;
		    }
		    
			CBORObject configurationParameters = this.groupConfigurationResources.get(groupName).getConfigurationParameters();
		    
    		// Perform the filtering based on the specified filter criteria
    		for (CBORObject elemKey : requestCBOR.getKeys()) {
    			
    			// The parameter in the filter must be present in the configuration
    			if (configurationParameters.ContainsKey(elemKey) == false) {
    				selected = false;
    				break;
    			}
    			
    			if (elemKey.equals(GroupcommParameters.GROUP_NAME)) {
    				if (!Util.matchingGroupOscoreName(groupName, requestCBOR.get(elemKey))) {
    					selected = false;
    					break;
    				}
    			}
    			else if (requestCBOR.get(elemKey).equals(configurationParameters.get(elemKey)) == false) {
					selected = false;
					break;
    			}
    			
    		}
    		
			if (selected == true) {
    			// This group configuration has passed the filtering and has been selected
	    		if (auxString.equals("") == false) {
	    			auxString += ",";
	    		}
        		auxString += "<" + request.getURI() + "/" + groupName + ">;rt=\"core.osc.gconf\"";
			}

    	}
    	
    	
    	// Respond to the request for retrieving a list of Group Configurations by filters
        
    	Response coapResponse = new Response(CoAP.ResponseCode.CONTENT);

    	if (this.existingGroupInfo.size() != 0) {
        	byte[] responsePayload = auxString.getBytes(Constants.charset);
        	coapResponse.setPayload(responsePayload);
    		coapResponse.getOptions().setContentFormat(MediaTypeRegistry.APPLICATION_LINK_FORMAT);
    	}

    	exchange.respond(coapResponse);

    }
    
    @Override
    public synchronized void handlePOST(CoapExchange exchange) {
        
    	System.out.println("POST request reached the GM at /" + groupCollectionResourcePath);
    	
    	// Process the request for creating a new Group Configuration
    	
    	String subject = null;
    	String errorString = null;
    	
    	Request request = exchange.advanced().getCurrentRequest();
        
        try {
			subject = CoapReq.getInstance(request).getSenderId();
		} catch (AceException e) {
		    System.err.println("Error while retrieving the client identity: " + e.getMessage());
		}
        if (subject == null) {
        	// At this point, this should not really happen, due to the earlier check at the Token Repository
        	errorString = new String("Unauthenticated client tried to get access");
        	System.err.println(errorString);
        	exchange.respond(CoAP.ResponseCode.UNAUTHORIZED, errorString);
        	return;
        }
        
    	// Check that at least one scope entry in the access token allows the "Create" admin permission
        boolean permitted = false;
    	CBORObject[] adminScopeEntries = Util.getGroupOSCOREAdminPermissionsFromToken(subject, null);
    	if (adminScopeEntries == null) {
        	errorString = new String("Operation not permitted");
    		System.err.println(errorString);
    		exchange.respond(CoAP.ResponseCode.FORBIDDEN, errorString);
    		return;
    	}

    	for (int i = 0; i < adminScopeEntries.length; i++) {
    		try {
        		short permissions = (short) adminScopeEntries[i].get(1).AsInt32(); 
        		permitted = Util.checkGroupOSCOREAdminPermission(permissions, GroupcommParameters.GROUP_OSCORE_ADMIN_CREATE);
			} catch (AceException e) {
				System.err.println("Error while verifying the admin permissions: " + e.getMessage());
			}
    		if (permitted) {
    			break;
    		}
    	}
    	if (!permitted) {
        	errorString = new String("Operation not permitted");
    		System.err.println(errorString);
    		exchange.respond(CoAP.ResponseCode.FORBIDDEN, errorString);
    		return;
    	}
    	
    	byte[] requestPayload = exchange.getRequestPayload();
    	
    	if(requestPayload == null || (requestPayload.length == 0)) {
        	errorString = new String("A payload must be present");
    		System.err.println(errorString);
    		exchange.respond(CoAP.ResponseCode.BAD_REQUEST, errorString);
    		return;
    	}
    	
    	if(exchange.getRequestOptions().hasContentFormat() == false ||
    	   exchange.getRequestOptions().getContentFormat() != Constants.APPLICATION_ACE_GROUPCOMM_CBOR) {
        	errorString = new String("The CoAP option Content-Format must be present, with value application/ace-groupcomm+cbor");
    		System.err.println(errorString);
    		exchange.respond(CoAP.ResponseCode.BAD_REQUEST, errorString);
    		return;
    	}

    	CBORObject requestCBOR = CBORObject.DecodeFromBytes(requestPayload);
    	
    	// The payload of the request must be a CBOR Map
    	if (!requestCBOR.getType().equals(CBORType.Map)) {
        	errorString = new String("Invalid payload format");
    		System.err.println(errorString);
			exchange.respond(CoAP.ResponseCode.BAD_REQUEST, errorString);
    		return;
    	}
    	
    	// The payload of the request must include the status parameter 'group_name'
    	if (!requestCBOR.getKeys().contains(GroupcommParameters.GROUP_NAME)) {
    		errorString = new String("The status parameter 'group_name' must be present");
    		System.err.println(errorString);
			exchange.respond(CoAP.ResponseCode.BAD_REQUEST, errorString);
    		return;
    	}
    	
    	// The payload of the request must not include:
    	// - The status parameters 'rt', 'ace_groupcomm_profile', and 'joining_uri'
    	// - The parameters 'conf_filter' and 'app_groups_diff', as not pertaining to this request
    	if (requestCBOR.getKeys().contains(GroupcommParameters.RT) ||
    		requestCBOR.getKeys().contains(GroupcommParameters.ACE_GROUPCOMM_PROFILE) ||
    		requestCBOR.getKeys().contains(GroupcommParameters.JOINING_URI) ||
    		requestCBOR.getKeys().contains(GroupcommParameters.CONF_FILTER) ||
    		requestCBOR.getKeys().contains(GroupcommParameters.APP_GROUPS_DIFF)) {
			errorString = new String("Invalid set of parameters in the request");
    		System.err.println(errorString);
    		exchange.respond(CoAP.ResponseCode.BAD_REQUEST, errorString);
    		return;
    	}
    	
    	// This Group Manager does not support RSA an signature algorithm
		if (requestCBOR.getKeys().contains(GroupcommParameters.SIGN_ALG)) {
    		CBORObject signAlg = requestCBOR.get(GroupcommParameters.SIGN_ALG);
    		if (signAlg.equals(AlgorithmID.RSA_PSS_256.AsCBOR()) ||
    			signAlg.equals(AlgorithmID.RSA_PSS_384.AsCBOR()) ||
    			signAlg.equals(AlgorithmID.RSA_PSS_512.AsCBOR())) {
    			
    		CBORObject myResponse = CBORObject.NewMap();
			errorString = new String("RSA is not supported as signature algorithm");
    		
    		CBORObject aceGroupcommError = CBORObject.NewMap();
    		aceGroupcommError.Add(0, GroupcommErrors.UNSUPPORTED_GROUP_CONF);
    		myResponse.Add(Constants.PROBLEM_DETAIL_ACE_GROUPCOMM_ERROR, aceGroupcommError);
    		myResponse.Add(Constants.PROBLEM_DETAIL_KEY_TITLE, GroupcommErrors.DESCRIPTION[GroupcommErrors.UNSUPPORTED_GROUP_CONF]);
    		myResponse.Add(Constants.PROBLEM_DETAIL_KEY_DETAIL, errorString);
    		
    		System.err.println(errorString);
    		exchange.respond(CoAP.ResponseCode.SERVICE_UNAVAILABLE,
    						 myResponse.EncodeToBytes(),
    						 Constants.APPLICATION_CONCISE_PROBLEM_DETAILS_CBOR);
    		
    		return;
			}
		}
    	
    	for (CBORObject key : requestCBOR.getKeys()) {
    		if (!GroupcommParameters.isAdminRequestParameterMeaningful(key, requestCBOR.get(key))) {
    			errorString = new String("Malformed or unrecognized paramemeter with CBOR abbreviation: " + key.AsInt32());
    			System.err.println(errorString);
    			exchange.respond(CoAP.ResponseCode.BAD_REQUEST, errorString);
    			return;
    		}
    	}

    	CBORObject ret = createNewGroupConfiguration(request, adminScopeEntries);
    	    	
    	// Respond to the request for creating a new Group Configuration
        
    	ResponseCode responseCode = CoAP.ResponseCode.valueOf(ret.get(0).AsInt32());
    	Response coapResponse = new Response(responseCode);
    	if (ret.get(1) != null) {
    		int contentFormat = ret.get(1).AsInt32();
        	coapResponse.getOptions().setContentFormat(contentFormat);

			String groupName = ret.get(ret.size() - 1).get(GroupcommParameters.GROUP_NAME).AsString();
			String locationPath = new String(groupCollectionResourcePath + "/" + groupName);

			coapResponse.getOptions().setLocationPath(locationPath);
    	}
    	byte[] responsePayload = null;
    	if (ret.get(2) == null) {
    		responsePayload = Bytes.EMPTY;
    	}
    	else if (ret.get(2).getType() == CBORType.Map) {
    		responsePayload = ret.get(2).EncodeToBytes();
    	}
    	else if (ret.get(2).getType() == CBORType.TextString) {
    		responsePayload = ret.get(2).AsString().getBytes(Constants.charset);
    	}
    	coapResponse.setPayload(responsePayload);

    	exchange.respond(coapResponse);
    	
    }
    
	/**
     * Create a new group-configuration resource
     * 
     * @param requestCBOR  the POST request to the group-collection resource
     * @param adminScopeEntries  the adminScopeEntries retrieved from the access token for the requester Administrator
     * @return  a CBOR array with three elements, in this order
     * 			- The CoAP response code for the response to the Administrator, as a CBOR integer
     * 			- The CoAP Content-Format to use in the response to the Administrator, as a CBOR integer. It can be null
     * 			- The payload for the response to the Administrator, as a CBOR map or a CBOR text string. It can be null
     * 
     */
    private CBORObject createNewGroupConfiguration(final Request request, final CBORObject[] adminScopeEntries) {
    	
    	String groupName = null;
    	CBORObject ret = CBORObject.NewArray();
    	
    	CBORObject requestCBOR = CBORObject.DecodeFromBytes(request.getPayload());

    	// Build a preliminary group configuration, with the final name still to be determined
    	CBORObject buildOutput = GroupOSCOREGroupConfigurationResource.buildGroupConfiguration(requestCBOR, null);
    	
    	// In case of failure, return the information to return an error response to the Administrator
    	if (buildOutput.size() == 3) {

    		for (int i = 0; i < buildOutput.size(); i++) {
    			ret.Add(buildOutput.get(i));
    		}
    		return ret;
    	}
    	
    	// Determine the group name to use
    	String proposedName = requestCBOR.get(GroupcommParameters.GROUP_NAME).AsString();
    	groupName = allocateGroupName(proposedName, adminScopeEntries);
    	
    	if (groupName == null) {
    		// No available and suitable name could be allocated for the new group.
    		//
    		// Return the information for replying with an error response.
    		ret.Add(CoAP.ResponseCode.INTERNAL_SERVER_ERROR.value);
    		ret.Add(Constants.APPLICATION_CONCISE_PROBLEM_DETAILS_CBOR);
    		CBORObject payloadCBOR = CBORObject.NewMap();
    		
    		CBORObject aceGroupcommError = CBORObject.NewMap();
    		aceGroupcommError.Add(0, GroupcommErrors.GROUP_NAME_NOT_DETERMINED);
    		payloadCBOR.Add(Constants.PROBLEM_DETAIL_ACE_GROUPCOMM_ERROR, aceGroupcommError);
    		payloadCBOR.Add(Constants.PROBLEM_DETAIL_KEY_TITLE, GroupcommErrors.DESCRIPTION[GroupcommErrors.GROUP_NAME_NOT_DETERMINED]);

    		ret.Add(payloadCBOR);
    		return ret;
    	}
    	
    	// The new name is available and suitable. Add the group configuration to the collection
    	
    	// Complete the group configuration with the selected group name
    	CBORObject groupConfiguration = buildOutput.get(3);
    	groupConfiguration.Add(GroupcommParameters.GROUP_NAME, groupName);
    	
    	// Complete the group configuration with the URI of the associated group-membership resource
    	String requestUri = request.getURI();
    	int index = requestUri.lastIndexOf(super.getURI());
    	String baseUri = request.getURI().substring(0, index + 1);
    	String joiningUri = baseUri + rootGroupMembershipResourcePath + "/" + groupName;
    	groupConfiguration.Add(GroupcommParameters.JOINING_URI, joiningUri);
    	
    	// Complete the group configuration with the URI of the associated Authorization Server
    	groupConfiguration.Add(GroupcommParameters.AS_URI, this.asUri);
    	
    	GroupOSCOREGroupConfigurationResource groupConfigurationResource = null;
    	
    	synchronized(groupConfigurationResources) {
            groupConfigurationResource =  new GroupOSCOREGroupConfigurationResource(groupName, groupConfiguration,
            																		this.groupConfigurationResources,
														  							this.existingGroupInfo);
            groupConfigurationResources.put(groupName, groupConfigurationResource);
            	
    	}

    	Set<Short> actions = new HashSet<>();
    	actions.add(Constants.GET);
    	actions.add(Constants.FETCH);
    	actions.add(Constants.POST);
    	actions.add(Constants.PATCH);
    	actions.add(Constants.iPATCH);
    	actions.add(Constants.DELETE);
    	this.myScopes.get(groupCollectionResourcePath).put(groupCollectionResourcePath + "/" + groupName, actions);
    	
    	try {
			this.valid.setGroupAdminResources(Collections.singleton(groupCollectionResourcePath + "/" + groupName));
		} catch (AceException e) {
			groupConfigurationResources.remove(groupName); // rollback
			myScopes.get(groupCollectionResourcePath).remove(groupCollectionResourcePath + "/" + groupName); // rollback
			
			String errorString = new String ("Error while initializing the group-configuration resource");			
    		ret.Add(CoAP.ResponseCode.INTERNAL_SERVER_ERROR.value);
    		ret.Add(null);
    		CBORObject payloadCBOR = CBORObject.FromObject(errorString);
    		ret.Add(payloadCBOR);
    		System.err.println(errorString + "\n" + e.getMessage());
    		return ret;
		}

    	this.add(groupConfigurationResource);
    	
    	
    	// Create the group-membership resource and make it actually accessible
    	
    	createGroupMembershipResource(groupConfigurationResource.getConfigurationParameters());
    	
    	
    	// Finalize the payload for the response to the Administrator
    	
    	CBORObject finalPayloadCBOR = CBORObject.NewMap();
    	
    	finalPayloadCBOR = buildOutput.get(2);    	
    	finalPayloadCBOR.Add(GroupcommParameters.GROUP_NAME, groupName);
    	finalPayloadCBOR.Add(GroupcommParameters.JOINING_URI, joiningUri);
    	finalPayloadCBOR.Add(GroupcommParameters.AS_URI, this.asUri);
    	
    	ret.Add(buildOutput.get(0));
    	ret.Add(buildOutput.get(1));
    	ret.Add(finalPayloadCBOR);
    	
    	return ret;
    	
    }

	/**
     * Check whether the group name proposed by the requesting Administrator can be used for the new group to be created.
     * 
     * If that is not the case, attempt to find an alternative group name. If the originally proposed name can be used
     * or an alternative group name is found, confirm it as the name of the new group to be created.
     * 
     * @param proposedGroupName  the group name originally proposed in the POST request from the Administrator
     * @param adminScopeEntries  the adminScopeEntries retrieved from the access token for the requester Administrator
     * @return  the new, alternative name to assign to the group, or null if it was not possible to determine one
     * 
     */
    private String allocateGroupName(final String proposedGroupName, final CBORObject[] adminScopeEntries) {
    	
    	String newName = null;
    	
    	synchronized (groupConfigurationResources) {
    		
        	if (!groupConfigurationResources.containsKey(proposedGroupName)) {
        		// The proposed name is available.
        		
        		// Check if there is at least one scope entry such that the name matches the name pattern
        		// and the set of permissions includes the "Create" admin permission
        		boolean permitted = false;
        		
        		for (int i = 0; i < adminScopeEntries.length; i++) {
        		    try {
        		        short permissions = (short) adminScopeEntries[i].get(1).AsInt32();
        		        if (Util.checkGroupOSCOREAdminPermission(permissions, GroupcommParameters.GROUP_OSCORE_ADMIN_CREATE)) {
        		        	permitted = Util.matchingGroupOscoreName(proposedGroupName, adminScopeEntries[i].get(0));
        		        }
        		    } catch (AceException e) {
        		        System.err.println("Error while verifying the admin permissions: " + e.getMessage());
        		    }
        		    if (permitted) {
        		        break;
        		    }
        		}
        		if (permitted) {
	        		// Reserve the proposed name, by adding a dummy entry
	        		// in the collection of group-configuration resources
	        		newName = new String(proposedGroupName);
	        		groupConfigurationResources.put(newName, null);
	        		return newName;
        		}
        	}
        	// The proposed group name is not available, or the requesting Administrator does not
        	// have the permission to create a new group with that name. Try to find a new name.
        	
        	// The result is either a group name that is available and can be given to a group
        	// created by the requesting Administrator, or null if no group name could be found
        	newName = findAlternativeGroupName(proposedGroupName, adminScopeEntries);
        	
    		if (newName != null) {
        		// Reserve the proposed name, by adding a dummy entry
        		// in the collection of group-configuration resources
        		groupConfigurationResources.put(newName, null);
    		}
    		
    	}
    	
    	return newName;
    	
    }
    
	/**
     * Try to find an alternative name for a new group to be created
     * 
     * @param proposedGroupName  the group name originally proposed in the POST request from the Administrator
     * @param adminScopeEntries  the adminScopeEntries retrieved from the access token for the requester Administrator
     * @return  the new, alternative name to assign to the group, or null if it was not possible to determine one
     * 
     */
    private String findAlternativeGroupName(final String proposedGroupName, final CBORObject[] adminScopeEntries) {

    	// The set of permissions resulting from the union of the Tperm of all the admin scope entries whose Toid is the
    	// wildcard name pattern. That is, any possible group name is associated with at least this set of permissions.
    	int basePermissions = 0;
    	
    	// The set of permissions that the requesting Administrator has for the originally proposed group name 'proposedGroupName'.
    	// This is the union of the Tperm values from the admin scope entries against whose Toid 'proposedGroupName' matches.
    	// If an alternative group name is found, this must be associated with exactly the same set of permissions (no more, no less).
    	int targetPermissions = 0;   
    	
    	// The admin scope entries for this Administrator such that their Toid is a literal name pattern.
    	// This does not include the entry whose Toid is equal to the originally proposed group name.
    	Set<CBORObject> literalPatternEntries = new HashSet<>();

    	// The admin scope entries for this Administrator such that their Toid is a complex name pattern (I-Regexp regular expressions).
    	Set<CBORObject> complexPatternEntries = new HashSet<>();
    	    	
    	
    	// PHASE 1 - Fill data structures
    	
    	// Go through all the admin scope entries for this Administrator
    	for (int i = 0; i < adminScopeEntries.length; i++) {
    		
    		CBORObject toid = adminScopeEntries[i].get(0);
    		short tperm = (short) adminScopeEntries[i].get(1).AsInt32();
    		
    		// Toid is the wildcard pattern
    		if (toid.equals(CBORObject.True)) {
    			basePermissions |= tperm;
    			targetPermissions |= tperm;
    		}

    		if ((toid.getType() == CBORType.TextString)) {

    			String pattern = toid.AsString();
    			
        		// Toid is a literal pattern
    			if (toid.isTagged() == false) {
    				if (pattern.equals(proposedGroupName)) {
    					targetPermissions |= tperm;
    				}
    				else {
    					literalPatternEntries.add(adminScopeEntries[i]);
    				}
    			}
    			
    			// Toid is a complex pattern (I-Regexp regular expression)
    			if (toid.HasTag(21065)) {
    				Pattern pat = Pattern.compile(pattern);
    				Matcher myMatcher = pat.matcher(proposedGroupName);
    				if (myMatcher.matches()) {
    					// The originally proposed name matches against this complex name pattern
    					targetPermissions |= tperm;
    				}
    				complexPatternEntries.add(adminScopeEntries[i]);
    			}
    			
    		}
    		
    	}
    	
    	
    	// PHASE 2 - Check if a group name from a stored literal pattern works
    	
    	for (CBORObject entryLiteral : literalPatternEntries) {
    		
    		String newName = new String(entryLiteral.get(0).AsString());
    		
    		int newPermissions = basePermissions;
    		newPermissions |= entryLiteral.get(1).AsInt32();
    		
    		if ((groupConfigurationResources.containsKey(newName)) || (newPermissions > targetPermissions)) {
    			continue;
    		}

    		for (CBORObject entryComplex : complexPatternEntries) {
    			String pattern = entryComplex.get(0).AsString();
				Pattern pat = Pattern.compile(pattern);
				Matcher myMatcher = pat.matcher(newName);
				if (myMatcher.matches()) {
					// The possible new name matches against this complex name pattern
    				newPermissions |= entryComplex.get(1).AsInt32();
    				if (newPermissions > targetPermissions) {
    					break;
    				}
				}
    		}

    		if ((newPermissions == targetPermissions) && (!groupConfigurationResources.containsKey(newName))) {
    			return newName;
    		}
    		
    	}
    	
    	
    	// PHASE 3 - Try random strings
    	
    	for (int i = 0; i < maxRandomGroupNameAttempts; i++) {
    		
    		String newName = generateRandomGroupName();
    		if ((groupConfigurationResources.containsKey(newName))) {
    			continue;
    		}
    		
    		int newPermissions = basePermissions;
    		
    		// Go through the admin scope entries for this Administrator where Toid is a literal name pattern
    		boolean invalid = false;
    		for (CBORObject entryLiteral : literalPatternEntries) {
    			
    			if (newName.equals(entryLiteral.get(0).AsString())) {
    				newPermissions |= entryLiteral.get(1).AsInt32();
    				if (newPermissions > targetPermissions) {
    					// There is a match, but this group name cannot be considered, since
    					// it would give the Administrator more permissions than intended
    					invalid = true;
    				}
    				// Stop inspecting the admin scope entries where Toid is a literal name pattern.
					// In fact, there cannot be another entry with the same Toid again.
    				break;
    			}

    		}
    		if (invalid) {
    			// Move on and generate a new group name to consider altogether
    			continue;
    		}
    		
    		// Go through the admin scope entries for this Administrator where
    		// Toid is a complex name pattern (I-Regexp regular expressions).
    		for (CBORObject entryComplex : complexPatternEntries) {
    			
      			String pattern = entryComplex.get(0).AsString();
				Pattern pat = Pattern.compile(pattern);
				Matcher myMatcher = pat.matcher(newName);
				if (myMatcher.matches()) {
					newPermissions |= entryComplex.get(1).AsInt32();
    				if (newPermissions > targetPermissions) {
    					// There is a match, but this group name cannot be considered, since
    					// it would give the Administrator more permissions than intended
    					invalid = true;
    					
        				// Stop inspecting the admin scope entries where Toid is
    					// a complex name pattern (I-Regexp regular expression).
        				break;
    				}

				}
    			
    		}
    		if (invalid) {
    			// Move on and generate a new group name to consider altogether
    			continue;
    		}
    		
    		if ((newPermissions == targetPermissions) && (!groupConfigurationResources.containsKey(newName))) {
    			return newName;
    		}
    		
    	}
    	
    	
    	// PHASE 4 - Try random strings that match with the available complex name patterns by construction
    	
		// Go through the admin scope entries for this Administrator where
		// Toid is a complex name pattern (I-Regexp regular expressions).
    	for (CBORObject entryComplex : complexPatternEntries) {
    		
    		Generex generex = new Generex(entryComplex.get(0).AsString());
    		
        	for (int i = 0; i < maxRandomGroupNameAttemptsPerPattern; i++) {
	    		
        		String newName = generex.random(1, maxRandomGroupNameLength);
        		
        		if ((groupConfigurationResources.containsKey(newName))) {
        			continue;
        		}
        		
        		int newPermissions = basePermissions;
        		newPermissions |= entryComplex.get(1).AsInt32();
        		
        		if (newPermissions > targetPermissions) {
        			// This group name cannot be considered, since it would
					// give the Administrator more permissions than intended
        			
        			// Move on and generate a new new that matches with
        			// this same complex name pattern by construction
        			continue;
        		}
        		
        		// Go through the admin scope entries for this Administrator where Toid is a literal name pattern
        		boolean invalid = false;
        		for (CBORObject entryLiteral : literalPatternEntries) {
        			
        			if (newName.equals(entryLiteral.get(0).AsString())) {
        				newPermissions |= entryLiteral.get(1).AsInt32();
        				if (newPermissions > targetPermissions) {
        					// There is a match, but this group name cannot be considered, since
        					// it would give the Administrator more permissions than intended
        					invalid = true;
        				}
        				// Stop inspecting the admin scope entries where Toid is a literal name pattern.
    					// In fact, there cannot be another entry with the same Toid again.
        				break;
        			}
        			
        		}
        		if (invalid) {
        			// Move on and generate a new group name to consider altogether,
        			// still matching with this same complex name pattern by construction
        			continue;
        		}
        		
        		// Go through the OTHER admin scope entries for this Administrator where
        		// Toid is a complex name pattern (I-Regexp regular expressions).
        		for (CBORObject entryComplexAlt : complexPatternEntries) {
        			
        			if (entryComplexAlt.equals(entryComplex))
        				continue;
        			
          			String patternAlt = entryComplexAlt.get(0).AsString();
    				Pattern patAlt = Pattern.compile(patternAlt);
    				Matcher myMatcherAlt = patAlt.matcher(newName);
    				if (myMatcherAlt.matches()) {
    					newPermissions |= entryComplexAlt.get(1).AsInt32();
        				if (newPermissions > targetPermissions) {
        					// There is a match, but this group name cannot be considered, since
        					// it would give the Administrator more permissions than intended
        					invalid = true;
        					
            				// Stop inspecting the admin scope entries where Toid is
        					// a complex name pattern (I-Regexp regular expression).
            				break;
        				}

    				}
        			
        		}
        		if (invalid) {
        			// Move on and generate a new group name to consider altogether,
        			// still matching with this same complex name pattern by construction
        			continue;
        		}
        		
        		if ((newPermissions == targetPermissions) && (!groupConfigurationResources.containsKey(newName))) {
        			return newName;
        		}
        		
        	}

    	}

    	return null;

    }

	/**
     * Generate a random group name
     * 
     * @return  the generated group name
     * 
     */
    private String generateRandomGroupName() {
    	
    	final String[] validCharacters = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M",
				  						  "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
				  						  "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
				  						  "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
				  						  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "-", ".", "_",
				  						  "~", "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=", ":", "@"};

		SecureRandom secureRandom1 = new SecureRandom();
		SecureRandom secureRandom2 = new SecureRandom();

		StringBuilder newName = new StringBuilder();
		int length = secureRandom1.nextInt(maxRandomGroupNameLength) + 1;

		for (int j = 0; j < length; j++) {
			int index = secureRandom2.nextInt(validCharacters.length);
			newName.append(validCharacters[index]);
		}

		return newName.toString();

    }
    
    /**
      * Create a new group-membership resource, following the creation of the corresponding group-configuration resource
      * 
      * @param groupConfiguration  the group configuration
      * 
      * @return  true if the creation succeeds, false otherwise
     */
    private boolean createGroupMembershipResource(final CBORObject groupConfiguration) {
    	
    	String groupName = groupConfiguration.get(GroupcommParameters.GROUP_NAME).AsString();
    	
    	// Include a new scope associated with the new group-membership resource and its sub-resources
    	
    	Map<String, Set<Short>> scopeDescription = new HashMap<>();
    	Set<Short> actions = new HashSet<>();
    	actions.add(Constants.FETCH);
    	scopeDescription.put(rootGroupMembershipResourcePath, actions);
    	actions = new HashSet<>();
    	actions.add(Constants.GET);
    	actions.add(Constants.POST);
    	scopeDescription.put(rootGroupMembershipResourcePath + "/" + groupName, actions);
    	actions = new HashSet<>();
    	actions.add(Constants.GET);
    	actions.add(Constants.FETCH);
    	scopeDescription.put(rootGroupMembershipResourcePath + "/" + groupName + "/creds", actions);
    	actions = new HashSet<>();
    	actions.add(Constants.GET);
    	scopeDescription.put(rootGroupMembershipResourcePath + "/" + groupName + "/kdc-cred", actions);
    	scopeDescription.put(rootGroupMembershipResourcePath + "/" + groupName + "/verif-data", actions);
    	scopeDescription.put(rootGroupMembershipResourcePath + "/" + groupName + "/num", actions);
    	scopeDescription.put(rootGroupMembershipResourcePath + "/" + groupName + "/active", actions);
    	scopeDescription.put(rootGroupMembershipResourcePath + "/" + groupName + "/policies", actions);
    	actions = new HashSet<>();
    	actions.add(Constants.FETCH);
    	scopeDescription.put(rootGroupMembershipResourcePath + "/" + groupName + "/stale-sids", actions);
    	myScopes.put(rootGroupMembershipResourcePath + "/" + groupName, scopeDescription);
    	
    	
    	// Mark the new group-membership resource and its sub-resources as such for the access Validator
    	
    	try {
	    	valid.setGroupMembershipResources(Collections.singleton(rootGroupMembershipResourcePath + "/" + groupName));
	    	valid.setGroupMembershipResources(Collections.singleton(rootGroupMembershipResourcePath + "/" + groupName + "/creds"));
	    	valid.setGroupMembershipResources(Collections.singleton(rootGroupMembershipResourcePath + "/" + groupName + "/kdc-cred"));
	    	valid.setGroupMembershipResources(Collections.singleton(rootGroupMembershipResourcePath + "/" + groupName + "/verif-data"));
	    	valid.setGroupMembershipResources(Collections.singleton(rootGroupMembershipResourcePath + "/" + groupName + "/num"));
	    	valid.setGroupMembershipResources(Collections.singleton(rootGroupMembershipResourcePath + "/" + groupName + "/active"));
	    	valid.setGroupMembershipResources(Collections.singleton(rootGroupMembershipResourcePath + "/" + groupName + "/policies"));
	    	valid.setGroupMembershipResources(Collections.singleton(rootGroupMembershipResourcePath + "/" + groupName + "/stale-sids"));
    	}
    	catch (AceException e) {
    		System.err.println("Error while verifying the admin permissions: " + e.getMessage());
    		return false;
    	}
    	
    	
    	// Create the actual associated group-membership resource and its sub-resources

    	// Group-membership resource - The name of the OSCORE group is used as resource name
    	Resource groupMembershipResource = new GroupOSCOREGroupMembershipResource(groupName,
    	                                                                          this.existingGroupInfo,
    	                                                                          rootGroupMembershipResourcePath,
    	                                                                          this.myScopes,
    	                                                                          this.valid);
    	// Add the /creds sub-resource
    	Resource credsSubResource = new GroupOSCORESubResourceCreds("creds", existingGroupInfo);
    	groupMembershipResource.add(credsSubResource);

    	// Add the /kdc-cred sub-resource
    	Resource kdcCredSubResource = new GroupOSCORESubResourceKdcCred("kdc-cred", existingGroupInfo);
    	groupMembershipResource.add(kdcCredSubResource);

    	// Add the /verif-data sub-resource
    	Resource verifDataSubResource = new GroupOSCORESubResourceVerifData("verif-data", existingGroupInfo);
    	groupMembershipResource.add(verifDataSubResource);

    	// Add the /num sub-resource
    	Resource numSubResource = new GroupOSCORESubResourceNum("num", existingGroupInfo);
    	groupMembershipResource.add(numSubResource);

    	// Add the /active sub-resource
    	Resource activeSubResource = new GroupOSCORESubResourceActive("active", existingGroupInfo);
    	groupMembershipResource.add(activeSubResource);

    	// Add the /policies sub-resource
    	Resource policiesSubResource = new GroupOSCORESubResourcePolicies("policies", existingGroupInfo);
    	groupMembershipResource.add(policiesSubResource);

    	// Add the /stale-sids sub-resource
    	Resource staleSidsSubResource = new GroupOSCORESubResourceStaleSids("stale-sids", existingGroupInfo);
    	groupMembershipResource.add(staleSidsSubResource);

    	// Add the /nodes sub-resource, as root to actually accessible per-node sub-resources
    	Resource nodesSubResource = new GroupOSCORESubResourceNodes("nodes");
    	groupMembershipResource.add(nodesSubResource);
    	
    	
    	// Create the GroupInfo object according to the group configuration
    	
    	final byte[] masterSecret = new byte[16];
    	try {
			SecureRandom.getInstanceStrong().nextBytes(masterSecret);
		} catch (NoSuchAlgorithmException e) {
			System.err.println("Error when generating the OSCORE Master Secret for the OSCORE group with name \"" + groupName + "\"");
			e.printStackTrace();
			return false;
		}
    	
    	final byte[] masterSalt = new byte[8];
    	try {
			SecureRandom.getInstanceStrong().nextBytes(masterSalt);
		} catch (NoSuchAlgorithmException e) {
			System.err.println("Error when generating the OSCORE Master Salt for the OSCORE group with name \"" + groupName + "\"");
			e.printStackTrace();
			return false;
		}
    	
    	final AlgorithmID hkdf;
    	try {
			hkdf = AlgorithmID.FromCBOR(groupConfiguration.get(GroupcommParameters.HKDF));
		} catch (CoseException e) {
			System.err.println("Error when setting the HKDF Algorithm for the OSCORE group with name \"" + groupName + "\"");
			e.printStackTrace();
			return false;
		}
    	
    	final int credFmt = groupConfiguration.get(GroupcommParameters.CRED_FMT).AsInt32();
    	
		AlgorithmID gpEncAlg = null;
    	try {
			if (!groupConfiguration.get(GroupcommParameters.GP_ENC_ALG).isNull()) {
				gpEncAlg = AlgorithmID.FromCBOR(groupConfiguration.get(GroupcommParameters.GP_ENC_ALG));
			}
		} catch (CoseException e) {
			System.err.println("Error when setting the Group Encryption Algorithm for the OSCORE group with name \"" + groupName + "\"");
			e.printStackTrace();
			return false;
		}
    	
		AlgorithmID signAlg = null;
    	try {
			if (!groupConfiguration.get(GroupcommParameters.SIGN_ALG).isNull()) {
				signAlg = AlgorithmID.FromCBOR(groupConfiguration.get(GroupcommParameters.SIGN_ALG));
			}
		} catch (CoseException e) {
			System.err.println("Error when setting the Signature Algorithm for the OSCORE group with name \"" + groupName + "\"");
			e.printStackTrace();
			return false;
		}
    	
    	final CBORObject signParams = groupConfiguration.get(GroupcommParameters.SIGN_PARAMS);

		AlgorithmID alg = null;
    	try {
			if (!groupConfiguration.get(GroupcommParameters.ALG).isNull()) {
				alg = AlgorithmID.FromCBOR(groupConfiguration.get(GroupcommParameters.ALG));
			}
		} catch (CoseException e) {
			System.err.println("Error when setting the AEAD Algorithm for the OSCORE group with name \"" + groupName + "\"");
			e.printStackTrace();
			return false;
		}
    	
		AlgorithmID ecdhAlg = null;
    	try {
			if (!groupConfiguration.get(GroupcommParameters.ECDH_ALG).isNull()) {
				ecdhAlg = AlgorithmID.FromCBOR(groupConfiguration.get(GroupcommParameters.ECDH_ALG));
			}
		} catch (CoseException e) {
			System.err.println("Error when setting the Pairwise Key Agreement Algorithm for the OSCORE group with name \"" + groupName + "\"");
			e.printStackTrace();
			return false;
		}
    	
    	final CBORObject ecdhParams = groupConfiguration.get(GroupcommParameters.ECDH_PARAMS);
    	
    	    	
    	// Generate the Group ID, according to the following rationale:
    	//
    	// - The Prefix uniquely identifies an OSCORE group throughout its rekeying occurrences.
    	//   The Prefix size is the same for all the OSCORE groups and is up to 4 bytes.
    	//
    	// - The Epoch of an Group ID changes each time the group is rekeyed. Its size is up to 4 bytes.
    	// - The initial value of Epoch is all zeroes.
    	
    	boolean available = false;
    	byte[] groupIdPrefix = new byte[this.groupIdPrefixSize];
    	byte[] groupIdEpoch = new byte[] { (byte) 0x00, (byte) 0x00 };
    	
    	synchronized (this.usedGroupIdPrefixes) {
    		
        	int sizeLimit = (int) Math.pow(2, this.groupIdPrefixSize);
        	if (this.usedGroupIdPrefixes.size() == sizeLimit) {
        		// Rollback
    			groupConfigurationResources.remove(groupName);
    			myScopes.get(groupCollectionResourcePath).remove(groupCollectionResourcePath + "/" + groupName);
        		
    			System.err.println("No available Group IDs for creating the OSCORE group with name \"" + groupName + "\"");
    			return false;
        	}
        	        	
        	CBORObject groupIdPrefixCbor = null;
        	while(available == false) {
            	try {
        			SecureRandom.getInstanceStrong().nextBytes(groupIdPrefix);
        		} catch (NoSuchAlgorithmException e) {
        			System.err.println("Error when generating the OSCORE Group ID for the OSCORE group with name \"" + groupName + "\"");
        			e.printStackTrace();
        			return false;
        		}
            	groupIdPrefixCbor = CBORObject.FromObject(groupIdPrefix);
            	available = (this.usedGroupIdPrefixes.contains(groupIdPrefixCbor) == false);
        	}
        	
        	this.usedGroupIdPrefixes.add(groupIdPrefixCbor);
        	
    	}

    	
    	// Set the asymmetric key pair and public key of the Group Manager
    	
    	// Asymmetric key pair, as a OneKey object
    	
    	OneKey gmKeyPair = null;
    	CBORObject parameters;
    	
    	boolean useGroupMode = groupConfiguration.get(GroupcommParameters.GROUP_MODE).AsBoolean();
    	if (useGroupMode == true) {
    		parameters = groupConfiguration.get(GroupcommParameters.SIGN_PARAMS);
    	}
    	else {
    		parameters = groupConfiguration.get(GroupcommParameters.ECDH_PARAMS);
    	}
    	gmKeyPair = Util.retrieveGmKeyPair(gmSigningKeyPairs, gmKeyAgreementKeyPairs, useGroupMode, parameters);
    	
    	if (gmKeyPair == null) {
    		// This should never happen
    		
    		// Rollback
			groupConfigurationResources.remove(groupName);
			myScopes.get(groupCollectionResourcePath).remove(groupCollectionResourcePath + "/" + groupName);
    		
			System.err.println("Error when setting up the Group Manager's authentication credential" +
							   "for the OSCORE group with name \"" + groupName + "\"");
			return false;
    	}
    	    	
    	// Serialization of the public authentication credential, according to the format used in the group
    	
    	byte[] gmAuthCred = Util.retrieveGmAuthCred(credFmt, gmSigningPublicAuthCred, gmKeyAgreementPublicAuthCred, useGroupMode, parameters);


    	int mode = GroupcommParameters.GROUP_OSCORE_GROUP_PAIRWISE_MODE;
    	boolean usePairwiseMode = groupConfiguration.get(GroupcommParameters.PAIRWISE_MODE).AsBoolean();
    	if (useGroupMode == true && usePairwiseMode == true) {
    		mode = GroupcommParameters.GROUP_OSCORE_GROUP_PAIRWISE_MODE;
    	}
    	else if (useGroupMode == true && usePairwiseMode == false) {
    		mode = GroupcommParameters.GROUP_OSCORE_GROUP_MODE_ONLY;
    	}
    	else if (useGroupMode == false && usePairwiseMode == true) {
    		mode = GroupcommParameters.GROUP_OSCORE_PAIRWISE_MODE_ONLY;
    	}
    	
    	CBORObject groupPolicies = groupConfiguration.get(GroupcommParameters.GROUP_POLICIES);
    	
    	boolean groupIdReuse = groupConfiguration.get(GroupcommParameters.GID_REUSE).AsBoolean();
    	
    	int maxStaleIdsSets = groupConfiguration.get(GroupcommParameters.MAX_STALE_SETS).AsInt32();
    	
    	int detHashAlg = 0;
    	if (groupConfiguration.get(GroupcommParameters.DET_REQ) != null) {
    		if (groupConfiguration.get(GroupcommParameters.DET_REQ).equals(CBORObject.True)) {
    			if (groupConfiguration.get(GroupcommParameters.DET_HASH_ALG) != null) {
    				detHashAlg = groupConfiguration.get(GroupcommParameters.DET_HASH_ALG).AsInt32();
    			}
    			else {
    				detHashAlg = -16; // SHA-256
    			}
    		}	
    	}
    	
    	GroupInfo myGroupInfo = new GroupInfo(groupName,
										      masterSecret,
										      masterSalt,
										      groupIdPrefixSize,
										      groupIdPrefix,
										      groupIdEpoch.length,
										      Util.bytesToInt(groupIdEpoch),
										      groupIdReuse,
										      prefixMonitorNames,
										      nodeNameSeparator,
										      hkdf,
										      credFmt,
										      mode,
										      gpEncAlg,
										      signAlg,
										      signParams,
										      alg,
										      ecdhAlg,
										      ecdhParams,
										      groupPolicies,
										      gmKeyPair,
										      gmAuthCred,
										      gmSigningKeyPairs,
										      gmSigningPublicAuthCred,
										      gmKeyAgreementKeyPairs,
										      gmKeyAgreementPublicAuthCred,
										      maxStaleIdsSets,
										      detHashAlg);
    	
    	boolean initialStatus = groupConfiguration.get(GroupcommParameters.ACTIVE).AsBoolean();
    	myGroupInfo.setStatus(initialStatus);
    	
    	if (groupConfiguration.get(GroupcommParameters.EXP) != null) {
    		Long exp = groupConfiguration.get(GroupcommParameters.EXP).AsInt64Value();
    		myGroupInfo.setExp(exp);
    	}

    	
		// Store the information on this OSCORE group
    	this.existingGroupInfo.put(groupName, myGroupInfo);
    	
    	// Finally make the group-membership resource accessible
    	this.groupOSCORERootGroupMembership.add(groupMembershipResource);
    	
    	return true;
    	
    }

}