mirror of
				https://gitlab.freedesktop.org/pipewire/pipewire.git
				synced 2025-11-03 09:01:54 -05:00 
			
		
		
		
	avb: add more state machine
This commit is contained in:
		
							parent
							
								
									893251c8eb
								
							
						
					
					
						commit
						0efc02cea6
					
				
					 2 changed files with 239 additions and 28 deletions
				
			
		| 
						 | 
					@ -26,6 +26,15 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "mrp.h"
 | 
					#include "mrp.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct attribute {
 | 
				
			||||||
 | 
						struct avbtp_mrp_attribute attr;
 | 
				
			||||||
 | 
						struct spa_list link;
 | 
				
			||||||
 | 
						uint8_t applicant_state;
 | 
				
			||||||
 | 
						uint8_t registrar_state;
 | 
				
			||||||
 | 
						uint16_t pending_indications;
 | 
				
			||||||
 | 
						struct spa_callbacks cb;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct mrp {
 | 
					struct mrp {
 | 
				
			||||||
	struct server *server;
 | 
						struct server *server;
 | 
				
			||||||
	struct spa_hook server_listener;
 | 
						struct spa_hook server_listener;
 | 
				
			||||||
| 
						 | 
					@ -43,10 +52,23 @@ static const struct server_events server_events = {
 | 
				
			||||||
	.destroy = mrp_destroy,
 | 
						.destroy = mrp_destroy,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void avbtp_mrp_attribute_init(struct avbtp_mrp *mrp,
 | 
					struct avbtp_mrp_attribute *avbtp_mrp_attribute_new(struct avbtp_mrp *mrp,
 | 
				
			||||||
		struct avbtp_mrp_attribute *attr,
 | 
							uint8_t type, uint8_t param,
 | 
				
			||||||
		uint8_t type, void *info)
 | 
							struct avbtp_mrp_attribute_methods *methods, void *data,
 | 
				
			||||||
 | 
							size_t user_size)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
						struct attribute *a;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a = calloc(1, sizeof(*a) + user_size);
 | 
				
			||||||
 | 
						if (a == NULL)
 | 
				
			||||||
 | 
							return NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a->attr.type = type;
 | 
				
			||||||
 | 
						a->attr.param = param;
 | 
				
			||||||
 | 
						a->attr.user_data = SPA_PTROFF(a, sizeof(*a), void);
 | 
				
			||||||
 | 
						a->cb = SPA_CALLBACKS_INIT(methods, data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return &a->attr;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void stop_avb_timer(void)
 | 
					static void stop_avb_timer(void)
 | 
				
			||||||
| 
						 | 
					@ -59,49 +81,232 @@ static void start_avb_timer(void)
 | 
				
			||||||
void avbtp_mrp_update_state(struct avbtp_mrp *mrp,
 | 
					void avbtp_mrp_update_state(struct avbtp_mrp *mrp,
 | 
				
			||||||
		struct avbtp_mrp_attribute *attr, int event, uint8_t param)
 | 
							struct avbtp_mrp_attribute *attr, int event, uint8_t param)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
						struct attribute *a = SPA_CONTAINER_OF(attr, struct attribute, attr);
 | 
				
			||||||
	switch (event) {
 | 
						switch (event) {
 | 
				
			||||||
	case AVBTP_MRP_EVENT_BEGIN:
 | 
						case AVBTP_MRP_EVENT_BEGIN:
 | 
				
			||||||
		attr->registrar_state = AVBTP_MRP_MT;
 | 
							a->registrar_state = AVBTP_MRP_MT;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	case AVBTP_MRP_EVENT_RX_NEW:
 | 
						case AVBTP_MRP_EVENT_RX_NEW:
 | 
				
			||||||
		if (attr->registrar_state == AVBTP_MRP_LV)
 | 
							if (a->registrar_state == AVBTP_MRP_LV)
 | 
				
			||||||
			stop_avb_timer();
 | 
								stop_avb_timer();
 | 
				
			||||||
		attr->registrar_state = AVBTP_MRP_IN;
 | 
							a->registrar_state = AVBTP_MRP_IN;
 | 
				
			||||||
		attr->pending_indications |= AVBTP_PENDING_JOIN_NEW;
 | 
							a->pending_indications |= AVBTP_PENDING_JOIN_NEW;
 | 
				
			||||||
		attr->param = param;
 | 
							a->attr.param = param;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	case AVBTP_MRP_EVENT_RX_JOININ:
 | 
						case AVBTP_MRP_EVENT_RX_JOININ:
 | 
				
			||||||
	case AVBTP_MRP_EVENT_RX_JOINMT:
 | 
						case AVBTP_MRP_EVENT_RX_JOINMT:
 | 
				
			||||||
		if (attr->registrar_state == AVBTP_MRP_LV)
 | 
							if (a->registrar_state == AVBTP_MRP_LV)
 | 
				
			||||||
			stop_avb_timer();
 | 
								stop_avb_timer();
 | 
				
			||||||
		if (attr->registrar_state == AVBTP_MRP_MT) {
 | 
							if (a->registrar_state == AVBTP_MRP_MT) {
 | 
				
			||||||
			attr->pending_indications |= AVBTP_PENDING_JOIN;
 | 
								a->pending_indications |= AVBTP_PENDING_JOIN;
 | 
				
			||||||
			attr->param = param;
 | 
								a->attr.param = param;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		attr->registrar_state = AVBTP_MRP_IN;
 | 
							a->registrar_state = AVBTP_MRP_IN;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	case AVBTP_MRP_EVENT_RX_LV:
 | 
						case AVBTP_MRP_EVENT_RX_LV:
 | 
				
			||||||
	case AVBTP_MRP_EVENT_RX_LVA:
 | 
						case AVBTP_MRP_EVENT_RX_LVA:
 | 
				
			||||||
	case AVBTP_MRP_EVENT_TX_LVA:
 | 
						case AVBTP_MRP_EVENT_TX_LVA:
 | 
				
			||||||
	case AVBTP_MRP_EVENT_REDECLARE:
 | 
						case AVBTP_MRP_EVENT_REDECLARE:
 | 
				
			||||||
		if (attr->registrar_state == AVBTP_MRP_IN) {
 | 
							if (a->registrar_state == AVBTP_MRP_IN) {
 | 
				
			||||||
			start_avb_timer();
 | 
								start_avb_timer();
 | 
				
			||||||
			attr->registrar_state = AVBTP_MRP_LV;
 | 
								a->registrar_state = AVBTP_MRP_LV;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	case AVBTP_MRP_EVENT_LV_TIMER:
 | 
						case AVBTP_MRP_EVENT_LV_TIMER:
 | 
				
			||||||
	case AVBTP_MRP_EVENT_FLUSH:
 | 
						case AVBTP_MRP_EVENT_FLUSH:
 | 
				
			||||||
		if (attr->registrar_state == AVBTP_MRP_LV) {
 | 
							if (a->registrar_state == AVBTP_MRP_LV) {
 | 
				
			||||||
			attr->pending_indications |= AVBTP_PENDING_LEAVE;
 | 
								a->pending_indications |= AVBTP_PENDING_LEAVE;
 | 
				
			||||||
			attr->param = param;
 | 
								a->attr.param = param;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		attr->registrar_state = AVBTP_MRP_MT;
 | 
							a->registrar_state = AVBTP_MRP_MT;
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		break;
 | 
							break;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (event) {
 | 
				
			||||||
 | 
						case AVBTP_MRP_EVENT_BEGIN:
 | 
				
			||||||
 | 
							a->applicant_state = AVBTP_MRP_VO;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case AVBTP_MRP_EVENT_NEW:
 | 
				
			||||||
 | 
							a->applicant_state = AVBTP_MRP_VN;
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case AVBTP_MRP_EVENT_JOIN:
 | 
				
			||||||
 | 
							switch (a->applicant_state) {
 | 
				
			||||||
 | 
							case AVBTP_MRP_VO:
 | 
				
			||||||
 | 
							case AVBTP_MRP_LO:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_VP;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_LA:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_AA;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_AO:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_AP;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_QO:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_QP;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case AVBTP_MRP_EVENT_LV:
 | 
				
			||||||
 | 
							switch (a->applicant_state) {
 | 
				
			||||||
 | 
							case AVBTP_MRP_QP:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_QO;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_AP:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_AO;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_VP:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_VO;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_VN:
 | 
				
			||||||
 | 
							case AVBTP_MRP_AN:
 | 
				
			||||||
 | 
							case AVBTP_MRP_AA:
 | 
				
			||||||
 | 
							case AVBTP_MRP_QA:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_LA;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case AVBTP_MRP_EVENT_RX_JOININ:
 | 
				
			||||||
 | 
							switch (a->applicant_state) {
 | 
				
			||||||
 | 
							case AVBTP_MRP_VO:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_AO;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_VP:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_AP;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_AA:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_QA;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_AO:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_QO;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_AP:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_QP;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							SPA_FALLTHROUGH;
 | 
				
			||||||
 | 
						case AVBTP_MRP_EVENT_RX_IN:
 | 
				
			||||||
 | 
							switch (a->applicant_state) {
 | 
				
			||||||
 | 
							case AVBTP_MRP_AA:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_QA;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							SPA_FALLTHROUGH;
 | 
				
			||||||
 | 
						case AVBTP_MRP_EVENT_RX_JOINMT:
 | 
				
			||||||
 | 
						case AVBTP_MRP_EVENT_RX_MT:
 | 
				
			||||||
 | 
							switch (a->applicant_state) {
 | 
				
			||||||
 | 
							case AVBTP_MRP_QA:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_AA;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_QO:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_AO;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_QP:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_AP;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_LO:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_VO;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case AVBTP_MRP_EVENT_RX_LV:
 | 
				
			||||||
 | 
						case AVBTP_MRP_EVENT_RX_LVA:
 | 
				
			||||||
 | 
						case AVBTP_MRP_EVENT_REDECLARE:
 | 
				
			||||||
 | 
							switch (a->applicant_state) {
 | 
				
			||||||
 | 
							case AVBTP_MRP_VO:
 | 
				
			||||||
 | 
							case AVBTP_MRP_AO:
 | 
				
			||||||
 | 
							case AVBTP_MRP_QO:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_LO;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_AN:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_VN;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_AA:
 | 
				
			||||||
 | 
							case AVBTP_MRP_QA:
 | 
				
			||||||
 | 
							case AVBTP_MRP_AP:
 | 
				
			||||||
 | 
							case AVBTP_MRP_QP:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_VP;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case AVBTP_MRP_EVENT_PERIODIC:
 | 
				
			||||||
 | 
							switch (a->applicant_state) {
 | 
				
			||||||
 | 
							case AVBTP_MRP_QA:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_AA;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_QP:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_AP;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case AVBTP_MRP_EVENT_TX:
 | 
				
			||||||
 | 
							switch (a->applicant_state) {
 | 
				
			||||||
 | 
							case AVBTP_MRP_VP:
 | 
				
			||||||
 | 
							case AVBTP_MRP_VN:
 | 
				
			||||||
 | 
							case AVBTP_MRP_AN:
 | 
				
			||||||
 | 
							case AVBTP_MRP_AA:
 | 
				
			||||||
 | 
							case AVBTP_MRP_LA:
 | 
				
			||||||
 | 
							case AVBTP_MRP_AP:
 | 
				
			||||||
 | 
							case AVBTP_MRP_LO:
 | 
				
			||||||
 | 
					//			int vector = makeTxEvent(e, st, 0);
 | 
				
			||||||
 | 
					//			doTx(st, vector);
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							switch (a->applicant_state) {
 | 
				
			||||||
 | 
							case AVBTP_MRP_VP:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_AA;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_VN:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_AN;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_AN:
 | 
				
			||||||
 | 
							case AVBTP_MRP_AA:
 | 
				
			||||||
 | 
							case AVBTP_MRP_AP:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_QA;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_LA:
 | 
				
			||||||
 | 
							case AVBTP_MRP_LO:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_VO;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						case AVBTP_MRP_EVENT_TX_LVA:
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							switch (a->applicant_state) {
 | 
				
			||||||
 | 
							case AVBTP_MRP_VP:
 | 
				
			||||||
 | 
							case AVBTP_MRP_VN:
 | 
				
			||||||
 | 
							case AVBTP_MRP_AN:
 | 
				
			||||||
 | 
							case AVBTP_MRP_AA:
 | 
				
			||||||
 | 
							case AVBTP_MRP_LA:
 | 
				
			||||||
 | 
							case AVBTP_MRP_QA:
 | 
				
			||||||
 | 
							case AVBTP_MRP_AP:
 | 
				
			||||||
 | 
							case AVBTP_MRP_QP:
 | 
				
			||||||
 | 
					//			int vector = makeTxEvent(e, st, 1);
 | 
				
			||||||
 | 
					//			doTx(st, vector);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							switch (a->applicant_state) {
 | 
				
			||||||
 | 
							case AVBTP_MRP_VO:
 | 
				
			||||||
 | 
							case AVBTP_MRP_LA:
 | 
				
			||||||
 | 
							case AVBTP_MRP_AO:
 | 
				
			||||||
 | 
							case AVBTP_MRP_QO:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_LO;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_VN:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_AN;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							case AVBTP_MRP_AN:
 | 
				
			||||||
 | 
							case AVBTP_MRP_AA:
 | 
				
			||||||
 | 
							case AVBTP_MRP_AP:
 | 
				
			||||||
 | 
							case AVBTP_MRP_QP:
 | 
				
			||||||
 | 
								a->applicant_state = AVBTP_MRP_QA;
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							break;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void avbtp_mrp_event(struct avbtp_mrp *mrp, struct avbtp_mrp_attribute *attr,
 | 
					void avbtp_mrp_event(struct avbtp_mrp *mrp, struct avbtp_mrp_attribute *attr,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -106,17 +106,23 @@ struct avbtp_packet_mrp_footer {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct avbtp_mrp_attribute {
 | 
					struct avbtp_mrp_attribute {
 | 
				
			||||||
	uint8_t type;
 | 
						uint8_t type;
 | 
				
			||||||
	uint8_t applicant_state;
 | 
					 | 
				
			||||||
	uint8_t registrar_state;
 | 
					 | 
				
			||||||
	uint16_t pending_indications;
 | 
					 | 
				
			||||||
	uint8_t param;
 | 
						uint8_t param;
 | 
				
			||||||
	struct avbtp_mrp_attribute *next;
 | 
						void *user_data;
 | 
				
			||||||
	void *attribute_info;
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void avbtp_mrp_attribute_init(struct avbtp_mrp *mrp,
 | 
					struct avbtp_mrp_attribute_methods {
 | 
				
			||||||
		struct avbtp_mrp_attribute *attr,
 | 
					#define AVBTP_VERSION_ATTRIBUTE_METHODS	0
 | 
				
			||||||
		uint8_t type, void *info);
 | 
						uint32_t version;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int (*compare) (void *data, struct avbtp_mrp_attribute *a, struct avbtp_mrp_attribute *b);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int (*merge) (void *data, struct avbtp_mrp_attribute *a, uint8_t *msg, int vector);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct avbtp_mrp_attribute *avbtp_mrp_attribute_new(struct avbtp_mrp *mrp,
 | 
				
			||||||
 | 
							uint8_t type, uint8_t param,
 | 
				
			||||||
 | 
							struct avbtp_mrp_attribute_methods *methods, void *data,
 | 
				
			||||||
 | 
							size_t user_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void avbtp_mrp_update_state(struct avbtp_mrp *mrp,
 | 
					void avbtp_mrp_update_state(struct avbtp_mrp *mrp,
 | 
				
			||||||
		struct avbtp_mrp_attribute *attr, int event, uint8_t param);
 | 
							struct avbtp_mrp_attribute *attr, int event, uint8_t param);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue