hfp.c (1d81c7fe685e46ecabc2566e0d735e259106f522) | hfp.c (94a27792279a0f8d9dcb16416be8a05c5b9cc58d) |
---|---|
1/* 2 * Copyright (C) 2014 BlueKitchen GmbH 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright --- 822 unchanged lines hidden (view full) --- 831 remove_hfp_connection_context(hfp_connection); 832 break; 833 834 default: 835 break; 836 } 837} 838// translates command string into hfp_command_t CMD | 1/* 2 * Copyright (C) 2014 BlueKitchen GmbH 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright --- 822 unchanged lines hidden (view full) --- 831 remove_hfp_connection_context(hfp_connection); 832 break; 833 834 default: 835 break; 836 } 837} 838// translates command string into hfp_command_t CMD |
839static hfp_command_t parse_command(const char * line_buffer, int isHandsFree){ 840 int offset = isHandsFree ? 0 : 2; | |
841 | 839 |
842 if (strncmp(line_buffer+offset, HFP_CALL_PHONE_NUMBER, strlen(HFP_CALL_PHONE_NUMBER)) == 0){ 843 return HFP_CMD_CALL_PHONE_NUMBER; 844 } | 840typedef struct { 841 const char * command; 842 hfp_command_t command_id; 843} hfp_command_entry_t; |
845 | 844 |
846 if (strncmp(line_buffer+offset, HFP_LIST_CURRENT_CALLS, strlen(HFP_LIST_CURRENT_CALLS)) == 0){ 847 return HFP_CMD_LIST_CURRENT_CALLS; 848 } | 845static hfp_command_entry_t hfp_ag_commmand_table[] = { 846 { "AT+BIND=", HFP_CMD_LIST_GENERIC_STATUS_INDICATORS }, 847 { "AT+BIND=?", HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS }, 848 { "AT+BIND?", HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS_STATE }, 849 { "AT+BTRH=", HFP_CMD_RESPONSE_AND_HOLD_COMMAND}, 850 { "AT+BTRH?", HFP_CMD_RESPONSE_AND_HOLD_QUERY}, 851 { "AT+CHLD=", HFP_CMD_CALL_HOLD }, 852 { "AT+CHLD=?", HFP_CMD_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES }, 853 { "AT+CIND=?", HFP_CMD_RETRIEVE_AG_INDICATORS}, 854 { "AT+CIND?", HFP_CMD_RETRIEVE_AG_INDICATORS_STATUS }, 855 { "AT+COPS=", HFP_CMD_QUERY_OPERATOR_SELECTION_NAME_FORMAT }, 856 { "AT+COPS?", HFP_CMD_QUERY_OPERATOR_SELECTION_NAME }, 857 { "ATA", HFP_CMD_CALL_ANSWERED }, 858}; |
849 | 859 |
850 if (strncmp(line_buffer+offset, HFP_SUBSCRIBER_NUMBER_INFORMATION, strlen(HFP_SUBSCRIBER_NUMBER_INFORMATION)) == 0){ 851 return HFP_CMD_GET_SUBSCRIBER_NUMBER_INFORMATION; 852 } | 860static hfp_command_entry_t hfp_hf_commmand_table[] = { 861 { "+BIND:", HFP_CMD_SET_GENERIC_STATUS_INDICATOR_STATUS }, 862 { "+BTRH:", HFP_CMD_RESPONSE_AND_HOLD_STATUS }, 863 { "+CHLD:", HFP_CMD_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES }, 864 { "+COPS:", HFP_CMD_QUERY_OPERATOR_SELECTION_NAME }, 865}; |
853 | 866 |
854 if (strncmp(line_buffer+offset, HFP_PHONE_NUMBER_FOR_VOICE_TAG, strlen(HFP_PHONE_NUMBER_FOR_VOICE_TAG)) == 0){ 855 if (isHandsFree) return HFP_CMD_AG_SENT_PHONE_NUMBER; 856 return HFP_CMD_HF_REQUEST_PHONE_NUMBER; 857 } | 867typedef struct { 868 const char * command; 869 hfp_command_t hf_command_id; 870 hfp_command_t ag_command_id; 871} hfp_command_table_t; |
858 | 872 |
859 if (strncmp(line_buffer+offset, HFP_TRANSMIT_DTMF_CODES, strlen(HFP_TRANSMIT_DTMF_CODES)) == 0){ 860 return HFP_CMD_TRANSMIT_DTMF_CODES; 861 } | 873static hfp_command_table_t hfp_command_table[] = { 874 { "+BAC", HFP_CMD_AVAILABLE_CODECS, HFP_CMD_AVAILABLE_CODECS}, 875 { "+BCC", HFP_CMD_TRIGGER_CODEC_CONNECTION_SETUP, HFP_CMD_TRIGGER_CODEC_CONNECTION_SETUP}, 876 { "+BCS", HFP_CMD_AG_SUGGESTED_CODEC, HFP_CMD_HF_CONFIRMED_CODEC}, 877 { "+BIA", HFP_CMD_ENABLE_INDIVIDUAL_AG_INDICATOR_STATUS_UPDATE, HFP_CMD_ENABLE_INDIVIDUAL_AG_INDICATOR_STATUS_UPDATE}, // +BIA:<enabled>,,<enabled>,,,<enabled> 878 { "+BIEV", HFP_CMD_HF_INDICATOR_STATUS, HFP_CMD_HF_INDICATOR_STATUS}, 879 { "+BINP", HFP_CMD_AG_SENT_PHONE_NUMBER, HFP_CMD_HF_REQUEST_PHONE_NUMBER}, 880 { "+BLDN", HFP_CMD_REDIAL_LAST_NUMBER, HFP_CMD_REDIAL_LAST_NUMBER}, 881 { "+BRSF", HFP_CMD_SUPPORTED_FEATURES, HFP_CMD_SUPPORTED_FEATURES}, 882 { "+BSIR", HFP_CMD_CHANGE_IN_BAND_RING_TONE_SETTING, HFP_CMD_CHANGE_IN_BAND_RING_TONE_SETTING}, 883 { "+BVRA", HFP_CMD_AG_ACTIVATE_VOICE_RECOGNITION, HFP_CMD_HF_ACTIVATE_VOICE_RECOGNITION }, // EC (Echo CAnceling), NR (Noise Reduction) 884 { "+CCWA", HFP_CMD_AG_SENT_CALL_WAITING_NOTIFICATION_UPDATE, HFP_CMD_ENABLE_CALL_WAITING_NOTIFICATION}, 885 { "+CHUP", HFP_CMD_HANG_UP_CALL, HFP_CMD_HANG_UP_CALL}, 886 { "+CIEV", HFP_CMD_TRANSFER_AG_INDICATOR_STATUS, HFP_CMD_TRANSFER_AG_INDICATOR_STATUS}, 887 { "+CLCC", HFP_CMD_LIST_CURRENT_CALLS, HFP_CMD_LIST_CURRENT_CALLS}, 888 { "+CLIP", HFP_CMD_AG_SENT_CLIP_INFORMATION, HFP_CMD_ENABLE_CLIP}, 889 { "+CME ERROR", HFP_CMD_EXTENDED_AUDIO_GATEWAY_ERROR, HFP_CMD_NONE}, 890 { "+CMEE", HFP_CMD_NONE, HFP_CMD_ENABLE_EXTENDED_AUDIO_GATEWAY_ERROR}, 891 { "+CMER", HFP_CMD_ENABLE_INDICATOR_STATUS_UPDATE, HFP_CMD_ENABLE_INDICATOR_STATUS_UPDATE}, 892 { "+CNUM", HFP_CMD_GET_SUBSCRIBER_NUMBER_INFORMATION, HFP_CMD_GET_SUBSCRIBER_NUMBER_INFORMATION}, 893 { "+NREC", HFP_CMD_TURN_OFF_EC_AND_NR, HFP_CMD_TURN_OFF_EC_AND_NR }, // EC (Echo CAnceling), NR (Noise Reduction)} 894 { "+VGM", HFP_CMD_SET_MICROPHONE_GAIN, HFP_CMD_SET_MICROPHONE_GAIN}, 895 { "+VGS", HFP_CMD_SET_SPEAKER_GAIN, HFP_CMD_SET_SPEAKER_GAIN}, 896 { "+VTS", HFP_CMD_TRANSMIT_DTMF_CODES, HFP_CMD_TRANSMIT_DTMF_CODES}, 897 { "ERROR", HFP_CMD_ERROR, HFP_CMD_ERROR}, 898 { "NOP", HFP_CMD_NONE, HFP_CMD_NONE}, // dummy commmand used by unit tests 899 { "OK", HFP_CMD_OK, HFP_CMD_NONE}, 900 { "RING", HFP_CMD_RING, HFP_CMD_RING}, 901}; |
862 | 902 |
863 if (strncmp(line_buffer+offset, HFP_SET_MICROPHONE_GAIN, strlen(HFP_SET_MICROPHONE_GAIN)) == 0){ 864 return HFP_CMD_SET_MICROPHONE_GAIN; 865 } | 903static hfp_command_t parse_command(const char * line_buffer, int isHandsFree){ |
866 | 904 |
867 if (strncmp(line_buffer+offset, HFP_SET_SPEAKER_GAIN, strlen(HFP_SET_SPEAKER_GAIN)) == 0){ 868 return HFP_CMD_SET_SPEAKER_GAIN; 869 } 870 871 if (strncmp(line_buffer+offset, HFP_ACTIVATE_VOICE_RECOGNITION, strlen(HFP_ACTIVATE_VOICE_RECOGNITION)) == 0){ 872 if (isHandsFree) return HFP_CMD_AG_ACTIVATE_VOICE_RECOGNITION; 873 return HFP_CMD_HF_ACTIVATE_VOICE_RECOGNITION; 874 } 875 876 if (strncmp(line_buffer+offset, HFP_TURN_OFF_EC_AND_NR, strlen(HFP_TURN_OFF_EC_AND_NR)) == 0){ 877 return HFP_CMD_TURN_OFF_EC_AND_NR; 878 } 879 880 if (strncmp(line_buffer, HFP_ANSWER_CALL, strlen(HFP_ANSWER_CALL)) == 0){ 881 return HFP_CMD_CALL_ANSWERED; 882 } 883 884 if (strncmp(line_buffer, HFP_CALL_PHONE_NUMBER, strlen(HFP_CALL_PHONE_NUMBER)) == 0){ | 905 // note: if parser in CMD_HEADER state would treats digits and maybe '+' as separator, match on "ATD" would work. 906 // prefix match on 'ATD', AG only 907 if ((isHandsFree == 0) && (strncmp(line_buffer, HFP_CALL_PHONE_NUMBER, strlen(HFP_CALL_PHONE_NUMBER)) == 0)){ |
885 return HFP_CMD_CALL_PHONE_NUMBER; 886 } 887 | 908 return HFP_CMD_CALL_PHONE_NUMBER; 909 } 910 |
888 if (strncmp(line_buffer+offset, HFP_REDIAL_LAST_NUMBER, strlen(HFP_REDIAL_LAST_NUMBER)) == 0){ 889 return HFP_CMD_REDIAL_LAST_NUMBER; 890 } | 911 uint16_t offset = isHandsFree ? 0 : 2; |
891 | 912 |
892 if (strncmp(line_buffer+offset, HFP_CHANGE_IN_BAND_RING_TONE_SETTING, strlen(HFP_CHANGE_IN_BAND_RING_TONE_SETTING)) == 0){ 893 return HFP_CMD_CHANGE_IN_BAND_RING_TONE_SETTING; 894 } | 913 uint16_t i; 914 uint16_t num_entries; |
895 | 915 |
896 if (strncmp(line_buffer+offset, HFP_HANG_UP_CALL, strlen(HFP_HANG_UP_CALL)) == 0){ 897 return HFP_CMD_HANG_UP_CALL; | 916 // role-based table lookup 917 hfp_command_entry_t * table; 918 if (isHandsFree == 0){ 919 table = hfp_ag_commmand_table; 920 num_entries = sizeof(hfp_ag_commmand_table) / sizeof(hfp_command_entry_t); 921 } else { 922 table = hfp_hf_commmand_table; 923 num_entries = sizeof(hfp_hf_commmand_table) / sizeof(hfp_command_entry_t); |
898 } | 924 } |
899 900 if (strncmp(line_buffer+offset, HFP_ERROR, strlen(HFP_ERROR)) == 0){ 901 return HFP_CMD_ERROR; 902 } 903 904 if (strncmp(line_buffer+offset, HFP_RING, strlen(HFP_RING)) == 0){ 905 return HFP_CMD_RING; 906 } 907 908 if (isHandsFree && (strncmp(line_buffer+offset, HFP_OK, strlen(HFP_OK)) == 0)){ 909 return HFP_CMD_OK; 910 } 911 912 if (strncmp(line_buffer+offset, HFP_SUPPORTED_FEATURES, strlen(HFP_SUPPORTED_FEATURES)) == 0){ 913 return HFP_CMD_SUPPORTED_FEATURES; 914 } 915 916 if (strncmp(line_buffer+offset, HFP_TRANSFER_HF_INDICATOR_STATUS, strlen(HFP_TRANSFER_HF_INDICATOR_STATUS)) == 0){ 917 return HFP_CMD_HF_INDICATOR_STATUS; 918 } 919 920 if (strncmp(line_buffer+offset, HFP_RESPONSE_AND_HOLD, strlen(HFP_RESPONSE_AND_HOLD)) == 0){ 921 if (strncmp(line_buffer+strlen(HFP_RESPONSE_AND_HOLD)+offset, "?", 1) == 0){ 922 return HFP_CMD_RESPONSE_AND_HOLD_QUERY; | 925 for (i=0;i<num_entries;i++) { 926 hfp_command_entry_t *entry = &table[i]; 927 int match = strcmp(line_buffer, entry->command); 928 if (match == 0){ 929 return entry->command_id; |
923 } | 930 } |
924 if (strncmp(line_buffer+strlen(HFP_RESPONSE_AND_HOLD)+offset, "=", 1) == 0){ 925 return HFP_CMD_RESPONSE_AND_HOLD_COMMAND; 926 } 927 return HFP_CMD_RESPONSE_AND_HOLD_STATUS; | |
928 } 929 | 931 } 932 |
930 if (strncmp(line_buffer+offset, HFP_INDICATOR, strlen(HFP_INDICATOR)) == 0){ 931 if (strncmp(line_buffer+strlen(HFP_INDICATOR)+offset, "?", 1) == 0){ 932 return HFP_CMD_RETRIEVE_AG_INDICATORS_STATUS; | 933 // combined table lookup 934 num_entries = sizeof(hfp_command_table) / sizeof(hfp_command_table_t); 935 for (i=0;i<num_entries;i++){ 936 hfp_command_table_t * entry = &hfp_command_table[i]; 937 bool match = strncmp(line_buffer+offset, entry->command, strlen(entry->command)) == 0; 938 if (match){ 939 if (isHandsFree == 1){ 940 if (entry->hf_command_id != HFP_CMD_NONE) return entry->hf_command_id; 941 } else { 942 if (entry->ag_command_id != HFP_CMD_NONE) return entry->ag_command_id; 943 } |
933 } | 944 } |
934 935 if (strncmp(line_buffer+strlen(HFP_INDICATOR)+offset, "=?", 2) == 0){ 936 return HFP_CMD_RETRIEVE_AG_INDICATORS; 937 } | |
938 } 939 | 945 } 946 |
940 if (strncmp(line_buffer+offset, HFP_AVAILABLE_CODECS, strlen(HFP_AVAILABLE_CODECS)) == 0){ 941 return HFP_CMD_AVAILABLE_CODECS; 942 } 943 944 if (strncmp(line_buffer+offset, HFP_ENABLE_STATUS_UPDATE_FOR_AG_INDICATORS, strlen(HFP_ENABLE_STATUS_UPDATE_FOR_AG_INDICATORS)) == 0){ 945 return HFP_CMD_ENABLE_INDICATOR_STATUS_UPDATE; 946 } 947 948 if (strncmp(line_buffer+offset, HFP_ENABLE_CLIP, strlen(HFP_ENABLE_CLIP)) == 0){ 949 if (isHandsFree) return HFP_CMD_AG_SENT_CLIP_INFORMATION; 950 return HFP_CMD_ENABLE_CLIP; 951 } 952 953 if (strncmp(line_buffer+offset, HFP_ENABLE_CALL_WAITING_NOTIFICATION, strlen(HFP_ENABLE_CALL_WAITING_NOTIFICATION)) == 0){ 954 if (isHandsFree) return HFP_CMD_AG_SENT_CALL_WAITING_NOTIFICATION_UPDATE; 955 return HFP_CMD_ENABLE_CALL_WAITING_NOTIFICATION; 956 } 957 958 if (strncmp(line_buffer+offset, HFP_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES, strlen(HFP_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES)) == 0){ 959 960 if (isHandsFree) return HFP_CMD_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES; 961 962 if (strncmp(line_buffer+strlen(HFP_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES)+offset, "=?", 2) == 0){ 963 return HFP_CMD_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES; 964 } 965 if (strncmp(line_buffer+strlen(HFP_SUPPORT_CALL_HOLD_AND_MULTIPARTY_SERVICES)+offset, "=", 1) == 0){ 966 return HFP_CMD_CALL_HOLD; 967 } 968 | 947 if (strncmp(line_buffer+offset, "AT+", 3) == 0){ |
969 return HFP_CMD_UNKNOWN; | 948 return HFP_CMD_UNKNOWN; |
970 } 971 972 if (strncmp(line_buffer+offset, HFP_GENERIC_STATUS_INDICATOR, strlen(HFP_GENERIC_STATUS_INDICATOR)) == 0){ 973 if (isHandsFree) { 974 return HFP_CMD_SET_GENERIC_STATUS_INDICATOR_STATUS; 975 } 976 if (strncmp(line_buffer+strlen(HFP_GENERIC_STATUS_INDICATOR)+offset, "=?", 2) == 0){ 977 return HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS; 978 } 979 if (strncmp(line_buffer+strlen(HFP_GENERIC_STATUS_INDICATOR)+offset, "=", 1) == 0){ 980 return HFP_CMD_LIST_GENERIC_STATUS_INDICATORS; 981 } 982 return HFP_CMD_RETRIEVE_GENERIC_STATUS_INDICATORS_STATE; 983 } 984 985 if (strncmp(line_buffer+offset, HFP_UPDATE_ENABLE_STATUS_FOR_INDIVIDUAL_AG_INDICATORS, strlen(HFP_UPDATE_ENABLE_STATUS_FOR_INDIVIDUAL_AG_INDICATORS)) == 0){ 986 return HFP_CMD_ENABLE_INDIVIDUAL_AG_INDICATOR_STATUS_UPDATE; 987 } 988 989 990 if (strncmp(line_buffer+offset, HFP_QUERY_OPERATOR_SELECTION, strlen(HFP_QUERY_OPERATOR_SELECTION)) == 0){ 991 if (strncmp(line_buffer+strlen(HFP_QUERY_OPERATOR_SELECTION)+offset, "=", 1) == 0){ 992 return HFP_CMD_QUERY_OPERATOR_SELECTION_NAME_FORMAT; 993 } 994 return HFP_CMD_QUERY_OPERATOR_SELECTION_NAME; | |
995 } 996 | 949 } 950 |
997 if (strncmp(line_buffer+offset, HFP_TRANSFER_AG_INDICATOR_STATUS, strlen(HFP_TRANSFER_AG_INDICATOR_STATUS)) == 0){ 998 return HFP_CMD_TRANSFER_AG_INDICATOR_STATUS; 999 } 1000 1001 if (isHandsFree && (strncmp(line_buffer+offset, HFP_EXTENDED_AUDIO_GATEWAY_ERROR, strlen(HFP_EXTENDED_AUDIO_GATEWAY_ERROR)) == 0)){ 1002 return HFP_CMD_EXTENDED_AUDIO_GATEWAY_ERROR; 1003 } 1004 1005 if (!isHandsFree && (strncmp(line_buffer+offset, HFP_ENABLE_EXTENDED_AUDIO_GATEWAY_ERROR, strlen(HFP_ENABLE_EXTENDED_AUDIO_GATEWAY_ERROR)) == 0)){ 1006 return HFP_CMD_ENABLE_EXTENDED_AUDIO_GATEWAY_ERROR; 1007 } 1008 1009 if (strncmp(line_buffer+offset, HFP_TRIGGER_CODEC_CONNECTION_SETUP, strlen(HFP_TRIGGER_CODEC_CONNECTION_SETUP)) == 0){ 1010 return HFP_CMD_TRIGGER_CODEC_CONNECTION_SETUP; 1011 } 1012 1013 if (strncmp(line_buffer+offset, HFP_CONFIRM_COMMON_CODEC, strlen(HFP_CONFIRM_COMMON_CODEC)) == 0){ 1014 if (isHandsFree){ 1015 return HFP_CMD_AG_SUGGESTED_CODEC; 1016 } else { 1017 return HFP_CMD_HF_CONFIRMED_CODEC; 1018 } 1019 } 1020 1021 if (strncmp(line_buffer+offset, "AT+", 3) == 0){ 1022 return HFP_CMD_UNKNOWN; 1023 } 1024 | |
1025 if (strncmp(line_buffer+offset, "+", 1) == 0){ 1026 return HFP_CMD_UNKNOWN; 1027 } | 951 if (strncmp(line_buffer+offset, "+", 1) == 0){ 952 return HFP_CMD_UNKNOWN; 953 } |
1028 1029 if (strncmp(line_buffer+offset, "NOP", 3) == 0){ 1030 return HFP_CMD_NONE; 1031 } 1032 | 954 |
1033 return HFP_CMD_NONE; 1034} 1035 1036static void hfp_parser_store_byte(hfp_connection_t * hfp_connection, uint8_t byte){ 1037 if ((hfp_connection->line_size + 1 ) >= HFP_MAX_INDICATOR_DESC_SIZE) return; 1038 hfp_connection->line_buffer[hfp_connection->line_size++] = byte; 1039 hfp_connection->line_buffer[hfp_connection->line_size] = 0; 1040} 1041static int hfp_parser_is_buffer_empty(hfp_connection_t * hfp_connection){ 1042 return hfp_connection->line_size == 0; 1043} 1044 1045static int hfp_parser_is_end_of_line(uint8_t byte){ 1046 return (byte == '\n') || (byte == '\r'); 1047} 1048 | 955 return HFP_CMD_NONE; 956} 957 958static void hfp_parser_store_byte(hfp_connection_t * hfp_connection, uint8_t byte){ 959 if ((hfp_connection->line_size + 1 ) >= HFP_MAX_INDICATOR_DESC_SIZE) return; 960 hfp_connection->line_buffer[hfp_connection->line_size++] = byte; 961 hfp_connection->line_buffer[hfp_connection->line_size] = 0; 962} 963static int hfp_parser_is_buffer_empty(hfp_connection_t * hfp_connection){ 964 return hfp_connection->line_size == 0; 965} 966 967static int hfp_parser_is_end_of_line(uint8_t byte){ 968 return (byte == '\n') || (byte == '\r'); 969} 970 |
1049void hfp_parser_reset_line_buffer(hfp_connection_t *hfp_connection) { | 971static void hfp_parser_reset_line_buffer(hfp_connection_t *hfp_connection) { |
1050 hfp_connection->line_size = 0; 1051 hfp_connection->line_buffer[0] = 0; 1052} 1053 1054static void hfp_parser_store_if_token(hfp_connection_t * hfp_connection, uint8_t byte){ 1055 switch (byte){ 1056 case ',': 1057 case ';': --- 614 unchanged lines hidden --- | 972 hfp_connection->line_size = 0; 973 hfp_connection->line_buffer[0] = 0; 974} 975 976static void hfp_parser_store_if_token(hfp_connection_t * hfp_connection, uint8_t byte){ 977 switch (byte){ 978 case ',': 979 case ';': --- 614 unchanged lines hidden --- |